/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ module; #include #include #include #include #include #include #include #include #include #include export module katja.utils; export namespace katja { enum class Info { // Error. unknown, // Installed in the same version. installed, // A different version is installed. updating, // Available, but not installed. installing, // Available. available }; struct JobData { sqlite3 *db; CURL *curl; virtual void package(Info info, const char *package_id, const char *summary) = 0; virtual void files(const std::vector&) = 0; virtual void details(char *package_id, const char *group, const char *description, const char *homepage, int uncompressed) = 0; virtual void set_percentage(double) = 0; }; struct DummyJobData final : JobData { void package(Info info, const char *package_id, const char *summary) override { } void files(const std::vector&) override { } void details(char *package_id, const char *group, const char *description, const char *homepage, int uncompressed) override { } void set_percentage(double) override { } }; /** * katja::get_file: * @curl: curl easy handle. * @source_url: source url. * @dest: destination. * * Download the file. * * Returns: CURLE_OK (zero) on success, non-zero otherwise. **/ CURLcode get_file(CURL **curl, const char *source_url, const std::optional& dest) { FILE *fout = nullptr; CURLcode ret; long response_code; std::string dest_dir_name; if ((*curl == nullptr) && (!(*curl = curl_easy_init()))) { return CURLE_BAD_FUNCTION_ARGUMENT; } curl_easy_setopt(*curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(*curl, CURLOPT_URL, source_url); if (!dest.has_value()) { curl_easy_setopt(*curl, CURLOPT_NOBODY, 1L); curl_easy_setopt(*curl, CURLOPT_HEADER, 1L); ret = curl_easy_perform(*curl); curl_easy_getinfo(*curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code != 200) { ret = CURLE_REMOTE_FILE_NOT_FOUND; } } else { dest_dir_name = dest.value().native(); if (std::filesystem::is_directory(dest.value())) { dest_dir_name += strrchr(source_url, '/'); } if ((fout = fopen(dest_dir_name.c_str(), "ab")) == nullptr) { return CURLE_WRITE_ERROR; } curl_easy_setopt(*curl, CURLOPT_WRITEDATA, fout); ret = curl_easy_perform(*curl); } curl_easy_reset(*curl); if (fout != nullptr) { fclose(fout); } return ret; } /** * katja::split_package_name: * Got the name of a package, without version-arch-release data. **/ std::optional> split_package_name(const std::string& pkg_filename) { std::string pkg_full_name; int len = pkg_filename.size(); if (len < 4) { return std::nullopt; } std::vector pkg_tokens(5); if (pkg_filename[len - 4] == '.') { /* Full name without extension */ pkg_tokens[3] = pkg_filename; pkg_full_name = std::string(std::crbegin(pkg_filename) + 4, std::crend(pkg_filename)); /* The last 3 characters should be the file extension */ pkg_tokens[4] = std::string(std::cbegin(pkg_filename) + len + 1, std::cend(pkg_filename)); } else { pkg_full_name = std::string(std::crbegin(pkg_filename), std::crend(pkg_filename)); } /* Reverse all of the bytes in the package filename to get the name, version and the architecture */ std::vector reversed_tokens; boost::algorithm::split(reversed_tokens, pkg_full_name, boost::is_any_of("-")); pkg_tokens[0] = std::string(std::crbegin(reversed_tokens[3]), std::crend(reversed_tokens[3])); /* Name */ pkg_tokens[1] = std::string(std::crbegin(reversed_tokens[2]), std::crend(reversed_tokens[2])); /* Version */ pkg_tokens[2] = std::string(std::crbegin(reversed_tokens[1]), std::crend(reversed_tokens[1])); /* Architecture */ return pkg_tokens; } /** * katja::is_installed: * Checks if a package is already installed in the system. * * @pkg_fullname: Package name should be looked for. * * Returns: Package installation information. **/ Info is_installed(const char *pkg_fullname) { Info ret = Info::installing; const char *it; std::uint8_t dashes = 0; ptrdiff_t pkg_name; if (pkg_fullname == nullptr) { return Info::unknown; } // We want to find the package name without version for the package we're // looking for. for (it = pkg_fullname + strlen(pkg_fullname); it != pkg_fullname; --it) { if (*it == '-') { if (dashes == 2) { break; } ++dashes; } } if (dashes < 2) { return Info::unknown; } pkg_name = it - pkg_fullname; // Read the package metadata directory and comprare all installed packages // with ones in the cache. auto pkg_metadata_dir = std::filesystem::path("/var/log/packages"); for (const auto& pkg_metadata_file_info : std::filesystem::directory_iterator{ pkg_metadata_dir }) { std::string filename = pkg_metadata_file_info.path().native(); const char *dir = filename.c_str(); dashes = 0; if (dir == pkg_fullname) { ret = Info::installed; } else { for (it = dir + filename.size(); it != dir; --it) { if (*it == '-') { if (dashes == 2) { break; } ++dashes; } } if (pkg_name == (it - dir) && strncmp(pkg_fullname, dir, pkg_name) == 0) { ret = Info::updating; } } if (ret != Info::installing) /* If installed */ { break; } } return ret; } }