mirror of https://github.com/procxx/kepka.git
				
				
				
			Use .visit() in export data parsing.
This commit is contained in:
		
							parent
							
								
									5a9d1a3fce
								
							
						
					
					
						commit
						35ffc03988
					
				| 
						 | 
					@ -35,15 +35,13 @@ int32 BarePeerId(PeerId peerId) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PeerId ParsePeerId(const MTPPeer &data) {
 | 
					PeerId ParsePeerId(const MTPPeer &data) {
 | 
				
			||||||
	switch (data.type()) {
 | 
						return data.visit([](const MTPDpeerUser &data) {
 | 
				
			||||||
	case mtpc_peerUser:
 | 
							return UserPeerId(data.vuser_id.v);
 | 
				
			||||||
		return UserPeerId(data.c_peerUser().vuser_id.v);
 | 
						}, [](const MTPDpeerChat &data) {
 | 
				
			||||||
	case mtpc_peerChat:
 | 
							return ChatPeerId(data.vchat_id.v);
 | 
				
			||||||
		return ChatPeerId(data.c_peerChat().vchat_id.v);
 | 
						}, [](const MTPDpeerChannel &data) {
 | 
				
			||||||
	case mtpc_peerChannel:
 | 
							return ChatPeerId(data.vchannel_id.v);
 | 
				
			||||||
		return ChatPeerId(data.c_peerChannel().vchannel_id.v);
 | 
						});
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	Unexpected("Type in ParsePeerId.");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Utf8String ParseString(const MTPstring &data) {
 | 
					Utf8String ParseString(const MTPstring &data) {
 | 
				
			||||||
| 
						 | 
					@ -51,29 +49,23 @@ Utf8String ParseString(const MTPstring &data) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FileLocation ParseLocation(const MTPFileLocation &data) {
 | 
					FileLocation ParseLocation(const MTPFileLocation &data) {
 | 
				
			||||||
	switch (data.type()) {
 | 
						return data.visit([](const MTPDfileLocation &data) {
 | 
				
			||||||
	case mtpc_fileLocation: {
 | 
							return FileLocation{
 | 
				
			||||||
		const auto &location = data.c_fileLocation();
 | 
								data.vdc_id.v,
 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			location.vdc_id.v,
 | 
					 | 
				
			||||||
			MTP_inputFileLocation(
 | 
								MTP_inputFileLocation(
 | 
				
			||||||
				location.vvolume_id,
 | 
									data.vvolume_id,
 | 
				
			||||||
				location.vlocal_id,
 | 
									data.vlocal_id,
 | 
				
			||||||
				location.vsecret)
 | 
									data.vsecret)
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	} break;
 | 
						}, [](const MTPDfileLocationUnavailable &data) {
 | 
				
			||||||
	case mtpc_fileLocationUnavailable: {
 | 
							return FileLocation{
 | 
				
			||||||
		const auto &location = data.c_fileLocationUnavailable();
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			MTP_inputFileLocation(
 | 
								MTP_inputFileLocation(
 | 
				
			||||||
				location.vvolume_id,
 | 
									data.vvolume_id,
 | 
				
			||||||
				location.vlocal_id,
 | 
									data.vlocal_id,
 | 
				
			||||||
				location.vsecret)
 | 
									data.vsecret)
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	} break;
 | 
						});
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	Unexpected("Type in ParseLocation.");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
File ParseMaxImage(
 | 
					File ParseMaxImage(
 | 
				
			||||||
| 
						 | 
					@ -84,50 +76,36 @@ File ParseMaxImage(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto maxArea = int64(0);
 | 
						auto maxArea = int64(0);
 | 
				
			||||||
	for (const auto &size : data.v) {
 | 
						for (const auto &size : data.v) {
 | 
				
			||||||
		switch (size.type()) {
 | 
							size.visit([&](const MTPDphotoSize &data) {
 | 
				
			||||||
		case mtpc_photoSize: {
 | 
								const auto area = data.vw.v * int64(data.vh.v);
 | 
				
			||||||
			const auto &fields = size.c_photoSize();
 | 
					 | 
				
			||||||
			const auto area = fields.vw.v * int64(fields.vh.v);
 | 
					 | 
				
			||||||
			if (area > maxArea) {
 | 
								if (area > maxArea) {
 | 
				
			||||||
				result.location = ParseLocation(fields.vlocation);
 | 
									result.location = ParseLocation(data.vlocation);
 | 
				
			||||||
				result.size = fields.vsize.v;
 | 
									result.size = data.vsize.v;
 | 
				
			||||||
				result.content = QByteArray();
 | 
									result.content = QByteArray();
 | 
				
			||||||
				maxArea = area;
 | 
									maxArea = area;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} break;
 | 
							}, [&](const MTPDphotoCachedSize &data) {
 | 
				
			||||||
 | 
								const auto area = data.vw.v * int64(data.vh.v);
 | 
				
			||||||
		case mtpc_photoCachedSize: {
 | 
					 | 
				
			||||||
			const auto &fields = size.c_photoCachedSize();
 | 
					 | 
				
			||||||
			const auto area = fields.vw.v * int64(fields.vh.v);
 | 
					 | 
				
			||||||
			if (area > maxArea) {
 | 
								if (area > maxArea) {
 | 
				
			||||||
				result.location = ParseLocation(fields.vlocation);
 | 
									result.location = ParseLocation(data.vlocation);
 | 
				
			||||||
				result.size = fields.vbytes.v.size();
 | 
									result.size = data.vbytes.v.size();
 | 
				
			||||||
				result.content = fields.vbytes.v;
 | 
									result.content = data.vbytes.v;
 | 
				
			||||||
				maxArea = area;
 | 
									maxArea = area;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} break;
 | 
							}, [](const MTPDphotoSizeEmpty &) {});
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
 | 
					Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
 | 
				
			||||||
	auto result = Photo();
 | 
						auto result = Photo();
 | 
				
			||||||
	switch (data.type()) {
 | 
						data.visit([&](const MTPDphoto &data) {
 | 
				
			||||||
	case mtpc_photo: {
 | 
							result.id = data.vid.v;
 | 
				
			||||||
		const auto &photo = data.c_photo();
 | 
							result.date = data.vdate.v;
 | 
				
			||||||
		result.id = photo.vid.v;
 | 
							result.image = ParseMaxImage(data.vsizes, suggestedPath);
 | 
				
			||||||
		result.date = photo.vdate.v;
 | 
						}, [&](const MTPDphotoEmpty &data) {
 | 
				
			||||||
		result.image = ParseMaxImage(photo.vsizes, suggestedPath);
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	} break;
 | 
						});
 | 
				
			||||||
 | 
					 | 
				
			||||||
	case mtpc_photoEmpty: {
 | 
					 | 
				
			||||||
		const auto &photo = data.c_photoEmpty();
 | 
					 | 
				
			||||||
		result.id = photo.vid.v;
 | 
					 | 
				
			||||||
	} break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: Unexpected("Photo type in ParsePhoto.");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,37 +144,29 @@ UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
User ParseUser(const MTPUser &data) {
 | 
					User ParseUser(const MTPUser &data) {
 | 
				
			||||||
	auto result = User();
 | 
						auto result = User();
 | 
				
			||||||
	switch (data.type()) {
 | 
						data.visit([&](const MTPDuser &data) {
 | 
				
			||||||
	case mtpc_user: {
 | 
							result.id = data.vid.v;
 | 
				
			||||||
		const auto &fields = data.c_user();
 | 
							if (data.has_first_name()) {
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
								result.firstName = ParseString(data.vfirst_name);
 | 
				
			||||||
		if (fields.has_first_name()) {
 | 
					 | 
				
			||||||
			result.firstName = ParseString(fields.vfirst_name);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (fields.has_last_name()) {
 | 
							if (data.has_last_name()) {
 | 
				
			||||||
			result.lastName = ParseString(fields.vlast_name);
 | 
								result.lastName = ParseString(data.vlast_name);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (fields.has_phone()) {
 | 
							if (data.has_phone()) {
 | 
				
			||||||
			result.phoneNumber = ParseString(fields.vphone);
 | 
								result.phoneNumber = ParseString(data.vphone);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (fields.has_username()) {
 | 
							if (data.has_username()) {
 | 
				
			||||||
			result.username = ParseString(fields.vusername);
 | 
								result.username = ParseString(data.vusername);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (fields.has_access_hash()) {
 | 
							if (data.has_access_hash()) {
 | 
				
			||||||
			result.input = MTP_inputUser(fields.vid, fields.vaccess_hash);
 | 
								result.input = MTP_inputUser(data.vid, data.vaccess_hash);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			result.input = MTP_inputUserEmpty();
 | 
								result.input = MTP_inputUserEmpty();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} break;
 | 
						}, [&](const MTPDuserEmpty &data) {
 | 
				
			||||||
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	case mtpc_userEmpty: {
 | 
					 | 
				
			||||||
		const auto &fields = data.c_userEmpty();
 | 
					 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.input = MTP_inputUserEmpty();
 | 
							result.input = MTP_inputUserEmpty();
 | 
				
			||||||
	} break;
 | 
						});
 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: Unexpected("Type in ParseUser.");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -211,52 +181,35 @@ std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Chat ParseChat(const MTPChat &data) {
 | 
					Chat ParseChat(const MTPChat &data) {
 | 
				
			||||||
	auto result = Chat();
 | 
						auto result = Chat();
 | 
				
			||||||
	switch (data.type()) {
 | 
						data.visit([&](const MTPDchat &data) {
 | 
				
			||||||
	case mtpc_chat: {
 | 
							result.id = data.vid.v;
 | 
				
			||||||
		const auto &fields = data.c_chat();
 | 
							result.title = ParseString(data.vtitle);
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.title = ParseString(fields.vtitle);
 | 
					 | 
				
			||||||
		result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
							result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
				
			||||||
	} break;
 | 
						}, [&](const MTPDchatEmpty &data) {
 | 
				
			||||||
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	case mtpc_chatEmpty: {
 | 
					 | 
				
			||||||
		const auto &fields = data.c_chatEmpty();
 | 
					 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
							result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
				
			||||||
	} break;
 | 
						}, [&](const MTPDchatForbidden &data) {
 | 
				
			||||||
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	case mtpc_chatForbidden: {
 | 
							result.title = ParseString(data.vtitle);
 | 
				
			||||||
		const auto &fields = data.c_chatForbidden();
 | 
					 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.title = ParseString(fields.vtitle);
 | 
					 | 
				
			||||||
		result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
							result.input = MTP_inputPeerChat(MTP_int(result.id));
 | 
				
			||||||
	} break;
 | 
						}, [&](const MTPDchannel &data) {
 | 
				
			||||||
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	case mtpc_channel: {
 | 
							result.broadcast = data.is_broadcast();
 | 
				
			||||||
		const auto &fields = data.c_channel();
 | 
							result.title = ParseString(data.vtitle);
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
							if (data.has_username()) {
 | 
				
			||||||
		result.broadcast = fields.is_broadcast();
 | 
								result.username = ParseString(data.vusername);
 | 
				
			||||||
		result.title = ParseString(fields.vtitle);
 | 
					 | 
				
			||||||
		if (fields.has_username()) {
 | 
					 | 
				
			||||||
			result.username = ParseString(fields.vusername);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		result.input = MTP_inputPeerChannel(
 | 
							result.input = MTP_inputPeerChannel(
 | 
				
			||||||
			MTP_int(result.id),
 | 
								MTP_int(result.id),
 | 
				
			||||||
			fields.vaccess_hash);
 | 
								data.vaccess_hash);
 | 
				
			||||||
	} break;
 | 
						}, [&](const MTPDchannelForbidden &data) {
 | 
				
			||||||
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	case mtpc_channelForbidden: {
 | 
							result.broadcast = data.is_broadcast();
 | 
				
			||||||
		const auto &fields = data.c_channelForbidden();
 | 
							result.title = ParseString(data.vtitle);
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.broadcast = fields.is_broadcast();
 | 
					 | 
				
			||||||
		result.title = ParseString(fields.vtitle);
 | 
					 | 
				
			||||||
		result.input = MTP_inputPeerChannel(
 | 
							result.input = MTP_inputPeerChannel(
 | 
				
			||||||
			MTP_int(result.id),
 | 
								MTP_int(result.id),
 | 
				
			||||||
			fields.vaccess_hash);
 | 
								data.vaccess_hash);
 | 
				
			||||||
	} break;
 | 
						});
 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: Unexpected("Type in ParseChat.");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -324,24 +277,15 @@ std::map<PeerId, Peer> ParsePeersLists(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Message ParseMessage(const MTPMessage &data) {
 | 
					Message ParseMessage(const MTPMessage &data) {
 | 
				
			||||||
	auto result = Message();
 | 
						auto result = Message();
 | 
				
			||||||
	switch (data.type()) {
 | 
						data.visit([&](const MTPDmessage &data) {
 | 
				
			||||||
	case mtpc_message: {
 | 
							result.id = data.vid.v;
 | 
				
			||||||
		const auto &fields = data.c_message();
 | 
							result.date = data.vdate.v;
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
						}, [&](const MTPDmessageService &data) {
 | 
				
			||||||
		result.date = fields.vdate.v;
 | 
							result.id = data.vid.v;
 | 
				
			||||||
	} break;
 | 
							result.date = data.vdate.v;
 | 
				
			||||||
 | 
						}, [&](const MTPDmessageEmpty &data) {
 | 
				
			||||||
	case mtpc_messageService: {
 | 
							result.id = data.vid.v;
 | 
				
			||||||
		const auto &fields = data.c_messageService();
 | 
						});
 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
		result.date = fields.vdate.v;
 | 
					 | 
				
			||||||
	} break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	case mtpc_messageEmpty: {
 | 
					 | 
				
			||||||
		const auto &fields = data.c_messageEmpty();
 | 
					 | 
				
			||||||
		result.id = fields.vid.v;
 | 
					 | 
				
			||||||
	} break;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -468,17 +412,7 @@ void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) {
 | 
				
			||||||
			to.list.push_back(std::move(info));
 | 
								to.list.push_back(std::move(info));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	switch (data.type()) {
 | 
						data.visit(process);
 | 
				
			||||||
	case mtpc_messages_dialogs:
 | 
					 | 
				
			||||||
		process(data.c_messages_dialogs());
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	case mtpc_messages_dialogsSlice:
 | 
					 | 
				
			||||||
		process(data.c_messages_dialogsSlice());
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: Unexpected("Type in AppendParsedChats.");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
 | 
					Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -138,19 +138,11 @@ void ApiWrap::requestUserpics(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_userpicsProcess->start([&] {
 | 
							_userpicsProcess->start([&] {
 | 
				
			||||||
			auto info = Data::UserpicsInfo();
 | 
								auto info = Data::UserpicsInfo();
 | 
				
			||||||
			switch (result.type()) {
 | 
								result.visit([&](const MTPDphotos_photos &data) {
 | 
				
			||||||
			case mtpc_photos_photos: {
 | 
					 | 
				
			||||||
				const auto &data = result.c_photos_photos();
 | 
					 | 
				
			||||||
				info.count = data.vphotos.v.size();
 | 
									info.count = data.vphotos.v.size();
 | 
				
			||||||
			} break;
 | 
								}, [&](const MTPDphotos_photosSlice &data) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			case mtpc_photos_photosSlice: {
 | 
					 | 
				
			||||||
				const auto &data = result.c_photos_photosSlice();
 | 
					 | 
				
			||||||
				info.count = data.vcount.v;
 | 
									info.count = data.vcount.v;
 | 
				
			||||||
			} break;
 | 
								});
 | 
				
			||||||
 | 
					 | 
				
			||||||
			default: Unexpected("Photos type in Controller::exportUserpics.");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return info;
 | 
								return info;
 | 
				
			||||||
		}());
 | 
							}());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -161,20 +153,15 @@ void ApiWrap::requestUserpics(
 | 
				
			||||||
void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) {
 | 
					void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) {
 | 
				
			||||||
	Expects(_userpicsProcess != nullptr);
 | 
						Expects(_userpicsProcess != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (result.type()) {
 | 
						if (result.type() == mtpc_photos_photos) {
 | 
				
			||||||
	case mtpc_photos_photos: {
 | 
					 | 
				
			||||||
		const auto &data = result.c_photos_photos();
 | 
					 | 
				
			||||||
		_userpicsProcess->lastSlice = true;
 | 
							_userpicsProcess->lastSlice = true;
 | 
				
			||||||
		loadUserpicsFiles(Data::ParseUserpicsSlice(data.vphotos));
 | 
					 | 
				
			||||||
	} break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	case mtpc_photos_photosSlice: {
 | 
					 | 
				
			||||||
		const auto &data = result.c_photos_photosSlice();
 | 
					 | 
				
			||||||
		loadUserpicsFiles(Data::ParseUserpicsSlice(data.vphotos));
 | 
					 | 
				
			||||||
	} break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: Unexpected("Photos type in Controller::exportUserpicsSlice.");
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						result.visit([&](const auto &data) {
 | 
				
			||||||
 | 
							if constexpr (MTPDphotos_photos::Is<decltype(data)>()) {
 | 
				
			||||||
 | 
								_userpicsProcess->lastSlice = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							loadUserpicsFiles(Data::ParseUserpicsSlice(data.vphotos));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ApiWrap::loadUserpicsFiles(Data::UserpicsSlice &&slice) {
 | 
					void ApiWrap::loadUserpicsFiles(Data::UserpicsSlice &&slice) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue