/* * (c) Copyright Ascensio System SIA 2010-2023 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ #include #include #include #include #include "help.h" #include "../common/File.h" #include "../common/Directory.h" #include "../../DesktopEditor/common/StringBuilder.h" #include "../../DesktopEditor/common/SystemUtils.h" #include "../../DesktopEditor/graphics/BaseThread.h" #include "../../OfficeUtils/src/OfficeUtils.h" #include "../../Common/Network/FileTransporter/include/FileTransporter.h" #ifdef CreateDirectory #undef CreateDirectory #endif #ifdef GetTempPath #undef GetTempPath #endif #ifdef LINUX #include #include #endif // Misc std::wstring CorrectDir(const std::wstring& sDir) { if (sDir.empty()) return L""; const wchar_t* data = sDir.c_str(); std::wstring::size_type pos1 = (data[0] == '\"') ? 1 : 0; std::wstring::size_type pos2 = sDir.length(); if (data[pos2 - 1] == '\"') --pos2; if (pos2 > 0 && ((data[pos2 - 1] == '\\') || (data[pos2 - 1] == '/'))) --pos2; return sDir.substr(pos1, pos2 - pos1); } std::wstring CorrectValue(const std::wstring& value) { if (value.empty()) return L""; const wchar_t* data = value.c_str(); std::wstring::size_type pos1 = (data[0] == '\"') ? 1 : 0; std::wstring::size_type pos2 = value.length(); if (data[pos2 - 1] == '\"') --pos2; return value.substr(pos1, pos2 - pos1); } bool SplitStringAsVector(const std::wstring& sData, const std::wstring& sDelimiter, std::vector& arrOutput) { arrOutput.clear(); if ( sData.length() ) { std::wstring sTmp = sData; NSStringUtils::string_replace(sTmp, L", ", L","); size_t pos_start = 0, pos_end, delim_len = sDelimiter.length(); std::wstring token = L""; while ((pos_end = sTmp.find(sDelimiter, pos_start)) != std::string::npos) { token = sTmp.substr(pos_start, pos_end - pos_start); pos_start = pos_end + delim_len; if (token.length()) arrOutput.push_back(token); } token = sTmp.substr(pos_start); if ( token.length() ) arrOutput.push_back(token); } return arrOutput.size() > 0; } // Manager enum PluginStatus { Removed = 0, Installed, Backup }; class CVersion { private: int m_major, m_minor, m_revision, m_build; public: std::wstring m_sVersion; CVersion() : m_major(1), m_minor(0), m_revision(0), m_build(0), m_sVersion(L"1.0.0") { } CVersion(std::wstring& sVersion) : m_major(1), m_minor(0), m_revision(0), m_build(0), m_sVersion(L"1.0.0") { if ( sVersion.length() ) { m_sVersion = sVersion; std::vector arr; SplitStringAsVector(m_sVersion, L".", arr); for (size_t i = 0; i < arr.size(); i++) { int iValue = std::stoi(arr[i]); switch (i) { case 0: m_major = iValue; break; case 1: m_minor = iValue; break; case 2: m_revision = iValue; break; case 3: m_build = iValue; break; default: break; } } if (m_major < 0) m_major = 0; if (m_minor < 0) m_minor = 0; if (m_revision < 0) m_revision = 0; if (m_build < 0) m_build = 0; } } bool operator > (CVersion& oVersion) { if (m_major > oVersion.m_major) return true; else if (m_major < oVersion.m_major) return false; if (m_minor > oVersion.m_minor) return true; else if (m_minor < oVersion.m_minor) return false; if (m_revision > oVersion.m_revision) return true; else if (m_revision < oVersion.m_revision) return false; if (m_build > oVersion.m_build) return true; else if (m_build < oVersion.m_build) return false; return false; } bool operator == (CVersion& oVersion) { return m_major == oVersion.m_major && m_minor == oVersion.m_minor && m_revision == oVersion.m_revision && m_build == oVersion.m_build && m_sVersion == oVersion.m_sVersion; } }; class CPluginInfo { public: std::wstring m_sName; std::wstring m_sNameConfig; std::wstring m_sGuid; std::wstring m_sDir; CVersion* m_pVersion; bool m_isValid; bool m_isDirGuid; CPluginInfo() { m_sName = L""; m_sNameConfig = L""; m_sGuid = L""; m_sDir = L""; m_pVersion = new CVersion(); m_isValid = true; m_isDirGuid = true; } CPluginInfo(std::wstring& sName, std::wstring& sNameConfig, std::wstring& sGuid, CVersion* pVersion) { m_sName = sName; m_sNameConfig = sNameConfig; m_sGuid = sGuid; m_pVersion = pVersion; m_isValid = true; } bool operator == (const CPluginInfo& oPlugin) { return m_sName == oPlugin.m_sName && m_sNameConfig == oPlugin.m_sNameConfig && m_sGuid == oPlugin.m_sGuid && m_pVersion == oPlugin.m_pVersion; } }; class CPluginsManager { private: std::wstring m_sManagerGuid = L"{AA2EA9B6-9EC2-415F-9762-634EE8D9A95E}"; std::wstring m_sOldManagerGuid = L"{8D67F3C5-7736-4BAE-A0F2-8C7127DC4BB8}"; std::wstring m_sPluginsDir; std::wstring m_sMarketplaceUrl; std::wstring m_sMarketplaceRepo; std::wstring m_sSettingsDir; std::wstring m_sSettingsFile; std::vector m_arrInstall, m_arrRestore, m_arrUpdate, m_arrRemove, m_arrIgnore; std::vector m_arrInstalled, m_arrRemoved, m_arrMarketplace, m_arrBackup; public: CPluginsManager() { m_sPluginsDir = L""; m_sMarketplaceUrl = L"https://onlyoffice.github.io"; m_sMarketplaceRepo = L"https://github.com/ONLYOFFICE/onlyoffice.github.io"; m_sSettingsDir = NSSystemUtils::GetAppDataDir() + L"/pluginsmanager"; #ifdef LINUX // GetAppDataDir creates folder with ONLYOFFICE on Linux // as result - two folders in lower/upper case, working with the correct folder NSStringUtils::string_replace(m_sSettingsDir, L"ONLYOFFICE", L"onlyoffice"); #endif m_sSettingsFile = m_sSettingsDir + L"/settings"; } // Usability void PrintHelp() { Message(sHelpText); } // Set bool SetDirectory(const std::wstring& sDir) { bool bResult = false; std::wstring sInfo = L""; if ( sDir.length() ) { m_sPluginsDir = sDir; // Check access if ( NSDirectory::Exists(m_sPluginsDir) ) { std::wstring sTestDir = m_sPluginsDir + L"/pm-check-access-denied"; if ( NSDirectory::CreateDirectory(sTestDir) ) { NSDirectory::DeleteDirectory(sTestDir); bResult = true; } else sInfo = L"Folder: " + m_sPluginsDir + L"\n" + L"access is denied. Check permissions. Exit"; } else sInfo = L"Folder: " + m_sPluginsDir + L"\n" + L"does not exist. Exit"; } if ( sInfo.length() ) Message(sInfo, L"", true); return bResult; } bool SetMarketplace(const std::wstring& sUrl) { bool bResult = false; if ( sUrl.length() ) { if (0 == sUrl.find(L"https://onlyoffice.github.io")) { m_sMarketplaceUrl = L"https://onlyoffice.github.io"; m_sMarketplaceRepo = L"https://github.com/ONLYOFFICE/onlyoffice.github.io"; } else if (0 == sUrl.find(L"https://onlyoffice-plugins.github.io/onlyoffice.github.io")) { m_sMarketplaceUrl = L"https://onlyoffice-plugins.github.io/onlyoffice.github.io"; m_sMarketplaceRepo = L"https://github.com/ONLYOFFICE-PLUGINS/onlyoffice.github.io"; } else { m_sMarketplaceUrl = sUrl; m_sMarketplaceRepo = L""; std::wstring::size_type posDelimeter = m_sMarketplaceUrl.find(';'); if (posDelimeter != std::wstring::npos) { m_sMarketplaceUrl = sUrl.substr(0, posDelimeter); m_sMarketplaceRepo = sUrl.substr(posDelimeter + 1); } } bResult = true; } return bResult; } bool SetInstallPlugins(const std::wstring& sPluginsList) { return SplitStringAsVector(sPluginsList, L",", m_arrInstall); } bool SetRestorePlugins(const std::wstring& sPluginsList) { return SplitStringAsVector(sPluginsList, L",", m_arrRestore); } bool SetUpdatePlugins(const std::wstring& sPluginsList) { return SplitStringAsVector(sPluginsList, L",", m_arrUpdate); } bool SetRemovePlugins(const std::wstring& sPluginsList) { return SplitStringAsVector(sPluginsList, L",", m_arrRemove); } bool SetIgnorePlugins(const std::wstring& sPluginsList) { return SplitStringAsVector(sPluginsList, L",", m_arrIgnore); } // Settings void ResetSettings() { Message(L"Reset settings", BoolToStr(true), false); if (NSFile::CFileBinary::Exists(m_sSettingsFile)) NSFile::CFileBinary::Remove(m_sSettingsFile); m_arrRemoved.clear(); } bool ReadSettings() { // Format // name|version|guid, ... m_arrRemoved.clear(); if (NSFile::CFileBinary::Exists(m_sSettingsFile)) { std::wstring sData = L""; if (NSFile::CFileBinary::ReadAllTextUtf8(m_sSettingsFile, sData)) { std::wstring::size_type pos1 = sData.find(sSetRemoved); std::wstring::size_type pos2 = sData.find(L"\n", pos1); if (pos1 != std::wstring::npos && pos2 != std::wstring::npos && pos2 > pos1) { std::wstring sRemovedGuids = sData.substr(pos1 + sSetRemoved.length(), pos2 - pos1 - sSetRemoved.length()); NSStringUtils::string_replace(sRemovedGuids, L"\r", L""); sRemovedGuids = CorrectValue(sRemovedGuids); std::vector arrTmp; if ( SplitStringAsVector(sRemovedGuids, L",", arrTmp) ) { for (size_t i = 0; i < arrTmp.size(); i++) { std::vector arrItems; if ( SplitStringAsVector(arrTmp[i], L"|", arrItems) && (arrItems.size() == 3) ) { CPluginInfo* pPluginInfo = new CPluginInfo(arrItems[0], arrItems[0], arrItems[2], new CVersion(arrItems[1])); m_arrRemoved.push_back(pPluginInfo); } } } } } } return m_arrRemoved.size() > 0; } bool SaveSettings() { bool bResult = false; if ( m_arrRemoved.size() ) { std::wstring sData = sSetRemoved; std::wstring sDelim = L"|"; for (size_t i = 0; i < m_arrRemoved.size(); i++) { sData += m_arrRemoved[i]->m_sName + sDelim + m_arrRemoved[i]->m_pVersion->m_sVersion + sDelim + m_arrRemoved[i]->m_sGuid + ((i < m_arrRemoved.size() - 1) ? L"," : L""); } sData += L"\n"; if ( !NSDirectory::Exists(m_sSettingsDir) ) NSDirectory::CreateDirectories(m_sSettingsDir); if ( NSFile::CFileBinary::Exists(m_sSettingsFile) ) NSFile::CFileBinary::Remove(m_sSettingsFile); NSFile::CFileBinary oFile; bResult = oFile.CreateFileW(m_sSettingsFile); oFile.WriteStringUTF8(sData); oFile.CloseFile(); } return bResult; } // Multi bool InstallPlugins() { bool bResult = true; Message(L"Install plugins ...", L"", true, true); InitPlugins(); if (m_sPluginsDir.length() && m_arrInstall.size() && m_arrMarketplace.size()) { for (size_t i = 0; i < m_arrInstall.size(); i++) { std::wstring sPlugin = m_arrInstall[i]; bResult &= InstallPlugin(sPlugin); } } GetInstalledPlugins(); return bResult; } bool RestorePlugins() { bool bResult = true; Message(L"Restore plugins ...", L"", true, true); InitPlugins(true); if (m_sPluginsDir.length() && m_arrRestore.size()) { for (size_t i = 0; i < m_arrRestore.size(); i++) { std::wstring sPlugin = m_arrRestore[i]; bResult &= RestorePlugin(sPlugin); } } if ( bResult ) GetInstalledPlugins(); return bResult; } bool UpdatePlugins() { bool bResult = true; Message(L"Update plugins ...", L"", true, true); InitPlugins(); if (m_sPluginsDir.length() && m_arrMarketplace.size()) { for (size_t i = 0; i < m_arrUpdate.size(); i++) { std::wstring sPlugin = m_arrUpdate[i]; bResult &= UpdatePlugin(sPlugin); } } GetInstalledPlugins(); return bResult; } bool UpdateAllPlugins() { bool bResult = true; Message(L"Update all plugins ...", L"", true, true); InitPlugins(); if (m_sPluginsDir.length() && m_arrInstalled.size() && m_arrMarketplace.size()) { std::vector::iterator it; for (it = m_arrInstalled.begin(); it != m_arrInstalled.end(); it++) { bResult &= UpdatePlugin((*it)->m_sName); } } GetInstalledPlugins(); return bResult; } bool RemovePlugins(bool bAll = false) { bool bResult = true; Message(bAll ? L"Remove all installed plugins ..." : L"Remove plugins ...", L"", true, true); InitPlugins(); if ( bAll ) { m_arrRemove.clear(); for (size_t i = 0; i < m_arrInstalled.size(); i++) { m_arrRemove.push_back(m_arrInstalled[i]->m_sName); } } if (m_sPluginsDir.length() && m_arrRemove.size() && m_arrMarketplace.size()) { for (size_t i = 0; i < m_arrRemove.size(); i++) { std::wstring sPlugin = m_arrRemove[i]; bResult &= RemovePlugin(sPlugin); } } GetInstalledPlugins(); return bResult; } void RenamePlugins() { bool bResult = false; Message(L"Rename plugins ...", L"", true, true); InitPlugins(); for (size_t i = 0; i < m_arrInstalled.size(); i++) { CPluginInfo* pPluginInfo = m_arrInstalled[i]; if ( !pPluginInfo->m_isDirGuid ) { std::wstring sPluginDir = m_sPluginsDir + L"/" + pPluginInfo->m_sGuid; if ( NSDirectory::Exists(sPluginDir) ) NSDirectory::DeleteDirectory(sPluginDir); bResult = NSDirectory::CreateDirectory(sPluginDir); bResult &= NSDirectory::CopyDirectory(pPluginInfo->m_sDir, sPluginDir); if ( bResult ) NSDirectory::DeleteDirectory(pPluginInfo->m_sDir); std::wstring sPrintInfo = L"Rename plugin: " + pPluginInfo->m_sName + L" -> " + pPluginInfo->m_sGuid; Message(sPrintInfo, BoolToStr(bResult), true); } } if ( bResult ) GetInstalledPlugins(); } // Local and Marketplace void GetInstalledPlugins(bool bPrint = true) { // Version control GetMarketPlugins(false); GetLocalPlugins(false, bPrint); } bool GetBackupPlugins(bool bPrint = true) { return GetLocalPlugins(true, bPrint); } bool GetMarketPlugins(bool bPrint = true) { bool bResult = false; if ( bPrint ) Message(L"Initialize marketplace plugins ...", L"", true, true); if ( !m_arrMarketplace.size() && m_sMarketplaceUrl.length() ) { m_arrMarketplace.clear(); std::wstring sConfigUrl = m_sMarketplaceUrl + L"/store/config.json"; std::wstring sTmpFile = NSFile::CFileBinary::GetTempPath() + L"/temp_asc_plugins_config.json"; if (DownloadFile(sConfigUrl, sTmpFile)) { std::vector arr; if ( ReadStoreConfig(sTmpFile, arr) ) { for (size_t i = 0; i < arr.size(); i++) { std::wstring sPluginName = arr[i]; std::transform(sPluginName.begin(), sPluginName.end(), sPluginName.begin(), tolower); CPluginInfo* pPluginInfo = FindMarketPlugin(sPluginName); if ( !pPluginInfo ) { pPluginInfo = FetchPluginInfo(sPluginName); if ( pPluginInfo ) { m_arrMarketplace.push_back(pPluginInfo); if ( bPrint ) MessagePluginInfo(pPluginInfo->m_sNameConfig, pPluginInfo->m_pVersion->m_sVersion, pPluginInfo->m_sGuid); } } } } NSFile::CFileBinary::Remove(sTmpFile); } } return bResult; } void PrintRemovedPlugins() { Message(L"Removed plugins:", L"", true, true); for (size_t i = 0; i < m_arrRemoved.size(); i++) { MessagePluginInfo(m_arrRemoved[i]->m_sName, m_arrRemoved[i]->m_pVersion->m_sVersion, m_arrRemoved[i]->m_sGuid); } } private: // Single CPluginInfo* FindLocalPlugin(const std::wstring& sPlugin, PluginStatus status = Installed) { CPluginInfo* pResult = NULL; if ( sPlugin.length() ) { std::vector arrPlugins; switch (status) { case Removed: arrPlugins = m_arrRemoved; break; case Backup: arrPlugins = m_arrBackup; break; default: arrPlugins = m_arrInstalled; break; } if ( arrPlugins.size() ) { // Find by name or GUID std::wstring sPluginName = sPlugin; std::transform(sPluginName.begin(), sPluginName.end(), sPluginName.begin(), tolower); std::vector::iterator it; for (it = arrPlugins.begin(); it != arrPlugins.end(); it++) { if (IsGuid(sPlugin)) { if ( (*it)->m_sGuid == sPlugin ) { pResult = *it; break; } } else if ( (*it)->m_sName == sPluginName || (*it)->m_sNameConfig == sPluginName ) { pResult = *it; break; } } } } return pResult; } CPluginInfo* FindMarketPlugin(const std::wstring& sPlugin) { CPluginInfo* pResult = NULL; if (m_arrMarketplace.size()) { std::wstring _sPlugin = sPlugin; if ( !IsGuid(_sPlugin) ) std::transform(_sPlugin.begin(), _sPlugin.end(), _sPlugin.begin(), tolower); std::vector::iterator it; for (it = m_arrMarketplace.begin(); it != m_arrMarketplace.end(); it++) { if ( (*it)->m_sName == _sPlugin || (*it)->m_sNameConfig == _sPlugin || (*it)->m_sGuid == _sPlugin ) { pResult = *it; break; } } } return pResult; } bool InstallPlugin(const std::wstring& sPlugin, bool bDirGuid = true, bool bPrint = true) { bool bResult = false; std::wstring sPrintInfo = L""; if (sPlugin.length()) { bool isNeedDownload = true; std::wstring sPackageUrl = L""; std::wstring sTmpFile = NSFile::CFileBinary::GetTempPath() + L"/temp_asc_plugin.plugin"; if (NSFile::CFileBinary::Exists(sTmpFile)) NSFile::CFileBinary::Remove(sTmpFile); std::wstring sTempDir = m_sPluginsDir + L"/temp_asc_plugin_directory"; std::wstring sTempDirExt = sTempDir; // Search by name or GUID CPluginInfo* pPlugin = FindMarketPlugin(sPlugin); if (pPlugin) { sPackageUrl = m_sMarketplaceUrl + L"/sdkjs-plugins/content/" + pPlugin->m_sName + L"/deploy/" + pPlugin->m_sName + L".plugin"; if (!m_sMarketplaceRepo.empty()) sPackageUrl = m_sMarketplaceRepo + L"/releases/latest/download/" + pPlugin->m_sName + L".plugin"; } else if (IsNeedDownload(sPlugin)) { // Try downlaod as is and unzip sPackageUrl = sPlugin; } else if (NSFile::CFileBinary::Exists(sPlugin)) { sTmpFile = sPlugin; isNeedDownload = false; } else { sPrintInfo = L"The plugin not found.\n" \ L"Check plugin name, GUID using --print-marketplace option or check file path"; } // if (isNeedDownload) { if ( sPackageUrl.length() && !DownloadFile(sPackageUrl, sTmpFile) ) sPrintInfo = L"Can't download: " + sPackageUrl; } if (NSFile::CFileBinary::Exists(sTmpFile)) { if (NSDirectory::Exists(sTempDir)) NSDirectory::DeleteDirectory(sTempDir); NSDirectory::CreateDirectory(sTempDir); // Archive COfficeUtils oOfficeUtils(NULL); if (S_OK == oOfficeUtils.IsArchive(sTmpFile) && S_OK == oOfficeUtils.ExtractToDirectory(sTmpFile, sTempDirExt, NULL, 0)) { std::wstring sConfigFile = sTempDirExt + L"/config.json"; // zip with subfolder if (!NSFile::CFileBinary::Exists(sConfigFile)) { std::vector arrDirs = NSDirectory::GetDirectories(sTempDirExt); if (arrDirs.size() == 1) { sTempDirExt = arrDirs[0]; sConfigFile = sTempDirExt + L"/config.json"; } } CPluginInfo* pPluginInfo = ReadPluginInfo(sConfigFile); if ( pPluginInfo ) { CPluginInfo* pInstalled = FindLocalPlugin(pPluginInfo->m_sGuid); if ( pInstalled ) { sPrintInfo = L"Already installed"; bResult = true; } else if ( pPluginInfo ) { std::wstring sPluginDir = m_sPluginsDir + L"/" + (bDirGuid ? pPluginInfo->m_sGuid : pPluginInfo->m_sName); // Check settings // Can install if user hasn't deleted the plugin before CPluginInfo* pRemoved = FindLocalPlugin(pPluginInfo->m_sGuid, Removed); if ( !pRemoved ) { if ( NSDirectory::Exists(sPluginDir) ) NSDirectory::DeleteDirectory(sPluginDir); NSDirectory::CreateDirectory(sPluginDir); NSDirectory::CopyDirectory(sTempDirExt, sPluginDir); bResult = true; } else { sPrintInfo = L"Installation cancelled. The plugin has been removed before.\n" \ L"Use --reset option to reset settings"; } } } } // Config else { std::vector arrPlugins; if ( ReadConfigJson(sTmpFile, arrPlugins) ) { // Recursion installation bool _bResult = true; for(size_t i = 0; i < arrPlugins.size(); i++) { _bResult &= InstallPlugin(arrPlugins[i], bDirGuid, bPrint); } bResult = _bResult; } } NSDirectory::DeleteDirectory(sTempDir); } if ( isNeedDownload ) NSFile::CFileBinary::Remove(sTmpFile); } if (bPrint) Message(L"Install plugin: " + sPlugin, sPrintInfo.length() ? sPrintInfo : BoolToStr(bResult), true); return bResult; } bool UpdatePlugin(const std::wstring& sPlugin) { bool bResult = true; std::wstring sVerToVer = L""; if ( sPlugin.length() ) { // Check config file if ( NSFile::CFileBinary::Exists(sPlugin) ) { std::vector arrPlugins; if ( ReadConfigJson(sPlugin, arrPlugins) ) { // Update if plugin exists // Install if plugin is not installed and not removed before bool _bResult = true; for(size_t i = 0; i < arrPlugins.size(); i++) { CPluginInfo* pPlugin = FindLocalPlugin(arrPlugins[i]); if ( pPlugin ) _bResult &= UpdatePlugin(arrPlugins[i]); else { _bResult &= InstallPlugin(arrPlugins[i]); // Update installed GetLocalPlugins(false, false); } } bResult = _bResult; } } else { CPluginInfo* pLocalPlugin = FindLocalPlugin(sPlugin); CPluginInfo* pMarketPlugin = FindMarketPlugin(sPlugin); if ( !pLocalPlugin ) { bResult = false; Message(L"The plugin not found: " + sPlugin, BoolToStr(bResult), true); } // Check new version if ( pLocalPlugin && pMarketPlugin ) { if ( *pMarketPlugin->m_pVersion > *pLocalPlugin->m_pVersion ) { sVerToVer = L"(" + pLocalPlugin->m_pVersion->m_sVersion + L" -> " + pMarketPlugin->m_pVersion->m_sVersion + L")"; bResult &= RemovePlugin(pLocalPlugin->m_sGuid, false, false); bResult &= InstallPlugin(pLocalPlugin->m_sGuid, pLocalPlugin->m_isDirGuid, false); Message(L"Update plugin: " + sPlugin + L" " + sVerToVer, BoolToStr(bResult), true); } else if ( *pMarketPlugin->m_pVersion == *pLocalPlugin->m_pVersion ) { Message(L"Update plugin: " + sPlugin + L". No updates available", BoolToStr(bResult), true); } } } } return bResult; } bool RestorePlugin(const std::wstring& sPlugin) { bool bResult = false; std::wstring sPrintInfo = L""; if (sPlugin.length()) { CPluginInfo* pPlugin = FindLocalPlugin(sPlugin, Backup); if ( pPlugin ) { std::wstring sPluginDir = m_sPluginsDir + L"/" + pPlugin->m_sGuid; std::wstring sPluginBackupDir = m_sPluginsDir + L"/backup/" + pPlugin->m_sGuid; if (NSDirectory::Exists(sPluginBackupDir)) { NSDirectory::CopyDirectory(sPluginBackupDir, sPluginDir); NSDirectory::DeleteDirectory(sPluginBackupDir); bResult = true; } } else { sPrintInfo = L"The plugin not found in the backup list, check using --print-backup\n" \ L"This option is available for plugins that are not in the marketplace.\n" \ L"Use --install command"; } } Message(L"Restore plugin: " + sPlugin, sPrintInfo.length() ? sPrintInfo : BoolToStr(bResult), true); return bResult; } bool RemovePlugin(const std::wstring& sPlugin, bool bSave = true, bool bPrint = true) { bool bResult = false; if (sPlugin.length()) { // Check config file if ( NSFile::CFileBinary::Exists(sPlugin) ) { std::vector arrPlugins; if ( ReadConfigJson(sPlugin, arrPlugins) ) { // Recursion removing bool _bResult = true; for(size_t i = 0; i < arrPlugins.size(); i++) { _bResult &= RemovePlugin(arrPlugins[i], bSave, bPrint); } bResult = _bResult; } } bool bBackup = false; CPluginInfo* pPlugin = FindLocalPlugin(sPlugin); // Try find in marketplace if name isn't short alias if ( !pPlugin ) pPlugin = FindMarketPlugin(sPlugin); else { // Need create backup for plugin if doesn't exist in the marketplace CPluginInfo* pCheck = FindMarketPlugin(sPlugin); if ( !pCheck ) bBackup = true; } if (pPlugin) { // Plugin folder can be without GUID std::wstring sPluginDir = m_sPluginsDir + L"/" + pPlugin->m_sGuid; if ( !NSDirectory::Exists(sPluginDir) ) sPluginDir = pPlugin->m_sDir; if (NSDirectory::Exists(sPluginDir)) { if (bBackup) { std::wstring sBackupDir = m_sPluginsDir + L"/backup"; std::wstring sPluginBackupDir = sBackupDir + L"/" + pPlugin->m_sGuid; if ( !NSDirectory::Exists(sBackupDir) ) NSDirectory::CreateDirectory(sBackupDir); if ( NSDirectory::Exists(sPluginBackupDir) ) NSDirectory::DeleteDirectory(sPluginBackupDir); NSDirectory::CopyDirectory(sPluginDir, sPluginBackupDir); if (bPrint) Message(L"Backup plugin: " + sPlugin, BoolToStr(bBackup), true); } NSDirectory::DeleteDirectory(sPluginDir); // Update installed GetLocalPlugins(false, false); // Check duplicate (bug #62807) CPluginInfo* pDuplicate = FindLocalPlugin(pPlugin->m_sGuid); if ( pDuplicate ) { bool bRes = RemovePlugin(pDuplicate->m_sName, false, false); if (bPrint) Message(L"Remove duplicate plugin: " + pDuplicate->m_sName, BoolToStr(bRes), true); } // Save to settings CPluginInfo* pRemoved = FindLocalPlugin(pPlugin->m_sGuid, Removed); if ( bSave && !pRemoved ) m_arrRemoved.push_back(pPlugin); bResult = true; } } } if (bPrint) { std::wstring sInfo = L"Remove plugin: " + sPlugin; Message(sInfo, BoolToStr(bResult), true); } return bResult; } // Methods void InitPlugins(bool bBackup = false) { if ( !m_arrBackup.size() ) GetBackupPlugins(false); // Backup plugins don't exist in the marketplace, without additional initialization if ( !bBackup ) { GetInstalledPlugins(false); GetMarketPlugins(false); } } bool GetLocalPlugins(bool bBackup = false, bool bPrint = true) { bool bResult = false; if ( bPrint ) Message(bBackup ? L"Backup plugins:" : L"Installed plugins:", L"", true, true); if (m_sPluginsDir.length()) { std::vector arrPlugins; std::vector arrDirs = NSDirectory::GetDirectories(m_sPluginsDir + (bBackup ? L"/backup" : L"")); for (size_t i = 0; i < arrDirs.size(); ++i) { std::wstring sFile = arrDirs[i] + L"/config.json"; if (NSFile::CFileBinary::Exists(sFile)) { CPluginInfo* pPluginInfo = ReadPluginInfo(sFile); if ( pPluginInfo && pPluginInfo->m_isValid && !IsPluginManager(pPluginInfo->m_sGuid) ) { // Save plugin folder for updating by name pPluginInfo->m_sDir = arrDirs[i]; pPluginInfo->m_isDirGuid = IsFolderGuid(arrDirs[i]); if (std::find(arrPlugins.begin(), arrPlugins.end(), pPluginInfo) == arrPlugins.end()) { // Sync short names with marketplace // for example, we can't find 'openai' in installed CPluginInfo* pMarketPlugin = FindMarketPlugin(pPluginInfo->m_sGuid); if ( pMarketPlugin && (pPluginInfo->m_sName != pMarketPlugin->m_sName) ) pPluginInfo->m_sName = pMarketPlugin->m_sName; // Check ignored bool bIgnored = false; if ( m_arrIgnore.size() ) { for (size_t i = 0; i < m_arrIgnore.size(); i++) { std::wstring sName = m_arrIgnore[i]; std::wstring sGuid = m_arrIgnore[i]; std::transform(sName.begin(), sName.end(), sName.begin(), tolower); if ( pPluginInfo->m_sName == sName || pPluginInfo->m_sNameConfig == sName || pPluginInfo->m_sGuid == sGuid) { bIgnored = true; } } } // Save if ( !bIgnored ) { arrPlugins.push_back(pPluginInfo); bResult = true; } if ( bPrint ) { // Check new version from marketplace std::wstring sVersion = pPluginInfo->m_pVersion->m_sVersion; if ( !bBackup && pMarketPlugin) { if ( *pMarketPlugin->m_pVersion > *pPluginInfo->m_pVersion ) sVersion += L" (new " + pMarketPlugin->m_pVersion->m_sVersion + L")"; } MessagePluginInfo(pPluginInfo->m_sNameConfig, sVersion, pPluginInfo->m_sGuid, bIgnored); } } } } } // Save to target array if ( bBackup ) m_arrBackup = arrPlugins; else m_arrInstalled = arrPlugins; } else { Message(L"Set plugin folder. Use the following parameter: " + sCmdPluginsDir, L"", true); } return bResult; } bool IsGuid(const std::wstring& sStr) { return (sStr.length() && sStr.at(0) == L'{') && (sStr.at(sStr.length() - 1) == L'}'); } bool IsFolderGuid(const std::wstring& sStr) { bool bResult = false; if ( sStr.length() ) { std::wstring sFolder = sStr; std::vector arrParts; NSStringUtils::string_replace(sFolder, L"\\", L"/"); if ( SplitStringAsVector(sFolder, L"/", arrParts) ) { std::wstring sDir = arrParts[arrParts.size() - 1]; bResult = IsGuid(sDir); } } return bResult; } bool IsNeedDownload(const std::wstring& FilePath) { std::wstring::size_type n1 = FilePath.find(L"www."); std::wstring::size_type n2 = FilePath.find(L"http://"); std::wstring::size_type n3 = FilePath.find(L"ftp://"); std::wstring::size_type n4 = FilePath.find(L"https://"); if (n1 != std::wstring::npos && n1 < 10) return true; if (n2 != std::wstring::npos && n2 < 10) return true; if (n3 != std::wstring::npos && n3 < 10) return true; if (n4 != std::wstring::npos && n4 < 10) return true; return false; } bool IsPluginManager(const std::wstring& sGuid) { return sGuid == m_sManagerGuid || sGuid == m_sOldManagerGuid; } bool ReadConfigJson(const std::wstring& sFile, std::vector& arrOutput) { // [ "...", "...", "..." ] std::wstring sJson = L""; try { if (NSFile::CFileBinary::Exists(sFile) && NSFile::CFileBinary::ReadAllTextUtf8(sFile, sJson)) { std::wstring sDelim = L"\""; std::wstring::size_type pos1 = sJson.find(sDelim); while ( pos1 != std::wstring::npos ) { std::wstring::size_type pos2 = sJson.find(sDelim, pos1 + 1); if (pos1 != std::wstring::npos && pos2 > pos1) arrOutput.push_back(sJson.substr(pos1 + 1, pos2 - pos1 - 1)); pos1 = sJson.find(sDelim, pos2 + 1); } } } catch(...) { Message(L"Can't read file: " + sFile, L"", true); } return arrOutput.size() > 0; } bool ReadStoreConfig(const std::wstring& sFile, std::vector& arrOutput) { // [ { "name": "", "discussion": "" }, ... ] std::wstring sJson = L""; if (NSFile::CFileBinary::Exists(sFile) && NSFile::CFileBinary::ReadAllTextUtf8(sFile, sJson)) { std::wstring sDelim = L"\"name\": \""; std::wstring::size_type pos1 = sJson.find(sDelim); while ( pos1 != std::wstring::npos ) { std::wstring::size_type pos2 = sJson.find(L"\"", pos1 + sDelim.length()); if (pos1 != std::wstring::npos && pos2 > pos1) arrOutput.push_back(sJson.substr(pos1 + sDelim.length(), pos2 - pos1 - sDelim.length())); pos1 = sJson.find(sDelim, pos2 + 1); } } return arrOutput.size() > 0; } void StringReplaceExt(std::wstring& sText, const std::wstring& sFrom, const std::wstring& sTo) { if ( sText.length() ) { std::wstring::size_type pos = sText.find(sFrom); while ( pos != std::wstring::npos ) { NSStringUtils::string_replace(sText, sFrom, sTo); pos = sText.find(sFrom); } } } bool DownloadFile(const std::wstring& sUrl, const std::wstring& sFile) { if (NSFile::CFileBinary::Exists(sFile)) NSFile::CFileBinary::Remove(sFile); NSNetwork::NSFileTransport::CFileDownloader oDownloader(sUrl, false); oDownloader.SetFilePath(sFile); oDownloader.Start(0); while (oDownloader.IsRunned()) { NSThreads::Sleep(10); } return NSFile::CFileBinary::Exists(sFile); } CPluginInfo* FetchPluginInfo(const std::wstring& sPluginName) { CPluginInfo* pResult = NULL; std::wstring sTmpFile = NSFile::CFileBinary::GetTempPath() + L"/temp_asc_plugin.json"; std::wstring sConfigUrl = m_sMarketplaceUrl + L"/sdkjs-plugins/content/" + sPluginName + L"/config.json"; if (NSFile::CFileBinary::Exists(sTmpFile)) NSFile::CFileBinary::Remove(sTmpFile); if (DownloadFile(sConfigUrl, sTmpFile)) { pResult = ReadPluginInfo(sTmpFile); // Names can be different, for example: ChatGPT and openai pResult->m_sName = sPluginName; NSFile::CFileBinary::Remove(sTmpFile); } return pResult; } CPluginInfo* ReadPluginInfo(const std::wstring& sConfigFile) { CPluginInfo* pResult = NULL; std::wstring sJson = L""; if (NSFile::CFileBinary::ReadAllTextUtf8(sConfigFile, sJson)) { pResult = new CPluginInfo(); // GUID std::wstring::size_type pos1 = sJson.find(L"asc.{"); std::wstring::size_type pos2 = sJson.find(L"}", pos1); if (pos1 != std::wstring::npos && pos2 != std::wstring::npos && pos2 > pos1) { pResult->m_sGuid = sJson.substr(pos1 + 4, pos2 - pos1 - 3); } // Name std::wstring sDelim = L"\"name\""; pos1 = sJson.find(sDelim); pos2 = sJson.find(L"\"", pos1 + sDelim.length()); std::wstring::size_type pos3 = sJson.find(L"\"", pos2 + 1); if (pos1 != std::wstring::npos && pos2 != std::wstring::npos && pos3 != std::wstring::npos && pos3 > pos2) { std::wstring sName = sJson.substr(pos2 + 1, pos3 - pos2 - 1); std::transform(sName.begin(), sName.end(), sName.begin(), tolower); pResult->m_sName = sName; pResult->m_sNameConfig = sName; } // Version sDelim = L"\"version\""; pos1 = sJson.find(sDelim); pos2 = sJson.find(L"\"", pos1 + sDelim.length()); pos3 = sJson.find(L"\"", pos2 + 1); if (pos1 != std::wstring::npos && pos2 != std::wstring::npos && pos3 != std::wstring::npos && pos3 > pos2) { std::wstring sVersion = sJson.substr(pos2 + 1, pos3 - pos2 - 1); pResult->m_pVersion = new CVersion(sVersion); } pResult->m_isValid = pResult->m_sGuid.length() && pResult->m_sName.length() && pResult->m_sNameConfig.length(); } return pResult; } std::wstring BoolToStr(bool bResult) { return bResult ? L"OK" : L"CANCELED"; } // Terminal void Message(const std::wstring& sText, std::wstring sResult = L"", bool bUseIndent = false, bool bSeparator = false) { if (bUseIndent) std::wcout << L"" << std::endl; std::wcout << sText.c_str() << std::endl; if ( sResult.length() ) std::wcout << sResult.c_str() << std::endl; if ( bSeparator ) std::wcout << L"------------------------------------------------------------------------------------" << std::endl; } void MessagePluginInfo(const std::wstring& sName, const std::wstring& sVersion, const std::wstring& sGuid, bool bIgnore = false) { std::wstring sName_ = sName; if ( bIgnore ) sName_ = sName_ + L"*"; std::wcout << std::left << std::setw(20) << sName_.c_str() << std::setw(26) << sVersion.c_str() << std::setw(40) << sGuid.c_str() << std::endl; } }; // Main #ifdef WIN32 int wmain(int argc, wchar_t** argv) #else int main(int argc, char** argv) #endif { setlocale(LC_ALL, ""); CPluginsManager oManager; oManager.ReadSettings(); // Parse arguments for (int i = 0; i < argc; ++i) { #ifdef WIN32 std::wstring sParam(argv[i]); #else std::string sParamA(argv[i]); std::wstring sParam = UTF8_TO_U(sParamA); #endif if (sParam.find(L"--") == 0) { std::wstring sKey = L""; std::wstring sValue = L""; // Parse key - value std::wstring::size_type pos = sParam.find('='); if ( pos == std::wstring::npos ) { sKey = sParam; if ( IsNeedSetValue(sKey)) { if (i < argc - 1) { i++; #ifdef WIN32 sValue = std::wstring(argv[i]); #else std::string sValueA(argv[i]); sValue = UTF8_TO_U(sValueA); #endif } // Checks if value or next key exist if ( !sValue.length() || (sValue.find(L"--") == 0) ) { std::wcout << L"\nError. Check input parameters\n"; return 1; } } } else { sKey = sParam.substr(0, pos); sValue = sParam.substr( pos + 1 ); } // Check key if ( !IsCommandExists(sKey) ) { std::wcout << L"\nError. Unknown parameter " << sKey << L"\n" << "Print usage information --help\n"; return 1; } // Usability if (sKey == sCmdHelp || sKey == sCmdHelpFull) { oManager.PrintHelp(); } // Settings else if (sKey == sCmdPluginsDir) { sValue = CorrectValue(sValue); if ( !oManager.SetDirectory(sValue) ) return 1; } else if (sKey == sCmdMarketplaceUrl) { sValue = CorrectValue(sValue); oManager.SetMarketplace(sValue); } else if (sKey == sCmdAutorename) { oManager.RenamePlugins(); } // Print else if (sKey == sCmdPrintInstalled) { oManager.GetInstalledPlugins(); } else if (sKey == sCmdPrintRemoved) { oManager.PrintRemovedPlugins(); } else if (sKey == sCmdPrintMarketplace) { oManager.GetMarketPlugins(); } else if (sKey == sCmdPrintBackup) { oManager.GetBackupPlugins(); } // Reset / Install / Update / Remove else if (sKey == sCmdReset) { oManager.ResetSettings(); } else if (sKey == sCmdInstall) { sValue = CorrectValue(sValue); if (sValue.length()) { oManager.SetInstallPlugins(sValue); oManager.InstallPlugins(); } } else if (sKey == sCmdRestore) { sValue = CorrectValue(sValue); if (sValue.length()) { oManager.SetRestorePlugins(sValue); oManager.RestorePlugins(); } } else if (sKey == sCmdUpdate) { sValue = CorrectValue(sValue); if (sValue.length()) { oManager.SetUpdatePlugins(sValue); oManager.UpdatePlugins(); } } else if (sKey == sCmdUpdateAll) { oManager.UpdateAllPlugins(); } else if (sKey == sCmdRemove) { sValue = CorrectValue(sValue); if (sValue.length()) { oManager.SetRemovePlugins(sValue); oManager.RemovePlugins(); } } else if (sKey == sCmdRemoveAll) { oManager.RemovePlugins(true); } else if (sKey == sCmdIgnore) { sValue = CorrectValue(sValue); if (sValue.length()) { oManager.SetIgnorePlugins(sValue); } } } } oManager.SaveSettings(); return 0; }