More flexible presubmit handling
This commit is contained in:
		
							parent
							
								
									967fe4d0fb
								
							
						
					
					
						commit
						bf10d88fb4
					
				@ -382,6 +382,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
		int failed = 0;
 | 
							int failed = 0;
 | 
				
			||||||
		char path[512];
 | 
							char path[512];
 | 
				
			||||||
		char metapath[512];
 | 
							char metapath[512];
 | 
				
			||||||
 | 
							char presubmitToken[512];
 | 
				
			||||||
		char response[512];
 | 
							char response[512];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		while (dumps->MoreFiles()) {
 | 
							while (dumps->MoreFiles()) {
 | 
				
			||||||
@ -405,12 +406,19 @@ class UploadThread: public IThread
 | 
				
			|||||||
				metapath[0] = '\0';
 | 
									metapath[0] = '\0';
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			bool wantsUpload = PresubmitCrashDump(path);
 | 
								presubmitToken[0] = '\0';
 | 
				
			||||||
 | 
								PresubmitResponse presubmitResponse = PresubmitCrashDump(path, presubmitToken, sizeof(presubmitToken));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (wantsUpload) {
 | 
								switch (presubmitResponse) {
 | 
				
			||||||
				bool uploaded = UploadCrashDump(path, metapath, response, sizeof(response));
 | 
									case kPRLocalError:
 | 
				
			||||||
 | 
										failed++;
 | 
				
			||||||
				if (uploaded) {
 | 
										g_pSM->LogError(myself, "Accelerator failed to locally process crash dump");
 | 
				
			||||||
 | 
										if (log) fprintf(log, "Failed to locally process crash dump");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case kPRRemoteError:
 | 
				
			||||||
 | 
									case kPRUploadCrashDumpAndMetadata:
 | 
				
			||||||
 | 
									case kPRUploadMetadataOnly:
 | 
				
			||||||
 | 
										if (UploadCrashDump((presubmitResponse == kPRUploadMetadataOnly) ? nullptr : path, metapath, presubmitToken, response, sizeof(response))) {
 | 
				
			||||||
						count++;
 | 
											count++;
 | 
				
			||||||
						g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response);
 | 
											g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response);
 | 
				
			||||||
						if (log) fprintf(log, "Uploaded crash dump: %s\n", response);
 | 
											if (log) fprintf(log, "Uploaded crash dump: %s\n", response);
 | 
				
			||||||
@ -419,18 +427,22 @@ class UploadThread: public IThread
 | 
				
			|||||||
						g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response);
 | 
											g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response);
 | 
				
			||||||
						if (log) fprintf(log, "Failed to upload crash dump: %s\n", response);
 | 
											if (log) fprintf(log, "Failed to upload crash dump: %s\n", response);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
			} else {
 | 
										break;
 | 
				
			||||||
 | 
									case kPRDontUpload:
 | 
				
			||||||
					skip++;
 | 
										skip++;
 | 
				
			||||||
					g_pSM->LogError(myself, "Accelerator crash dump upload skipped by server");
 | 
										g_pSM->LogError(myself, "Accelerator crash dump upload skipped by server");
 | 
				
			||||||
					if (log) fprintf(log, "Skipped due to server request\n");
 | 
										if (log) fprintf(log, "Skipped due to server request\n");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (libsys->PathExists(metapath)) {
 | 
								if (metapath[0]) {
 | 
				
			||||||
				unlink(metapath);
 | 
									unlink(metapath);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			unlink(path);
 | 
								unlink(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (log) fflush(log);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			dumps->NextEntry();
 | 
								dumps->NextEntry();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -449,7 +461,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined _LINUX
 | 
					#if defined _LINUX
 | 
				
			||||||
	bool UploadSymbolFile(const google_breakpad::CodeModule *module) {
 | 
						bool UploadSymbolFile(const google_breakpad::CodeModule *module, const char *presubmitToken) {
 | 
				
			||||||
		auto debugFile = module->debug_file();
 | 
							auto debugFile = module->debug_file();
 | 
				
			||||||
		if (debugFile[0] != '/') {
 | 
							if (debugFile[0] != '/') {
 | 
				
			||||||
			return false;
 | 
								return false;
 | 
				
			||||||
@ -487,6 +499,16 @@ class UploadThread: public IThread
 | 
				
			|||||||
		// printf(">>> %s\n", output.c_str());
 | 
							// printf(">>> %s\n", output.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		IWebForm *form = webternet->CreateForm();
 | 
							IWebForm *form = webternet->CreateForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
				
			||||||
 | 
							if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (presubmitToken && presubmitToken[0]) {
 | 
				
			||||||
 | 
								form->AddString("PresubmitToken", presubmitToken);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
		form->AddString("symbol_file", output.c_str());
 | 
							form->AddString("symbol_file", output.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MemoryDownloader data;
 | 
							MemoryDownloader data;
 | 
				
			||||||
@ -517,7 +539,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool UploadModuleFile(const google_breakpad::CodeModule *module) {
 | 
						bool UploadModuleFile(const google_breakpad::CodeModule *module, const char *presubmitToken) {
 | 
				
			||||||
		const auto &codeFile = module->code_file();
 | 
							const auto &codeFile = module->code_file();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef WIN32
 | 
					#ifndef WIN32
 | 
				
			||||||
@ -531,10 +553,19 @@ class UploadThread: public IThread
 | 
				
			|||||||
		if (log) fprintf(log, "Submitting binary for %s\n", codeFile.c_str());
 | 
							if (log) fprintf(log, "Submitting binary for %s\n", codeFile.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		IWebForm *form = webternet->CreateForm();
 | 
							IWebForm *form = webternet->CreateForm();
 | 
				
			||||||
		form->AddString("code_file_path", codeFile.c_str());
 | 
					
 | 
				
			||||||
		form->AddString("code_identifier", module->code_identifier().c_str());
 | 
							const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
				
			||||||
		form->AddString("debug_file_path", module->debug_file().c_str());
 | 
							if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (presubmitToken && presubmitToken[0]) {
 | 
				
			||||||
 | 
								form->AddString("PresubmitToken", presubmitToken);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddString("debug_identifier", module->debug_identifier().c_str());
 | 
							form->AddString("debug_identifier", module->debug_identifier().c_str());
 | 
				
			||||||
 | 
							form->AddString("code_identifier", module->code_identifier().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddFile("code_file", codeFile.c_str());
 | 
							form->AddFile("code_file", codeFile.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MemoryDownloader data;
 | 
							MemoryDownloader data;
 | 
				
			||||||
@ -616,7 +647,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
	std::map<std::string, ModuleType, PathComparator> modulePathMap;
 | 
						std::map<std::string, ModuleType, PathComparator> modulePathMap;
 | 
				
			||||||
	bool InitModuleClassificationMap(const std::string &base) {
 | 
						bool InitModuleClassificationMap(const std::string &base) {
 | 
				
			||||||
		if (!modulePathMap.empty()) {
 | 
							if (!modulePathMap.empty()) {
 | 
				
			||||||
			return false;
 | 
								modulePathMap.clear();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		modulePathMap[base] = kMTGame;
 | 
							modulePathMap[base] = kMTGame;
 | 
				
			||||||
@ -664,7 +695,15 @@ class UploadThread: public IThread
 | 
				
			|||||||
		return path.substr(0, file_start);
 | 
							return path.substr(0, file_start);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool PresubmitCrashDump(const char *path) {
 | 
						enum PresubmitResponse {
 | 
				
			||||||
 | 
							kPRLocalError,
 | 
				
			||||||
 | 
							kPRRemoteError,
 | 
				
			||||||
 | 
							kPRDontUpload,
 | 
				
			||||||
 | 
							kPRUploadCrashDumpAndMetadata,
 | 
				
			||||||
 | 
							kPRUploadMetadataOnly,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PresubmitResponse PresubmitCrashDump(const char *path, char *tokenBuffer, size_t tokenBufferLength) {
 | 
				
			||||||
		google_breakpad::ProcessState processState;
 | 
							google_breakpad::ProcessState processState;
 | 
				
			||||||
		google_breakpad::ProcessResult processResult;
 | 
							google_breakpad::ProcessResult processResult;
 | 
				
			||||||
		google_breakpad::MinidumpProcessor minidumpProcessor(nullptr, nullptr);
 | 
							google_breakpad::MinidumpProcessor minidumpProcessor(nullptr, nullptr);
 | 
				
			||||||
@ -675,7 +714,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (processResult != google_breakpad::PROCESS_OK) {
 | 
							if (processResult != google_breakpad::PROCESS_OK) {
 | 
				
			||||||
			return false;
 | 
								return kPRLocalError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int requestingThread = processState.requesting_thread();
 | 
							int requestingThread = processState.requesting_thread();
 | 
				
			||||||
@ -685,7 +724,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		const google_breakpad::CallStack *stack = processState.threads()->at(requestingThread);
 | 
							const google_breakpad::CallStack *stack = processState.threads()->at(requestingThread);
 | 
				
			||||||
		if (!stack) {
 | 
							if (!stack) {
 | 
				
			||||||
			return false;
 | 
								return kPRLocalError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int frameCount = stack->frames()->size();
 | 
							int frameCount = stack->frames()->size();
 | 
				
			||||||
@ -728,9 +767,8 @@ class UploadThread: public IThread
 | 
				
			|||||||
		IWebForm *form = webternet->CreateForm();
 | 
							IWebForm *form = webternet->CreateForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
							const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
				
			||||||
		if (minidumpAccount) form->AddString("UserID", minidumpAccount);
 | 
							if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddString("GameDirectory", g_pSM->GetGameFolderName());
 | 
					 | 
				
			||||||
		form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
							form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddString("CrashSignature", summaryLine.c_str());
 | 
							form->AddString("CrashSignature", summaryLine.c_str());
 | 
				
			||||||
@ -746,7 +784,7 @@ class UploadThread: public IThread
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if (!uploaded) {
 | 
							if (!uploaded) {
 | 
				
			||||||
			if (log) fprintf(log, "Presubmit failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode());
 | 
								if (log) fprintf(log, "Presubmit failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode());
 | 
				
			||||||
			return false;
 | 
								return kPRRemoteError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int responseSize = data.GetSize();
 | 
							int responseSize = data.GetSize();
 | 
				
			||||||
@ -761,28 +799,49 @@ class UploadThread: public IThread
 | 
				
			|||||||
		if (responseSize < 2) {
 | 
							if (responseSize < 2) {
 | 
				
			||||||
			if (log) fprintf(log, "Presubmit response too short\n");
 | 
								if (log) fprintf(log, "Presubmit response too short\n");
 | 
				
			||||||
			delete[] response;
 | 
								delete[] response;
 | 
				
			||||||
			return false;
 | 
								return kPRRemoteError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (response[0] == 'E') {
 | 
							if (response[0] == 'E') {
 | 
				
			||||||
			if (log) fprintf(log, "Presubmit error: %s\n", &response[2]);
 | 
								if (log) fprintf(log, "Presubmit error: %s\n", &response[2]);
 | 
				
			||||||
			delete[] response;
 | 
								delete[] response;
 | 
				
			||||||
			return false;
 | 
								return kPRRemoteError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool submitCrash = (response[0] == 'Y');
 | 
							PresubmitResponse presubmitResponse = kPRRemoteError;
 | 
				
			||||||
 | 
							if (response[0] == 'Y') presubmitResponse = kPRUploadCrashDumpAndMetadata;
 | 
				
			||||||
 | 
							else if (response[0] == 'N') presubmitResponse = kPRDontUpload;
 | 
				
			||||||
 | 
							else if (response[0] == 'M') presubmitResponse = kPRUploadMetadataOnly;
 | 
				
			||||||
 | 
							else return kPRRemoteError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (response[1] != '|') {
 | 
							if (response[1] != '|') {
 | 
				
			||||||
			if (log) fprintf(log, "Response delimiter missing\n");
 | 
								if (log) fprintf(log, "Response delimiter missing\n");
 | 
				
			||||||
			delete[] response;
 | 
								delete[] response;
 | 
				
			||||||
			return false;
 | 
								return kPRRemoteError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		unsigned int responseCount = responseSize - 2;
 | 
							unsigned int responseCount = responseSize - 2;
 | 
				
			||||||
		if (responseCount < moduleCount) {
 | 
							if (responseCount < moduleCount) {
 | 
				
			||||||
			if (log) fprintf(log, "Response module list doesn't match sent list (%d < %d)\n", responseCount, moduleCount);
 | 
								if (log) fprintf(log, "Response module list doesn't match sent list (%d < %d)\n", responseCount, moduleCount);
 | 
				
			||||||
			delete[] response;
 | 
								delete[] response;
 | 
				
			||||||
			return false;
 | 
								return presubmitResponse;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// There was a presubmit token included.
 | 
				
			||||||
 | 
							if (tokenBuffer && responseCount > moduleCount && response[2 + moduleCount] == '|') {
 | 
				
			||||||
 | 
								int tokenStart = 2 + moduleCount + 1;
 | 
				
			||||||
 | 
								int tokenEnd = tokenStart;
 | 
				
			||||||
 | 
								while (tokenEnd < responseSize && response[tokenEnd] != '|') {
 | 
				
			||||||
 | 
									tokenEnd++;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								size_t tokenLength = tokenEnd - tokenStart;
 | 
				
			||||||
 | 
								if (tokenLength < tokenBufferLength) {
 | 
				
			||||||
 | 
									strncpy(tokenBuffer, &response[tokenStart], tokenLength);
 | 
				
			||||||
 | 
									tokenBuffer[tokenLength] = '\0';
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (log) fprintf(log, "Got a presubmit token from server: %s\n", tokenBuffer);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto mainModule = processState.modules()->GetMainModule();
 | 
							auto mainModule = processState.modules()->GetMainModule();
 | 
				
			||||||
@ -800,8 +859,13 @@ class UploadThread: public IThread
 | 
				
			|||||||
		bool canBinarySubmit = !binarySubmitOption || (tolower(binarySubmitOption[0]) == 'y' || binarySubmitOption[0] == '1');
 | 
							bool canBinarySubmit = !binarySubmitOption || (tolower(binarySubmitOption[0]) == 'y' || binarySubmitOption[0] == '1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
 | 
							for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
 | 
				
			||||||
			bool submitSymbols = (response[2 + moduleIndex] == 'Y');
 | 
								bool submitSymbols = false;
 | 
				
			||||||
			bool submitBinary = (response[2 + moduleIndex] == 'U');
 | 
								bool submitBinary = (response[2 + moduleIndex] == 'U');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined _LINUX
 | 
				
			||||||
 | 
								submitSymbols = (response[2 + moduleIndex] == 'Y');
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (!submitSymbols && !submitBinary) {
 | 
								if (!submitSymbols && !submitBinary) {
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -833,32 +897,38 @@ class UploadThread: public IThread
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (canBinarySubmit && submitBinary) {
 | 
								if (canBinarySubmit && submitBinary) {
 | 
				
			||||||
				UploadModuleFile(module);
 | 
									UploadModuleFile(module, tokenBuffer);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined _LINUX
 | 
					#if defined _LINUX
 | 
				
			||||||
			if (submitSymbols) {
 | 
								if (submitSymbols) {
 | 
				
			||||||
				UploadSymbolFile(module);
 | 
									UploadSymbolFile(module, tokenBuffer);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		delete[] response;
 | 
							delete[] response;
 | 
				
			||||||
		return submitCrash;
 | 
							return presubmitResponse;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool UploadCrashDump(const char *path, const char *metapath, char *response, int maxlen) {
 | 
						bool UploadCrashDump(const char *path, const char *metapath, const char *presubmitToken, char *response, int maxlen) {
 | 
				
			||||||
		IWebForm *form = webternet->CreateForm();
 | 
							IWebForm *form = webternet->CreateForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
							const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
 | 
				
			||||||
		if (minidumpAccount) form->AddString("UserID", minidumpAccount);
 | 
							if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddString("GameDirectory", g_pSM->GetGameFolderName());
 | 
							form->AddString("GameDirectory", crashGameDirectory);
 | 
				
			||||||
		form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
							form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		form->AddFile("upload_file_minidump", path);
 | 
							if (presubmitToken && presubmitToken[0]) {
 | 
				
			||||||
 | 
								form->AddString("PresubmitToken", presubmitToken);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (metapath[0]) {
 | 
							if (path && path[0]) {
 | 
				
			||||||
 | 
								form->AddFile("upload_file_minidump", path);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (metapath && metapath[0]) {
 | 
				
			||||||
			form->AddFile("upload_file_metadata", metapath);
 | 
								form->AddFile("upload_file_metadata", metapath);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user