OBS源码学习(五)-将OBS自动更新功能迁移到公司自己的服务器上

11 篇文章 0 订阅

一、实现自己运行程序的manifest.json

static void GenerateJson(string path)
{
	//path = "D:\\OBSUpdate\\obs-studio\\build\\rundir\\Release";
	string runDirPath = path; 
	char *p = const_cast<char *>(path.c_str());
	fileSearch(p);
	//生成hash值
	std::map<string, string> fileHastMap;
	if (fileSizeMap.size() > 0) {

		for (auto it = fileSizeMap.begin(); it != fileSizeMap.end();
		     ++it) {
			uint8_t existingHash[BLAKE2_HASH_LENGTH];
			CalculateFileHash2(AnsiToUnicode(it->first.c_str()),existingHash);
			char hashString[BLAKE2_HASH_STR_LENGTH];
			HashToStringTwo(existingHash, hashString);
			fileHastMap[it->first] = string(hashString);
			
		}
	}

	//生成json文件
	json_t *obj = json_object();
	json_object_set(obj, "notes", json_string("test"));
	json_object_set(obj, "version_major", json_integer(0));
	json_object_set(obj, "version_minor", json_integer(0));
	json_object_set(obj, "version_patch", json_integer(1));

	json_t *objPackages = json_array();
	json_object_set(obj, "packages", objPackages);

	//core
	json_t *objCore = json_object();
	json_t *objFiles = json_array();
	if (fileSizeMap.size() > 0) {
		for (auto it = fileSizeMap.begin(); it != fileSizeMap.end();
		     ++it) {		
			string name = it->first;
			int size = it->second;
			string hash;
			if (fileHastMap.count(name) == 1) {
				hash = fileHastMap[name];
			}
			//
			int strSize = runDirPath.size();
			string subPath = name.substr(strSize);
			for (size_t i = 0; i < subPath.size(); i++) {
				if (subPath[i] == '\\') {
					subPath.replace(i, 1, string("\/"));
					i++;
				}
			}
			json_t *objFile = json_object();
			json_object_set_new(objFile, "name",
				json_string(subPath.substr(1).c_str()));
			json_object_set_new(objFile, "hash",
					    json_string(hash.c_str()));
			json_object_set_new(objFile, "size",
					    json_integer(size));
			json_array_append_new(objFiles, objFile);
		}
	}
	json_object_set_new(objCore, "files", objFiles);
	json_object_set_new(objCore, "name", json_string("core"));
	//
	json_array_append_new(objPackages, objCore);

	string newManifest;
	if (json_object_size(obj) > 0) {
		char *post_body = json_dumps(obj, JSON_COMPACT);
		int responseCode;
		int len = (int)strlen(post_body);
		uLong compressSize = compressBound(len);
		string compressedJson;

		compressedJson.resize(compressSize);
		compress2((Bytef *)&compressedJson[0], &compressSize,
			  (const Bytef *)post_body, len, Z_BEST_COMPRESSION);
		compressedJson.resize(compressSize);
		newManifest = string(post_body);
		free(post_body);
		if (obj)
			json_delete(obj);
	} else {
		newManifest = "[]";
	}

	//保存json文件
	if (!newManifest.empty()) {
		ofstream fout;
		fout.open("D:\\manifest.json", ios_base::out | ios_base::trunc);
		fout << newManifest << endl;
		fout.close();
	}
}
static std::map<string, int> fileSizeMap;
static void fileSearch(char *path)
{
	char *AddMapPath = path;
	char dirNew[200];
	strcpy(dirNew, path);
	strcat(dirNew, "\\*.*"); // 在目录后面加上"\\*.*"进行第一次搜索
	strcat(AddMapPath, "\\");

	intptr_t handle;
	_finddata_t findData;
	handle = _findfirst(dirNew, &findData);
	if (handle == -1) // 检查是否成功
	{
		return;
	}

	do {
		if (findData.attrib & _A_SUBDIR) {
			if (strcmp(findData.name, ".") == 0 ||
			    strcmp(findData.name, "..") == 0)
				continue;
			// 在目录后面加上"\\"和搜索到的目录名进行下一次搜索
			strcpy(dirNew, path);			
			//strcat(dirNew, "\\");
			strcat(dirNew, findData.name);			
			fileSearch(dirNew);	
		} else {
			fileSizeMap.insert(std::make_pair(AddMapPath + string(findData.name),int(findData.size)));
			//cout << "++++++++++++++++++++++++++++++\n"<< AddMapPath + string(findData.name);
			//cout << findData.name << "\t" << findData.size << " bytes.\n"<< fileSizeMap.size();
		}
	} while (_findnext(handle, &findData) == 0);

	_findclose(handle); // 关闭搜索句柄
}

以上代码是实现运行程序的json。
二、需要修改的地方

void AutoUpdateThread::run()
try {
	long responseCode;
	vector<string> extraHeaders;
	string text;
	string error;
	string signature;
	CryptProvider localProvider;
	BYTE manifestHash[BLAKE2_HASH_LENGTH];
	bool updatesAvailable = false;
	bool success;
	//GenerateJson("D:\\OBSUpdate\\obs-studio\\build\\rundir\\Release");
	struct FinishedTrigger {
		inline ~FinishedTrigger()
		{
			QMetaObject::invokeMethod(App()->GetMainWindow(),
						  "updateCheckFinished");
		}
	} finishedTrigger;

	BPtr<char> manifestPath = GetConfigPathPtr("obs-studio\\updates\\manifest.json");

	/* ----------------------------------- *
	 * create signature provider           */
	// 创建签名提供者
	if (!CryptAcquireContext(&localProvider, nullptr, MS_ENH_RSA_AES_PROV,
				 PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
		throw strprintf("CryptAcquireContext failed: %lu",
				GetLastError());

	provider = localProvider;

	/* ----------------------------------- *
	 * avoid downloading manifest again    */
	// 避免再次下载清单
	if (CalculateFileHash(manifestPath, manifestHash)) {
		char hashString[BLAKE2_HASH_STR_LENGTH];
		HashToString(manifestHash, hashString);
		qDebug() << "--------------------";
		string header = "If-None-Match: ";
		header += hashString;
		extraHeaders.push_back(move(header));
	}

	/* ----------------------------------- *
	 * get current install GUID            */

	string guid = GetProgramGUID();
	if (!guid.empty()) {
		string header = "X-OBS2-GUID: ";
		header += guid;
		extraHeaders.push_back(move(header));
		qDebug() << "+++++++++++++++++++";
	}

	/* ----------------------------------- *
	 * get manifest from server            */

	success = GetRemoteFile(WIN_MANIFEST_URL, text, error, &responseCode,
				nullptr, nullptr, extraHeaders, &signature);

	if (!success || (responseCode != 200 && responseCode != 304)) {
		if (responseCode == 404)
			return;

		throw strprintf("Failed to fetch manifest file: %s",
				error.c_str());
		
	}

	/* ----------------------------------- *
	 * verify file signature               */

	/* a new file must be digitally signed */
	//if (responseCode == 200) {
	//	qDebug() << "LLLLLLLLLLLLLLLLLLLLLLLLLL"<< signature.data()
	//		 << signature.size();
	//	success = CheckDataSignature(text, "manifest", signature.data(),
	//				     signature.size());
	//	
	//	if (!success)
	//		throw string("Invalid manifest signature");
	//}

	/* ----------------------------------- *
	 * write or load manifest              */

	if (responseCode == 200) {
		if (!QuickWriteFile(manifestPath, text.data(), text.size()))
			throw strprintf("Could not write file '%s'",
					manifestPath.Get());
	} else {
		if (!QuickReadFile(manifestPath, text))
			throw strprintf("Could not read file '%s'",
					manifestPath.Get());
	}

	/* ----------------------------------- *
	 * check manifest for update           */

	string notes;
	int updateVer = 0;

	success = ParseUpdateManifest(text.c_str(), &updatesAvailable, notes,
				      updateVer);

	if (!success)
		throw string("Failed to parse manifest");

	if (!updatesAvailable) {
		if (manualUpdate)
			info(QTStr("Updater.NoUpdatesAvailable.Title"),
			     QTStr("Updater.NoUpdatesAvailable.Text"));
		return;
	}

	/* ----------------------------------- *
	 * skip this version if set to skip    */

	int skipUpdateVer = config_get_int(GetGlobalConfig(), "General",
					   "SkipUpdateVersion");
	if (!manualUpdate && updateVer == skipUpdateVer)
		return;

	/* ----------------------------------- *
	 * fetch updater module                */
	// 获取更新程序模块
	/*if (!FetchUpdaterModule(WIN_UPDATER_URL))
		return;*/

	/* ----------------------------------- *
	 * query user for update               */

	int queryResult = queryUpdate(manualUpdate, notes.c_str());

	if (queryResult == OBSUpdate::No) {
		if (!manualUpdate) {
			long long t = (long long)time(nullptr);
			config_set_int(GetGlobalConfig(), "General",
				       "LastUpdateCheck", t);
		}
		return;

	} else if (queryResult == OBSUpdate::Skip) {
		config_set_int(GetGlobalConfig(), "General",
			       "SkipUpdateVersion", updateVer);
		return;
	}

	/* ----------------------------------- *
	 * get working dir                     */

	wchar_t cwd[MAX_PATH];
	GetModuleFileNameW(nullptr, cwd, _countof(cwd) - 1);
	wchar_t *p = wcsrchr(cwd, '\\');
	if (p)
		*p = 0;

	/* ----------------------------------- *
	 * execute updater                     */

	BPtr<char> updateFilePath =
		GetConfigPathPtr("obs-studio\\updates\\updater.exe");
	BPtr<wchar_t> wUpdateFilePath;

	size_t size = os_utf8_to_wcs_ptr(updateFilePath, 0, &wUpdateFilePath);
	if (!size)
		throw string("Could not convert updateFilePath to wide");

	/* note, can't use CreateProcess to launch as admin. */
	SHELLEXECUTEINFO execInfo = {};

	execInfo.cbSize = sizeof(execInfo);
	execInfo.lpFile = wUpdateFilePath;
#ifndef UPDATE_CHANNEL
#define UPDATE_ARG_SUFFIX L""
#else
#define UPDATE_ARG_SUFFIX UPDATE_CHANNEL
#endif
	if (App()->IsPortableMode())
		execInfo.lpParameters = UPDATE_ARG_SUFFIX L" Portable";
	else
		execInfo.lpParameters = UPDATE_ARG_SUFFIX;

	execInfo.lpDirectory = cwd;
	execInfo.nShow = SW_SHOWNORMAL;

	//执行 updater.exe 文件
	if (!ShellExecuteEx(&execInfo)) {
		QString msg = QTStr("Updater.FailedToLaunch");
		info(msg, msg);
		throw strprintf("Can't launch updater '%s': %d",
				updateFilePath.Get(), GetLastError());
	}

	/* force OBS to perform another update check immediately after updating
	 * in case of issues with the new version */
	config_set_int(GetGlobalConfig(), "General", "LastUpdateCheck", 0);
	config_set_int(GetGlobalConfig(), "General", "SkipUpdateVersion", 0);
	config_set_string(GetGlobalConfig(), "General", "InstallGUID",
			  guid.c_str());

	QMetaObject::invokeMethod(App()->GetMainWindow(), "close");

} catch (string &text) {
	blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
}

updater.cpp

bool DownloadWorkerThread()
{
	const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;

	HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.1",
					  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
					  WINHTTP_NO_PROXY_NAME,
					  WINHTTP_NO_PROXY_BYPASS, 0);
	if (!hSession) {
		downloadThreadFailure = true;
		Status(L"Update failed: Couldn't open obsproject.com");
		return false;
	}

	WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS,
			 (LPVOID)&tlsProtocols, sizeof(tlsProtocols));

	HttpHandle hConnect = WinHttpConnect(hSession,
					     L"seefm.oss-cn-beijing.aliyuncs.com",
					     INTERNET_DEFAULT_HTTPS_PORT, 0);
	if (!hConnect) {
		downloadThreadFailure = true;
		Status(L"Update failed: Couldn't connect to cdn-fastly.obsproject.com");
		return false;
	}

	for (;;) {
		bool foundWork = false;

		unique_lock<mutex> ulock(updateMutex);

		for (update_t &update : updates) {
			int responseCode;

			DWORD waitResult =
				WaitForSingleObject(cancelRequested, 0);
			if (waitResult == WAIT_OBJECT_0) {
				return false;
			}

			if (update.state != STATE_PENDING_DOWNLOAD)
				continue;

			update.state = STATE_DOWNLOADING;

			ulock.unlock();

			foundWork = true;

			if (downloadThreadFailure) {
				return false;
			}

			Status(L"Downloading %s", update.sourceURL.c_str());

			if (!HTTPGetFile(hConnect, update.sourceURL.c_str(),
					 update.tempPath.c_str(),
					 L"Accept-Encoding: gzip",
					 &responseCode)) {

				downloadThreadFailure = true;
				DeleteFile(update.tempPath.c_str());
				Status(L"Update failed: Could not download "
				       L"%s (error code %d)",
				       update.outputPath.c_str(), responseCode);
				Sleep(10000);
				return 1;
			}
			if (responseCode != 200) {
				downloadThreadFailure = true;
				DeleteFile(update.tempPath.c_str());
				Status(L"Update failed: Could not download "
				       L"%s (error code %d)",
				       update.outputPath.c_str(), responseCode);
				Sleep(10000);
				return 1;
			}

			BYTE downloadHash[BLAKE2_HASH_LENGTH];
			if (!CalculateFileHash(update.tempPath.c_str(),
					       downloadHash)) {
				downloadThreadFailure = true;
				DeleteFile(update.tempPath.c_str());
				Status(L"Update failed: Couldn't verify "
				       L"integrity of %s",
				       update.outputPath.c_str());
				Sleep(10000);
				return 1;
			}
			//比较下载文件的hash值与manifest中文件的hash值是否一致  这个作用就是这个
			//if (memcmp(update.downloadhash, downloadHash, 20)) {
			//	downloadThreadFailure = true;
			//	DeleteFile(update.tempPath.c_str());
			//	Status(L"Update failed: Integrity check "
			//	       L"failed on %s",
			//	       update.outputPath.c_str());
			//	Sleep(10000);
			//	return 1;
			//}

			ulock.lock();
			update.state = STATE_DOWNLOADED;
			completedUpdates++;
		}

		if (!foundWork) {
			break;
		}
		if (downloadThreadFailure) {
			return false;
		}
	}

	return true;
}

需要注意的是要将WIN_MANIFEST_URL和UPDATE_URL的地址换成自己公司的下载地址,一般都在云上存储,您必须维护自己的json和updater.exe,
每次更新完都要将新的json和新的程序上传到存储云上。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值