bool CAddonDatabase::GetAddon(const std::string& id, AddonPtr& addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; // there may be multiple addons with this id (eg from different repositories) in the database, // so we want to retrieve the latest version. Order by version won't work as the database // won't know that 1.10 > 1.2, so grab them all and order outside std::string sql = PrepareSQL("select id,version from addon where addonID='%s'",id.c_str()); m_pDS2->query(sql.c_str()); if (m_pDS2->eof()) return false; AddonVersion maxversion("0.0.0"); int maxid = 0; while (!m_pDS2->eof()) { AddonVersion version(m_pDS2->fv(1).get_asString()); if (version > maxversion) { maxid = m_pDS2->fv(0).get_asInt(); maxversion = version; } m_pDS2->next(); } return GetAddon(maxid,addon); } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str()); } addon.reset(); return false; }
bool CAddonDatabase::GetAddon(int id, AddonPtr &addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; std::string sql = "SELECT addon.*," " broken.reason," " addonextra.key, addonextra.value," " dependencies.addon, dependencies.version, dependencies.optional" " FROM addon" " LEFT JOIN broken" " ON broken.addonID = addon.addonID" " LEFT JOIN addonextra" " ON addonextra.id = addon.id" " LEFT JOIN dependencies" " ON dependencies.id = addon.id"; sql += PrepareSQL(" WHERE addon.id=%i", id); m_pDS2->query(sql.c_str()); if (!m_pDS2->eof()) { const dbiplus::query_data &data = m_pDS2->get_result_set().records; const dbiplus::sql_record* const record = data[0]; AddonProps props(record->at(addon_addonID).get_asString(), TranslateType(record->at(addon_type).get_asString()), record->at(addon_version).get_asString(), record->at(addon_minversion).get_asString()); props.name = record->at(addon_name).get_asString(); props.summary = record->at(addon_summary).get_asString(); props.description = record->at(addon_description).get_asString(); props.changelog = record->at(addon_changelog).get_asString(); props.path = record->at(addon_path).get_asString(); props.icon = record->at(addon_icon).get_asString(); props.fanart = record->at(addon_fanart).get_asString(); props.author = record->at(addon_author).get_asString(); props.disclaimer = record->at(addon_disclaimer).get_asString(); props.broken = record->at(broken_reason).get_asString(); /* while this is a cartesion join and we'll typically get multiple rows, we rely on the fact that extrainfo and dependencies are maps, so insert() will insert the first instance only */ for (dbiplus::query_data::const_iterator i = data.begin(); i != data.end(); ++i) { const dbiplus::sql_record* const record = *i; if (!record->at(addonextra_key).get_asString().empty()) props.extrainfo.insert(make_pair(record->at(addonextra_key).get_asString(), record->at(addonextra_value).get_asString())); if (!m_pDS2->fv(dependencies_addon).get_asString().empty()) props.dependencies.insert(make_pair(record->at(dependencies_addon).get_asString(), make_pair(AddonVersion(record->at(dependencies_version).get_asString()), record->at(dependencies_optional).get_asBool()))); } addon = CAddonMgr::AddonFromProps(props); return NULL != addon.get(); } } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %i", __FUNCTION__, id); } addon.reset(); return false; }
bool CAddonDatabase::GetAddon(int id, AddonPtr &addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; std::string sql = "SELECT addon.*," " broken.reason," " addonextra.key, addonextra.value," " dependencies.addon, dependencies.version, dependencies.optional" " FROM addon" " LEFT JOIN broken" " ON broken.addonID = addon.addonID" " LEFT JOIN addonextra" " ON addonextra.id = addon.id" " LEFT JOIN dependencies" " ON dependencies.id = addon.id"; sql += PrepareSQL(" WHERE addon.id=%i", id); m_pDS2->query(sql); if (!m_pDS2->eof()) { const dbiplus::query_data &data = m_pDS2->get_result_set().records; const dbiplus::sql_record* const record = data[0]; CAddonBuilder builder; builder.SetId(record->at(addon_addonID).get_asString()); builder.SetType(TranslateType(record->at(addon_type).get_asString())); builder.SetVersion(AddonVersion(record->at(addon_version).get_asString())); builder.SetMinVersion(AddonVersion(record->at(addon_minversion).get_asString())); builder.SetName(record->at(addon_name).get_asString()); builder.SetSummary(record->at(addon_summary).get_asString()); builder.SetDescription(record->at(addon_description).get_asString()); builder.SetChangelog(record->at(addon_changelog).get_asString()); builder.SetDisclaimer(record->at(addon_disclaimer).get_asString()); builder.SetAuthor(record->at(addon_author).get_asString()); builder.SetBroken(record->at(broken_reason).get_asString()); builder.SetPath(record->at(addon_path).get_asString()); builder.SetIcon(record->at(addon_icon).get_asString()); builder.SetFanart(record->at(addon_fanart).get_asString()); InfoMap extrainfo; ADDONDEPS dependencies; /* while this is a cartesion join and we'll typically get multiple rows, we rely on the fact that extrainfo and dependencies are maps, so insert() will insert the first instance only */ for (dbiplus::query_data::const_iterator i = data.begin(); i != data.end(); ++i) { const dbiplus::sql_record* const record = *i; if (!record->at(addonextra_key).get_asString().empty()) extrainfo.insert(std::make_pair(record->at(addonextra_key).get_asString(), record->at(addonextra_value).get_asString())); if (!m_pDS2->fv(dependencies_addon).get_asString().empty()) dependencies.insert(std::make_pair(record->at(dependencies_addon).get_asString(), std::make_pair(AddonVersion(record->at(dependencies_version).get_asString()), record->at(dependencies_optional).get_asBool()))); } builder.SetExtrainfo(std::move(extrainfo)); builder.SetDependencies(std::move(dependencies)); addon = builder.Build(); return NULL != addon.get(); } } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %i", __FUNCTION__, id); } addon.reset(); return false; }
bool CAddonInstallJob::DoWork() { AddonPtr repoPtr = GetRepoForAddon(m_addon); CStdString installFrom; if (!repoPtr || repoPtr->Props().libname.empty()) { // Addons are installed by downloading the .zip package on the server to the local // packages folder, then extracting from the local .zip package into the addons folder // Both these functions are achieved by "copying" using the vfs. CStdString dest="special://home/addons/packages/"; CStdString package = URIUtils::AddFileToFolder("special://home/addons/packages/", URIUtils::GetFileName(m_addon->Path())); if (URIUtils::HasSlashAtEnd(m_addon->Path())) { // passed in a folder - all we need do is copy it across installFrom = m_addon->Path(); } else { // zip passed in - download + extract CStdString path(m_addon->Path()); if (!m_referer.empty() && URIUtils::IsInternetStream(path)) { CURL url(path); url.SetProtocolOptions(m_referer); path = url.Get(); } if (!CFile::Exists(package) && !DownloadPackage(path, dest)) { CFile::Delete(package); return false; } // at this point we have the package - check that it is valid if (!CFile::Exists(package) || !CheckHash(package)) { CFile::Delete(package); return false; } // check the archive as well - should have just a single folder in the root CStdString archive; URIUtils::CreateArchivePath(archive,"zip",package,""); CFileItemList archivedFiles; CDirectory::GetDirectory(archive, archivedFiles); if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder) { // invalid package CFile::Delete(package); return false; } installFrom = archivedFiles[0]->GetPath(); } repoPtr.reset(); } // run any pre-install functions bool reloadAddon = OnPreInstall(); // perform install if (!Install(installFrom, repoPtr)) return false; // something went wrong // run any post-install guff OnPostInstall(reloadAddon); // and we're done! return true; }
bool CAddonInstallJob::DoWork() { SetTitle(StringUtils::Format(g_localizeStrings.Get(24057).c_str(), m_addon->Name().c_str())); SetProgress(0); // check whether all the dependencies are available or not SetText(g_localizeStrings.Get(24058)); std::pair<std::string, std::string> failedDep; if (!CAddonInstaller::GetInstance().CheckDependencies(m_addon, failedDep)) { std::string details = StringUtils::Format(g_localizeStrings.Get(24142).c_str(), failedDep.first.c_str(), failedDep.second.c_str()); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: %s", m_addon->ID().c_str(), details.c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), details); return false; } AddonPtr repoPtr = GetRepoForAddon(m_addon); std::string installFrom; if (!repoPtr || repoPtr->Props().libname.empty()) { // Addons are installed by downloading the .zip package on the server to the local // packages folder, then extracting from the local .zip package into the addons folder // Both these functions are achieved by "copying" using the vfs. std::string dest = "special://home/addons/packages/"; std::string package = URIUtils::AddFileToFolder("special://home/addons/packages/", URIUtils::GetFileName(m_addon->Path())); if (URIUtils::HasSlashAtEnd(m_addon->Path())) { // passed in a folder - all we need do is copy it across installFrom = m_addon->Path(); } else { CAddonDatabase db; if (!db.Open()) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to open database", m_addon->ID().c_str()); ReportInstallError(m_addon->ID(), m_addon->ID()); return false; } std::string md5; // check that we don't already have a valid copy if (!m_hash.empty() && CFile::Exists(package)) { if (db.GetPackageHash(m_addon->ID(), package, md5) && m_hash != md5) { db.RemovePackage(package); CFile::Delete(package); } } // zip passed in - download + extract if (!CFile::Exists(package)) { std::string path(m_addon->Path()); if (!m_referer.empty() && URIUtils::IsInternetStream(path)) { CURL url(path); url.SetProtocolOptions(m_referer); path = url.Get(); } if (!DownloadPackage(path, dest)) { CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to download %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } } // at this point we have the package - check that it is valid SetText(g_localizeStrings.Get(24077)); if (!m_hash.empty()) { md5 = CUtil::GetFileMD5(package); if (!StringUtils::EqualsNoCase(md5, m_hash)) { CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: MD5 mismatch after download %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } db.AddPackage(m_addon->ID(), package, md5); } // check the archive as well - should have just a single folder in the root CURL archive = URIUtils::CreateArchivePath("zip", CURL(package), ""); CFileItemList archivedFiles; CDirectory::GetDirectory(archive, archivedFiles); if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder) { // invalid package db.RemovePackage(package); CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: invalid package %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } installFrom = archivedFiles[0]->GetPath(); } repoPtr.reset(); } // run any pre-install functions ADDON::OnPreInstall(m_addon); // perform install if (!Install(installFrom, repoPtr)) return false; // run any post-install guff CEventLog::GetInstance().Add( EventPtr(new CAddonManagementEvent(m_addon, m_update ? 24065 : 24064)), !IsModal() && CSettings::GetInstance().GetBool(CSettings::SETTING_GENERAL_ADDONNOTIFICATIONS), false); ADDON::OnPostInstall(m_addon, m_update, IsModal()); //Clear addon from the disabled table CAddonDatabase database; database.Open(); database.DisableAddon(m_addon->ID(), false); // and we're done! MarkFinished(); return true; }
bool CAddonInstallJob::DoWork() { AddonPtr repoPtr = GetRepoForAddon(m_addon); std::string installFrom; if (!repoPtr || repoPtr->Props().libname.empty()) { // Addons are installed by downloading the .zip package on the server to the local // packages folder, then extracting from the local .zip package into the addons folder // Both these functions are achieved by "copying" using the vfs. std::string dest="special://home/addons/packages/"; std::string package = URIUtils::AddFileToFolder("special://home/addons/packages/", URIUtils::GetFileName(m_addon->Path())); if (URIUtils::HasSlashAtEnd(m_addon->Path())) { // passed in a folder - all we need do is copy it across installFrom = m_addon->Path(); } else { std::string md5; CAddonDatabase db; db.Open(); // check that we don't already have a valid copy if (!m_hash.empty() && CFile::Exists(package)) { if (db.GetPackageHash(m_addon->ID(), package, md5) && m_hash != md5) { db.RemovePackage(package); CFile::Delete(package); } } // zip passed in - download + extract if (!CFile::Exists(package)) { std::string path(m_addon->Path()); if (!m_referer.empty() && URIUtils::IsInternetStream(path)) { CURL url(path); url.SetProtocolOptions(m_referer); path = url.Get(); } if (!DownloadPackage(path, dest)) { CFile::Delete(package); return false; } } // at this point we have the package - check that it is valid if (!m_hash.empty()) { md5 = CUtil::GetFileMD5(package); if (!StringUtils::EqualsNoCase(md5, m_hash)) { CFile::Delete(package); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); CLog::Log(LOGERROR, "MD5 mismatch after download %s", package.c_str()); return false; } db.AddPackage(m_addon->ID(), package, md5); } // check the archive as well - should have just a single folder in the root CURL archive = URIUtils::CreateArchivePath("zip",CURL(package),""); CFileItemList archivedFiles; CDirectory::GetDirectory(archive, archivedFiles); if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder) { // invalid package db.RemovePackage(package); CFile::Delete(package); return false; } installFrom = archivedFiles[0]->GetPath(); } repoPtr.reset(); } // run any pre-install functions bool reloadAddon = OnPreInstall(); // perform install if (!Install(installFrom, repoPtr)) return false; // something went wrong // run any post-install guff OnPostInstall(reloadAddon); // and we're done! return true; }