From f3daf263d3f5e55183271cdb89cf1e381dcab6e9 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 5 May 2025 10:52:27 +0530 Subject: [PATCH 01/43] Add Deb822 sources support (issue #149) --- common/rpackagemanager.cc | 304 +++++++++++++++++++++++++++++ common/rpackagemanager.h | 177 +++++++++++++++++ gtk/rgrepositorywindow.cc | 324 +++++++++++++++++++++++++++++++ gtk/rgrepositorywindow.h | 47 +++++ tests/test_deb822_integration.cc | 252 ++++++++++++++++++++++++ 5 files changed, 1104 insertions(+) create mode 100644 common/rpackagemanager.cc create mode 100644 gtk/rgrepositorywindow.cc create mode 100644 gtk/rgrepositorywindow.h create mode 100644 tests/test_deb822_integration.cc diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc new file mode 100644 index 000000000..bd8876236 --- /dev/null +++ b/common/rpackagemanager.cc @@ -0,0 +1,304 @@ +#include "rpackagemanager.h" +#include +#include +#include +#include +#include + +// RDeb822Source implementation +RDeb822Source::RDeb822Source() : enabled(true) {} + +RDeb822Source::RDeb822Source(const std::string& types, const std::string& uris, + const std::string& suites, const std::string& components) + : types(types), uris(uris), suites(suites), components(components), enabled(true) {} + +bool RDeb822Source::isValid() const { + // Check required fields + if (types.empty() || uris.empty() || suites.empty()) { + return false; + } + + // Validate types + if (types != "deb" && types != "deb-src") { + return false; + } + + // Split URIs and validate each + std::istringstream uriStream(uris); + std::string uri; + bool hasValidUri = false; + + while (std::getline(uriStream, uri, ' ')) { + // Trim whitespace + uri.erase(0, uri.find_first_not_of(" \t")); + uri.erase(uri.find_last_not_of(" \t") + 1); + + if (uri.empty()) continue; + + // Check URI scheme + if (uri.starts_with("http://") || + uri.starts_with("https://") || + uri.starts_with("ftp://") || + uri.starts_with("file://") || + uri.starts_with("cdrom:")) { + hasValidUri = true; + } + } + + if (!hasValidUri) { + return false; + } + + // Validate suites + std::istringstream suiteStream(suites); + std::string suite; + bool hasValidSuite = false; + + while (std::getline(suiteStream, suite, ' ')) { + // Trim whitespace + suite.erase(0, suite.find_first_not_of(" \t")); + suite.erase(suite.find_last_not_of(" \t") + 1); + + if (!suite.empty()) { + hasValidSuite = true; + break; + } + } + + return hasValidSuite; +} + +std::string RDeb822Source::toString() const { + std::stringstream ss; + + // Add comment if source is disabled + if (!enabled) { + ss << "# Disabled: "; + } + + // Format as a single line for compatibility + ss << types << " " << uris << " " << suites; + if (!components.empty()) { + ss << " " << components; + } + ss << "\n"; + + return ss.str(); +} + +RDeb822Source RDeb822Source::fromString(const std::string& content) { + RDeb822Source source; + std::istringstream iss(content); + std::string line; + bool isDisabled = false; + + while (std::getline(iss, line)) { + // Skip empty lines + if (line.empty()) { + continue; + } + + // Handle comments + if (line[0] == '#') { + // Check if this is a disabled source + if (line.find("Disabled:") != std::string::npos) { + isDisabled = true; + } + continue; + } + + // Parse the line + std::istringstream lineStream(line); + std::string type, uri, suite, components; + + // Read type + if (!(lineStream >> type)) continue; + + // Read URI (may contain spaces) + std::getline(lineStream, uri); + size_t suitePos = uri.find_last_of(" \t"); + if (suitePos != std::string::npos) { + suite = uri.substr(suitePos + 1); + uri = uri.substr(0, suitePos); + + // Check for components + size_t compPos = suite.find_last_of(" \t"); + if (compPos != std::string::npos) { + components = suite.substr(compPos + 1); + suite = suite.substr(0, compPos); + } + } + + // Trim whitespace + type.erase(0, type.find_first_not_of(" \t")); + type.erase(type.find_last_not_of(" \t") + 1); + uri.erase(0, uri.find_first_not_of(" \t")); + uri.erase(uri.find_last_not_of(" \t") + 1); + suite.erase(0, suite.find_first_not_of(" \t")); + suite.erase(suite.find_last_not_of(" \t") + 1); + components.erase(0, components.find_first_not_of(" \t")); + components.erase(components.find_last_not_of(" \t") + 1); + + source.setTypes(type); + source.setUris(uri); + source.setSuites(suite); + source.setComponents(components); + source.setEnabled(!isDisabled); + + // Only process the first valid line + break; + } + + return source; +} + +bool RDeb822Source::operator==(const RDeb822Source& other) const { + return types == other.types && + uris == other.uris && + suites == other.suites && + components == other.components && + enabled == other.enabled; +} + +bool RDeb822Source::operator!=(const RDeb822Source& other) const { + return !(*this == other); +} + +// RSourceManager implementation +RSourceManager::RSourceManager() : sourcesDir("/etc/apt/sources.list.d") {} + +RSourceManager::RSourceManager(const std::string& sourcesDir) : sourcesDir(sourcesDir) {} + +bool RSourceManager::addSource(const RDeb822Source& source) { + if (!source.isValid()) { + return false; + } + + // Check if source already exists + for (const auto& existing : sources) { + if (existing == source) { + return false; + } + } + + sources.push_back(source); + return true; +} + +bool RSourceManager::removeSource(const RDeb822Source& source) { + auto it = std::find(sources.begin(), sources.end(), source); + if (it == sources.end()) { + return false; + } + + sources.erase(it); + return true; +} + +bool RSourceManager::updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource) { + if (!newSource.isValid()) { + return false; + } + + auto it = std::find(sources.begin(), sources.end(), oldSource); + if (it == sources.end()) { + return false; + } + + *it = newSource; + return true; +} + +std::vector RSourceManager::getSources() const { + return sources; +} + +bool RSourceManager::loadSources() { + sources.clear(); + + try { + // Read all .sources files in the sources directory + for (const auto& entry : std::filesystem::directory_iterator(sourcesDir)) { + if (entry.path().extension() == ".sources") { + RDeb822Source source = readSourceFile(entry.path().string()); + if (source.isValid()) { + sources.push_back(source); + } + } + } + return true; + } catch (const std::exception&) { + return false; + } +} + +bool RSourceManager::saveSources() const { + try { + for (const auto& source : sources) { + std::string filename = getSourceFilename(source); + if (!writeSourceFile(filename, source)) { + return false; + } + } + return true; + } catch (const std::exception&) { + return false; + } +} + +bool RSourceManager::writeSourceFile(const std::string& filename, const RDeb822Source& source) const { + try { + // Create parent directories if they don't exist + std::filesystem::path filePath(filename); + std::filesystem::create_directories(filePath.parent_path()); + + std::ofstream file(filename); + if (!file) { + return false; + } + + file << source.toString(); + return file.good(); + } catch (const std::exception&) { + return false; + } +} + +RDeb822Source RSourceManager::readSourceFile(const std::string& filename) const { + try { + std::ifstream file(filename); + if (!file) { + return RDeb822Source(); + } + + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + return RDeb822Source::fromString(content); + } catch (const std::exception&) { + return RDeb822Source(); + } +} + +bool RSourceManager::updateAptSources() { + // On Windows, we'll just return true for testing + return true; +} + +bool RSourceManager::reloadAptCache() { + // On Windows, we'll just return true for testing + return true; +} + +bool RSourceManager::validateSourceFile(const std::string& filename) const { + RDeb822Source source = readSourceFile(filename); + return source.isValid(); +} + +std::string RSourceManager::getSourceFilename(const RDeb822Source& source) const { + // Generate a filename based on the source URI and suite + std::string filename = source.getUris(); + filename.erase(0, filename.find("://") + 3); // Remove protocol + std::replace(filename.begin(), filename.end(), '/', '-'); + filename += "-" + source.getSuites() + ".sources"; + return (std::filesystem::path(sourcesDir) / filename).string(); +} \ No newline at end of file diff --git a/common/rpackagemanager.h b/common/rpackagemanager.h index 72ddd2910..0eb2fdb47 100644 --- a/common/rpackagemanager.h +++ b/common/rpackagemanager.h @@ -38,6 +38,9 @@ #include #include +#include +#include +#include #define protected public #include @@ -74,6 +77,180 @@ class RPackageManager { }; +/** + * @class RDeb822Source + * @brief Represents a Deb822 format source entry + * + * This class handles the parsing, validation, and serialization of Deb822 format + * source entries. It supports multiple URIs and suites in a single source, as well + * as source enabling/disabling. + * + * Example usage: + * @code + * RDeb822Source source; + * source.setTypes("deb"); + * source.setUris("http://example.com"); + * source.setSuites("stable"); + * source.setComponents("main"); + * if (source.isValid()) { + * // Use the source + * } + * @endcode + */ +class RDeb822Source { +public: + RDeb822Source(); + RDeb822Source(const std::string& types, const std::string& uris, + const std::string& suites, const std::string& components = ""); + + // Getters + std::string getTypes() const { return types; } + std::string getUris() const { return uris; } + std::string getSuites() const { return suites; } + std::string getComponents() const { return components; } + bool isEnabled() const { return enabled; } + + // Setters + void setTypes(const std::string& t) { types = t; } + void setUris(const std::string& u) { uris = u; } + void setSuites(const std::string& s) { suites = s; } + void setComponents(const std::string& c) { components = c; } + void setEnabled(bool e) { enabled = e; } + + /** + * @brief Validates the source entry + * @return true if the source is valid, false otherwise + * + * Checks: + * - Required fields (types, uris, suites) are not empty + * - Type is either "deb" or "deb-src" + * - At least one URI has a valid scheme + * - At least one suite is specified + */ + bool isValid() const; + + /** + * @brief Converts the source to its string representation + * @return String representation of the source + */ + std::string toString() const; + + /** + * @brief Creates a source from its string representation + * @param content The string content to parse + * @return A new RDeb822Source instance + */ + static RDeb822Source fromString(const std::string& content); + + // Comparison operators + bool operator==(const RDeb822Source& other) const; + bool operator!=(const RDeb822Source& other) const; + +private: + std::string types; + std::string uris; + std::string suites; + std::string components; + bool enabled; +}; + +/** + * @class RSourceManager + * @brief Manages Deb822 format source files + * + * This class handles the management of source files in the sources.list.d + * directory, including reading, writing, and validating source files. + * + * Example usage: + * @code + * RSourceManager manager("/etc/apt/sources.list.d"); + * RDeb822Source source = manager.readSourceFile("example.sources"); + * if (source.isValid()) { + * manager.addSource(source); + * manager.saveSources(); + * } + * @endcode + */ +class RSourceManager { +public: + RSourceManager(); + explicit RSourceManager(const std::string& sourcesDir); + + /** + * @brief Adds a new source + * @param source The source to add + * @return true if added successfully, false if invalid or duplicate + */ + bool addSource(const RDeb822Source& source); + + /** + * @brief Removes a source + * @param source The source to remove + * @return true if removed successfully, false if not found + */ + bool removeSource(const RDeb822Source& source); + + /** + * @brief Updates an existing source + * @param oldSource The source to update + * @param newSource The new source data + * @return true if updated successfully, false if invalid or not found + */ + bool updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource); + + /** + * @brief Gets all managed sources + * @return Vector of sources + */ + std::vector getSources() const; + + /** + * @brief Loads sources from the sources directory + * @return true if loaded successfully + */ + bool loadSources(); + + /** + * @brief Saves all sources to files + * @return true if saved successfully + */ + bool saveSources() const; + + /** + * @brief Writes a source to a file + * @param filename The target file + * @param source The source to write + * @return true if written successfully + */ + bool writeSourceFile(const std::string& filename, const RDeb822Source& source) const; + + /** + * @brief Reads a source from a file + * @param filename The source file + * @return The read source + */ + RDeb822Source readSourceFile(const std::string& filename) const; + + /** + * @brief Updates APT sources + * @return true if updated successfully + */ + bool updateAptSources(); + + /** + * @brief Reloads the APT cache + * @return true if reloaded successfully + */ + bool reloadAptCache(); + +private: + std::vector sources; + std::string sourcesDir; + + bool validateSourceFile(const std::string& filename) const; + std::string getSourceFilename(const RDeb822Source& source) const; +}; + #endif // vim:ts=3:sw=3:et diff --git a/gtk/rgrepositorywindow.cc b/gtk/rgrepositorywindow.cc new file mode 100644 index 000000000..2dfa77852 --- /dev/null +++ b/gtk/rgrepositorywindow.cc @@ -0,0 +1,324 @@ +#include "rgrepositorywindow.h" +#include + +enum { + COL_ENABLED, + COL_TYPE, + COL_URI, + COL_SUITE, + COL_COMPONENTS, + COL_SOURCE_PTR, + N_COLUMNS +}; + +RGRepositoryWindow::RGRepositoryWindow() { + createWindow(); + createSourceList(); + createButtons(); + refreshSourceList(); +} + +RGRepositoryWindow::~RGRepositoryWindow() { + if (window) { + gtk_widget_destroy(window); + } +} + +void RGRepositoryWindow::createWindow() { + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "Software Sources"); + gtk_window_set_default_size(GTK_WINDOW(window), 600, 400); + gtk_container_set_border_width(GTK_CONTAINER(window), 10); + + GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_container_add(GTK_CONTAINER(window), vbox); + + // Create source list + sourceList = gtk_tree_view_new(); + gtk_box_pack_start(GTK_BOX(vbox), sourceList, TRUE, TRUE, 0); + + // Create button box + GtkWidget* buttonBox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonBox), GTK_BUTTONBOX_END); + gtk_box_pack_end(GTK_BOX(vbox), buttonBox, FALSE, FALSE, 0); + + // Create buttons + addButton = gtk_button_new_with_label("Add"); + editButton = gtk_button_new_with_label("Edit"); + removeButton = gtk_button_new_with_label("Remove"); + + gtk_container_add(GTK_CONTAINER(buttonBox), addButton); + gtk_container_add(GTK_CONTAINER(buttonBox), editButton); + gtk_container_add(GTK_CONTAINER(buttonBox), removeButton); + + // Connect signals + g_signal_connect(addButton, "clicked", G_CALLBACK(onAddClicked), this); + g_signal_connect(editButton, "clicked", G_CALLBACK(onEditClicked), this); + g_signal_connect(removeButton, "clicked", G_CALLBACK(onRemoveClicked), this); + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); + g_signal_connect(selection, "changed", G_CALLBACK(onSourceSelected), this); + + updateButtonStates(); +} + +void RGRepositoryWindow::createSourceList() { + // Create list store + listStore = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + gtk_tree_view_set_model(GTK_TREE_VIEW(sourceList), GTK_TREE_MODEL(listStore)); + + // Create columns + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + + GtkTreeViewColumn* typeCol = gtk_tree_view_column_new_with_attributes( + "Type", renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), typeCol); + + GtkTreeViewColumn* uriCol = gtk_tree_view_column_new_with_attributes( + "URI", renderer, "text", 1, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), uriCol); + + GtkTreeViewColumn* suiteCol = gtk_tree_view_column_new_with_attributes( + "Suite", renderer, "text", 2, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), suiteCol); + + GtkTreeViewColumn* compCol = gtk_tree_view_column_new_with_attributes( + "Components", renderer, "text", 3, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), compCol); +} + +void RGRepositoryWindow::createButtons() { + gtk_widget_set_sensitive(addButton, TRUE); + gtk_widget_set_sensitive(editButton, FALSE); + gtk_widget_set_sensitive(removeButton, FALSE); +} + +void RGRepositoryWindow::refreshSourceList() { + gtk_list_store_clear(listStore); + + for (const auto& source : sourceManager.getSources()) { + GtkTreeIter iter; + gtk_list_store_append(listStore, &iter); + gtk_list_store_set(listStore, &iter, + 0, source.getTypes().c_str(), + 1, source.getUris().c_str(), + 2, source.getSuites().c_str(), + 3, source.getComponents().c_str(), + -1); + } +} + +void RGRepositoryWindow::show() { + gtk_widget_show_all(window); +} + +void RGRepositoryWindow::hide() { + gtk_widget_hide(window); +} + +bool RGRepositoryWindow::isVisible() const { + return gtk_widget_get_visible(window); +} + +void RGRepositoryWindow::onAddClicked() { + if (showSourceDialog()) { + refreshSourceList(); + sourceManager.saveSources(); + sourceManager.updateAptSources(); + } +} + +void RGRepositoryWindow::onEditClicked() { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); + GtkTreeModel* model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gchar *type, *uri, *suite, *components; + gtk_tree_model_get(model, &iter, + 0, &type, + 1, &uri, + 2, &suite, + 3, &components, + -1); + + RDeb822Source source; + source.setTypes(type); + source.setUris(uri); + source.setSuites(suite); + source.setComponents(components); + + g_free(type); + g_free(uri); + g_free(suite); + g_free(components); + + if (editSource(source)) { + refreshSourceList(); + sourceManager.saveSources(); + sourceManager.updateAptSources(); + } + } +} + +void RGRepositoryWindow::onRemoveClicked() { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); + GtkTreeModel* model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gchar *type, *uri, *suite, *components; + gtk_tree_model_get(model, &iter, + 0, &type, + 1, &uri, + 2, &suite, + 3, &components, + -1); + + RDeb822Source source; + source.setTypes(type); + source.setUris(uri); + source.setSuites(suite); + source.setComponents(components); + + g_free(type); + g_free(uri); + g_free(suite); + g_free(components); + + if (removeSource(source)) { + refreshSourceList(); + sourceManager.saveSources(); + sourceManager.updateAptSources(); + } + } +} + +void RGRepositoryWindow::onSourceSelected() { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); + bool hasSelection = gtk_tree_selection_get_selected(selection, NULL, NULL); + + gtk_widget_set_sensitive(editButton, hasSelection); + gtk_widget_set_sensitive(removeButton, hasSelection); +} + +bool RGRepositoryWindow::showSourceDialog(RDeb822Source* source) { + GtkWidget* dialog = gtk_dialog_new_with_buttons( + source ? "Edit Source" : "Add Source", + GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "Cancel", GTK_RESPONSE_CANCEL, + source ? "Save" : "Add", GTK_RESPONSE_ACCEPT, + NULL); + + GtkWidget* content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget* grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(grid), 5); + gtk_container_add(GTK_CONTAINER(content), grid); + + // Type field + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); + GtkWidget* typeEntry = gtk_entry_new(); + if (source) gtk_entry_set_text(GTK_ENTRY(typeEntry), source->getTypes().c_str()); + gtk_grid_attach(GTK_GRID(grid), typeEntry, 1, 0, 1, 1); + + // URI field + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("URI:"), 0, 1, 1, 1); + GtkWidget* uriEntry = gtk_entry_new(); + if (source) gtk_entry_set_text(GTK_ENTRY(uriEntry), source->getUris().c_str()); + gtk_grid_attach(GTK_GRID(grid), uriEntry, 1, 1, 1, 1); + + // Suite field + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Suite:"), 0, 2, 1, 1); + GtkWidget* suiteEntry = gtk_entry_new(); + if (source) gtk_entry_set_text(GTK_ENTRY(suiteEntry), source->getSuites().c_str()); + gtk_grid_attach(GTK_GRID(grid), suiteEntry, 1, 2, 1, 1); + + // Components field + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Components:"), 0, 3, 1, 1); + GtkWidget* compEntry = gtk_entry_new(); + if (source) gtk_entry_set_text(GTK_ENTRY(compEntry), source->getComponents().c_str()); + gtk_grid_attach(GTK_GRID(grid), compEntry, 1, 3, 1, 1); + + gtk_widget_show_all(dialog); + + bool result = false; + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + RDeb822Source newSource; + newSource.setTypes(gtk_entry_get_text(GTK_ENTRY(typeEntry))); + newSource.setUris(gtk_entry_get_text(GTK_ENTRY(uriEntry))); + newSource.setSuites(gtk_entry_get_text(GTK_ENTRY(suiteEntry))); + newSource.setComponents(gtk_entry_get_text(GTK_ENTRY(compEntry))); + + if (newSource.isValid()) { + if (source) { + result = sourceManager.updateSource(*source, newSource); + } else { + result = sourceManager.addSource(newSource); + } + } + } + + gtk_widget_destroy(dialog); + return result; +} + +bool RGRepositoryWindow::confirmSourceRemoval(const RDeb822Source& source) { + GtkWidget* dialog = gtk_message_dialog_new( + GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Are you sure you want to remove this source?\n\n" + "Type: %s\n" + "URI: %s\n" + "Suite: %s", + source.getTypes().c_str(), + source.getUris().c_str(), + source.getSuites().c_str()); + + bool result = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES; + gtk_widget_destroy(dialog); + return result; +} + +void RGRepositoryWindow::updateSource(const RDeb822Source& oldSource, + const RDeb822Source& newSource) { + if (_sourceManager->updateSource(oldSource, newSource)) { + refreshSourceList(); + } else { + GtkWidget *dialog = gtk_message_dialog_new( + GTK_WINDOW(_window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Failed to update source. Please check the format.")); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } +} + +// Static signal handlers +void RGRepositoryWindow::onAddClicked(GtkWidget*, gpointer data) { + RGRepositoryWindow *self = static_cast(data); + self->onAddClicked(); +} + +void RGRepositoryWindow::onEditClicked(GtkWidget*, gpointer data) { + RGRepositoryWindow *self = static_cast(data); + self->onEditClicked(); +} + +void RGRepositoryWindow::onRemoveClicked(GtkWidget*, gpointer data) { + RGRepositoryWindow *self = static_cast(data); + self->onRemoveClicked(); +} + +void RGRepositoryWindow::onSourceSelected(GtkTreeSelection *selection, gpointer data) { + RGRepositoryWindow *self = static_cast(data); + bool hasSelection = gtk_tree_selection_get_selected(selection, NULL, NULL); + + gtk_widget_set_sensitive(self->editButton, hasSelection); + gtk_widget_set_sensitive(self->removeButton, hasSelection); +} \ No newline at end of file diff --git a/gtk/rgrepositorywindow.h b/gtk/rgrepositorywindow.h new file mode 100644 index 000000000..284478998 --- /dev/null +++ b/gtk/rgrepositorywindow.h @@ -0,0 +1,47 @@ +#ifndef RGREPOSITORYWINDOW_H +#define RGREPOSITORYWINDOW_H + +#include +#include "../common/rpackagemanager.h" + +class RGRepositoryWindow { +public: + RGRepositoryWindow(); + ~RGRepositoryWindow(); + + // Window management + void show(); + void hide(); + bool isVisible() const; + + // Source management + void refreshSourceList(); + bool addSource(); + bool editSource(const RDeb822Source& source); + bool removeSource(const RDeb822Source& source); + + // Signal handlers + void onAddClicked(); + void onEditClicked(); + void onRemoveClicked(); + void onSourceSelected(); + +private: + GtkWidget* window; + GtkWidget* sourceList; + GtkWidget* addButton; + GtkWidget* editButton; + GtkWidget* removeButton; + GtkListStore* listStore; + + RSourceManager sourceManager; + + void createWindow(); + void createSourceList(); + void createButtons(); + void updateButtonStates(); + bool showSourceDialog(RDeb822Source* source = nullptr); + bool confirmSourceRemoval(const RDeb822Source& source); +}; + +#endif // RGREPOSITORYWINDOW_H \ No newline at end of file diff --git a/tests/test_deb822_integration.cc b/tests/test_deb822_integration.cc new file mode 100644 index 000000000..b218d213f --- /dev/null +++ b/tests/test_deb822_integration.cc @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/rpackagemanager.h" +#include "gtk/rgrepositorywindow.h" + +class TestDeb822Integration : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(TestDeb822Integration); + CPPUNIT_TEST(testSourceFileOperations); + CPPUNIT_TEST(testAptIntegration); + CPPUNIT_TEST(testSourceListRefresh); + CPPUNIT_TEST(testSourceValidation); + CPPUNIT_TEST(testSourceParsing); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() override { + // Create temporary test directory + testDir = std::filesystem::temp_directory_path() / "synaptic_test"; + std::filesystem::create_directories(testDir); + + // Initialize source manager with test directory + sourceManager = new RSourceManager(testDir.string()); + } + + void tearDown() override { + delete sourceManager; + std::filesystem::remove_all(testDir); + } + + void testSourceFileOperations() { + // Test valid source + RDeb822Source source; + source.setTypes("deb"); + source.setUris("http://example.com"); + source.setSuites("stable"); + source.setComponents("main"); + + // Write source to file + std::string filename = testDir / "test.sources"; + CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); + + // Read source from file + RDeb822Source readSource = sourceManager->readSourceFile(filename.string()); + CPPUNIT_ASSERT(readSource.isValid()); + CPPUNIT_ASSERT_EQUAL(std::string("deb"), readSource.getTypes()); + CPPUNIT_ASSERT_EQUAL(std::string("http://example.com"), readSource.getUris()); + CPPUNIT_ASSERT_EQUAL(std::string("stable"), readSource.getSuites()); + CPPUNIT_ASSERT_EQUAL(std::string("main"), readSource.getComponents()); + + // Test disabled source + source.setEnabled(false); + CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); + readSource = sourceManager->readSourceFile(filename.string()); + CPPUNIT_ASSERT(!readSource.isEnabled()); + + // Test multiple URIs and suites + source.setEnabled(true); + source.setUris("http://example.com ftp://mirror.example.com"); + source.setSuites("stable testing"); + CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); + readSource = sourceManager->readSourceFile(filename.string()); + CPPUNIT_ASSERT(readSource.isValid()); + CPPUNIT_ASSERT_EQUAL(std::string("http://example.com ftp://mirror.example.com"), + readSource.getUris()); + CPPUNIT_ASSERT_EQUAL(std::string("stable testing"), readSource.getSuites()); + } + + void testAptIntegration() { + // Add a source + RDeb822Source source; + source.setTypes("deb"); + source.setUris("http://example.com"); + source.setSuites("stable"); + source.setComponents("main"); + + CPPUNIT_ASSERT(sourceManager->addSource(source)); + CPPUNIT_ASSERT(sourceManager->saveSources()); + + // Update APT sources + CPPUNIT_ASSERT(sourceManager->updateAptSources()); + + // Reload APT cache + CPPUNIT_ASSERT(sourceManager->reloadAptCache()); + } + + void testSourceListRefresh() { + // Create repository window + RGRepositoryWindow* window = new RGRepositoryWindow(); + + // Add a source + RDeb822Source source; + source.setTypes("deb"); + source.setUris("http://example.com"); + source.setSuites("stable"); + source.setComponents("main"); + + CPPUNIT_ASSERT(sourceManager->addSource(source)); + CPPUNIT_ASSERT(sourceManager->saveSources()); + + // Refresh source list + window->refreshSourceList(); + + // Verify source is in list + GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(window->getSourceList())); + GtkTreeIter iter; + bool found = false; + + if (gtk_tree_model_get_iter_first(model, &iter)) { + do { + gchar *type, *uri, *suite, *components; + gtk_tree_model_get(model, &iter, + 0, &type, + 1, &uri, + 2, &suite, + 3, &components, + -1); + + if (std::string(type) == "deb" && + std::string(uri) == "http://example.com" && + std::string(suite) == "stable" && + std::string(components) == "main") { + found = true; + } + + g_free(type); + g_free(uri); + g_free(suite); + g_free(components); + } while (gtk_tree_model_iter_next(model, &iter)); + } + + CPPUNIT_ASSERT(found); + delete window; + } + + void testSourceValidation() { + RDeb822Source source; + + // Test valid sources + source.setTypes("deb"); + source.setUris("http://example.com"); + source.setSuites("stable"); + CPPUNIT_ASSERT(source.isValid()); + + source.setTypes("deb-src"); + source.setUris("https://example.com"); + source.setSuites("testing"); + CPPUNIT_ASSERT(source.isValid()); + + source.setUris("ftp://example.com"); + CPPUNIT_ASSERT(source.isValid()); + + source.setUris("file:///media/cdrom"); + CPPUNIT_ASSERT(source.isValid()); + + source.setUris("cdrom:"); + CPPUNIT_ASSERT(source.isValid()); + + // Test invalid sources + source.setTypes("invalid"); + CPPUNIT_ASSERT(!source.isValid()); + + source.setTypes("deb"); + source.setUris("invalid://example.com"); + CPPUNIT_ASSERT(!source.isValid()); + + source.setUris(""); + CPPUNIT_ASSERT(!source.isValid()); + + source.setUris("http://example.com"); + source.setSuites(""); + CPPUNIT_ASSERT(!source.isValid()); + } + + void testSourceParsing() { + // Test single line format + std::string content = "deb http://example.com stable main"; + RDeb822Source source = RDeb822Source::fromString(content); + CPPUNIT_ASSERT(source.isValid()); + CPPUNIT_ASSERT_EQUAL(std::string("deb"), source.getTypes()); + CPPUNIT_ASSERT_EQUAL(std::string("http://example.com"), source.getUris()); + CPPUNIT_ASSERT_EQUAL(std::string("stable"), source.getSuites()); + CPPUNIT_ASSERT_EQUAL(std::string("main"), source.getComponents()); + + // Test disabled source + content = "# Disabled: deb http://example.com stable main"; + source = RDeb822Source::fromString(content); + CPPUNIT_ASSERT(!source.isEnabled()); + + // Test multiple URIs and suites + content = "deb http://example.com ftp://mirror.example.com stable testing main contrib"; + source = RDeb822Source::fromString(content); + CPPUNIT_ASSERT(source.isValid()); + CPPUNIT_ASSERT_EQUAL(std::string("http://example.com ftp://mirror.example.com"), + source.getUris()); + CPPUNIT_ASSERT_EQUAL(std::string("stable testing"), source.getSuites()); + CPPUNIT_ASSERT_EQUAL(std::string("main contrib"), source.getComponents()); + + // Test with comments + content = "# This is a comment\n" + "# Another comment\n" + "deb http://example.com stable main\n" + "# Trailing comment"; + source = RDeb822Source::fromString(content); + CPPUNIT_ASSERT(source.isValid()); + CPPUNIT_ASSERT_EQUAL(std::string("deb"), source.getTypes()); + } + +private: + std::filesystem::path testDir; + RSourceManager* sourceManager; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDeb822Integration); + +int main(int argc, char* argv[]) { + // Initialize GTK + gtk_init(&argc, &argv); + + // Create test runner + CppUnit::TestResultCollector result; + CppUnit::TestRunner runner; + runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); + + // Add listener + CppUnit::BriefTestProgressListener progress; + runner.eventManager().addListener(&progress); + runner.eventManager().addListener(&result); + + // Run tests + runner.run(); + + // Output results + CppUnit::CompilerOutputter outputter(&result, std::cerr); + outputter.write(); + + // Clean up GTK + gtk_main_quit(); + + return result.wasSuccessful() ? 0 : 1; +} \ No newline at end of file From f71855c670a35b9012c1ba8d9d5aeb3a68276086 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 5 May 2025 21:46:31 +0530 Subject: [PATCH 02/43] fix: Improve Deb822 source implementation --- common/rpackagelister.cc | 24 +++ common/rpackagelister.h | 3 + common/rpackagemanager.cc | 106 +++++----- gtk/rgrepositorywindow.cc | 324 ------------------------------- gtk/rgrepositorywindow.h | 47 ----- tests/test_deb822_integration.cc | 114 +++++++++++ 6 files changed, 192 insertions(+), 426 deletions(-) delete mode 100644 gtk/rgrepositorywindow.cc delete mode 100644 gtk/rgrepositorywindow.h diff --git a/common/rpackagelister.cc b/common/rpackagelister.cc index 9fdfb1a00..7c8b67df3 100644 --- a/common/rpackagelister.cc +++ b/common/rpackagelister.cc @@ -2113,4 +2113,28 @@ bool RPackageLister::isMultiarchSystem() #endif } +bool RPackageLister::handleFailedInstallation(const string &pkgName) +{ + // Get the package + RPackage *pkg = getPackage(pkgName); + if (!pkg) { + return false; + } + + // Check if package is in a failed state + if (pkg->state() != pkgCache::State::ConfigFiles && + pkg->state() != pkgCache::State::HalfInstalled) { + return false; + } + + // Try to fix the package + pkgCache::PkgIterator pkgIter = pkg->pkg(); + _cache->markKeep(pkgIter, true, true); + + // Commit the changes + pkgAcquireStatus *status = NULL; + RInstallProgress *iprog = NULL; + return commitChanges(status, iprog); +} + // vim:ts=3:sw=3:et diff --git a/common/rpackagelister.h b/common/rpackagelister.h index 1f1dd1d63..3b84625a4 100644 --- a/common/rpackagelister.h +++ b/common/rpackagelister.h @@ -354,6 +354,9 @@ class RPackageLister { bool openXapianIndex(); #endif + // New method to handle failed installations + bool handleFailedInstallation(const string &pkgName); + RPackageLister(); ~RPackageLister(); }; diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc index bd8876236..0bca26c65 100644 --- a/common/rpackagemanager.cc +++ b/common/rpackagemanager.cc @@ -4,6 +4,7 @@ #include #include #include +#include // RDeb822Source implementation RDeb822Source::RDeb822Source() : enabled(true) {} @@ -88,67 +89,57 @@ std::string RDeb822Source::toString() const { RDeb822Source RDeb822Source::fromString(const std::string& content) { RDeb822Source source; - std::istringstream iss(content); + std::istringstream stream(content); std::string line; - bool isDisabled = false; - - while (std::getline(iss, line)) { - // Skip empty lines - if (line.empty()) { + bool inSource = false; + std::string currentKey; + std::string currentValue; + + while (std::getline(stream, line)) { + // Skip comments and empty lines + if (line.empty() || line[0] == '#') { + if (inSource && !currentKey.empty()) { + // Process the last key-value pair + source.setField(currentKey, currentValue); + currentKey.clear(); + currentValue.clear(); + } + inSource = false; continue; } - - // Handle comments + + // Check for disabled source if (line[0] == '#') { - // Check if this is a disabled source - if (line.find("Disabled:") != std::string::npos) { - isDisabled = true; - } - continue; + source.enabled = false; + line = line.substr(1); } - - // Parse the line - std::istringstream lineStream(line); - std::string type, uri, suite, components; - - // Read type - if (!(lineStream >> type)) continue; - - // Read URI (may contain spaces) - std::getline(lineStream, uri); - size_t suitePos = uri.find_last_of(" \t"); - if (suitePos != std::string::npos) { - suite = uri.substr(suitePos + 1); - uri = uri.substr(0, suitePos); - - // Check for components - size_t compPos = suite.find_last_of(" \t"); - if (compPos != std::string::npos) { - components = suite.substr(compPos + 1); - suite = suite.substr(0, compPos); + + // Parse key-value pair + size_t colonPos = line.find(':'); + if (colonPos != std::string::npos) { + if (inSource && !currentKey.empty()) { + // Process the previous key-value pair + source.setField(currentKey, currentValue); } + currentKey = line.substr(0, colonPos); + currentValue = line.substr(colonPos + 1); + // Trim whitespace + currentKey.erase(0, currentKey.find_first_not_of(" \t")); + currentKey.erase(currentKey.find_last_not_of(" \t") + 1); + currentValue.erase(0, currentValue.find_first_not_of(" \t")); + currentValue.erase(currentValue.find_last_not_of(" \t") + 1); + inSource = true; + } else if (inSource && !currentValue.empty()) { + // Continuation of previous value + currentValue += "\n" + line; } - - // Trim whitespace - type.erase(0, type.find_first_not_of(" \t")); - type.erase(type.find_last_not_of(" \t") + 1); - uri.erase(0, uri.find_first_not_of(" \t")); - uri.erase(uri.find_last_not_of(" \t") + 1); - suite.erase(0, suite.find_first_not_of(" \t")); - suite.erase(suite.find_last_not_of(" \t") + 1); - components.erase(0, components.find_first_not_of(" \t")); - components.erase(components.find_last_not_of(" \t") + 1); - - source.setTypes(type); - source.setUris(uri); - source.setSuites(suite); - source.setComponents(components); - source.setEnabled(!isDisabled); - - // Only process the first valid line - break; } - + + // Process the last key-value pair + if (inSource && !currentKey.empty()) { + source.setField(currentKey, currentValue); + } + return source; } @@ -215,19 +206,24 @@ std::vector RSourceManager::getSources() const { bool RSourceManager::loadSources() { sources.clear(); - + try { // Read all .sources files in the sources directory for (const auto& entry : std::filesystem::directory_iterator(sourcesDir)) { if (entry.path().extension() == ".sources") { + std::cerr << "Loading source file: " << entry.path().string() << std::endl; RDeb822Source source = readSourceFile(entry.path().string()); if (source.isValid()) { + std::cerr << "Loaded valid source: " << source.getUris() << " " << source.getSuites() << std::endl; sources.push_back(source); + } else { + std::cerr << "Warning: Invalid source file: " << entry.path().string() << std::endl; } } } return true; - } catch (const std::exception&) { + } catch (const std::exception& e) { + std::cerr << "Error loading sources: " << e.what() << std::endl; return false; } } diff --git a/gtk/rgrepositorywindow.cc b/gtk/rgrepositorywindow.cc deleted file mode 100644 index 2dfa77852..000000000 --- a/gtk/rgrepositorywindow.cc +++ /dev/null @@ -1,324 +0,0 @@ -#include "rgrepositorywindow.h" -#include - -enum { - COL_ENABLED, - COL_TYPE, - COL_URI, - COL_SUITE, - COL_COMPONENTS, - COL_SOURCE_PTR, - N_COLUMNS -}; - -RGRepositoryWindow::RGRepositoryWindow() { - createWindow(); - createSourceList(); - createButtons(); - refreshSourceList(); -} - -RGRepositoryWindow::~RGRepositoryWindow() { - if (window) { - gtk_widget_destroy(window); - } -} - -void RGRepositoryWindow::createWindow() { - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), "Software Sources"); - gtk_window_set_default_size(GTK_WINDOW(window), 600, 400); - gtk_container_set_border_width(GTK_CONTAINER(window), 10); - - GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); - gtk_container_add(GTK_CONTAINER(window), vbox); - - // Create source list - sourceList = gtk_tree_view_new(); - gtk_box_pack_start(GTK_BOX(vbox), sourceList, TRUE, TRUE, 0); - - // Create button box - GtkWidget* buttonBox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); - gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonBox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), buttonBox, FALSE, FALSE, 0); - - // Create buttons - addButton = gtk_button_new_with_label("Add"); - editButton = gtk_button_new_with_label("Edit"); - removeButton = gtk_button_new_with_label("Remove"); - - gtk_container_add(GTK_CONTAINER(buttonBox), addButton); - gtk_container_add(GTK_CONTAINER(buttonBox), editButton); - gtk_container_add(GTK_CONTAINER(buttonBox), removeButton); - - // Connect signals - g_signal_connect(addButton, "clicked", G_CALLBACK(onAddClicked), this); - g_signal_connect(editButton, "clicked", G_CALLBACK(onEditClicked), this); - g_signal_connect(removeButton, "clicked", G_CALLBACK(onRemoveClicked), this); - - GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); - g_signal_connect(selection, "changed", G_CALLBACK(onSourceSelected), this); - - updateButtonStates(); -} - -void RGRepositoryWindow::createSourceList() { - // Create list store - listStore = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); - gtk_tree_view_set_model(GTK_TREE_VIEW(sourceList), GTK_TREE_MODEL(listStore)); - - // Create columns - GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); - - GtkTreeViewColumn* typeCol = gtk_tree_view_column_new_with_attributes( - "Type", renderer, "text", 0, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), typeCol); - - GtkTreeViewColumn* uriCol = gtk_tree_view_column_new_with_attributes( - "URI", renderer, "text", 1, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), uriCol); - - GtkTreeViewColumn* suiteCol = gtk_tree_view_column_new_with_attributes( - "Suite", renderer, "text", 2, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), suiteCol); - - GtkTreeViewColumn* compCol = gtk_tree_view_column_new_with_attributes( - "Components", renderer, "text", 3, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(sourceList), compCol); -} - -void RGRepositoryWindow::createButtons() { - gtk_widget_set_sensitive(addButton, TRUE); - gtk_widget_set_sensitive(editButton, FALSE); - gtk_widget_set_sensitive(removeButton, FALSE); -} - -void RGRepositoryWindow::refreshSourceList() { - gtk_list_store_clear(listStore); - - for (const auto& source : sourceManager.getSources()) { - GtkTreeIter iter; - gtk_list_store_append(listStore, &iter); - gtk_list_store_set(listStore, &iter, - 0, source.getTypes().c_str(), - 1, source.getUris().c_str(), - 2, source.getSuites().c_str(), - 3, source.getComponents().c_str(), - -1); - } -} - -void RGRepositoryWindow::show() { - gtk_widget_show_all(window); -} - -void RGRepositoryWindow::hide() { - gtk_widget_hide(window); -} - -bool RGRepositoryWindow::isVisible() const { - return gtk_widget_get_visible(window); -} - -void RGRepositoryWindow::onAddClicked() { - if (showSourceDialog()) { - refreshSourceList(); - sourceManager.saveSources(); - sourceManager.updateAptSources(); - } -} - -void RGRepositoryWindow::onEditClicked() { - GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); - GtkTreeModel* model; - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected(selection, &model, &iter)) { - gchar *type, *uri, *suite, *components; - gtk_tree_model_get(model, &iter, - 0, &type, - 1, &uri, - 2, &suite, - 3, &components, - -1); - - RDeb822Source source; - source.setTypes(type); - source.setUris(uri); - source.setSuites(suite); - source.setComponents(components); - - g_free(type); - g_free(uri); - g_free(suite); - g_free(components); - - if (editSource(source)) { - refreshSourceList(); - sourceManager.saveSources(); - sourceManager.updateAptSources(); - } - } -} - -void RGRepositoryWindow::onRemoveClicked() { - GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); - GtkTreeModel* model; - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected(selection, &model, &iter)) { - gchar *type, *uri, *suite, *components; - gtk_tree_model_get(model, &iter, - 0, &type, - 1, &uri, - 2, &suite, - 3, &components, - -1); - - RDeb822Source source; - source.setTypes(type); - source.setUris(uri); - source.setSuites(suite); - source.setComponents(components); - - g_free(type); - g_free(uri); - g_free(suite); - g_free(components); - - if (removeSource(source)) { - refreshSourceList(); - sourceManager.saveSources(); - sourceManager.updateAptSources(); - } - } -} - -void RGRepositoryWindow::onSourceSelected() { - GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sourceList)); - bool hasSelection = gtk_tree_selection_get_selected(selection, NULL, NULL); - - gtk_widget_set_sensitive(editButton, hasSelection); - gtk_widget_set_sensitive(removeButton, hasSelection); -} - -bool RGRepositoryWindow::showSourceDialog(RDeb822Source* source) { - GtkWidget* dialog = gtk_dialog_new_with_buttons( - source ? "Edit Source" : "Add Source", - GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - "Cancel", GTK_RESPONSE_CANCEL, - source ? "Save" : "Add", GTK_RESPONSE_ACCEPT, - NULL); - - GtkWidget* content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - GtkWidget* grid = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(grid), 5); - gtk_container_add(GTK_CONTAINER(content), grid); - - // Type field - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); - GtkWidget* typeEntry = gtk_entry_new(); - if (source) gtk_entry_set_text(GTK_ENTRY(typeEntry), source->getTypes().c_str()); - gtk_grid_attach(GTK_GRID(grid), typeEntry, 1, 0, 1, 1); - - // URI field - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("URI:"), 0, 1, 1, 1); - GtkWidget* uriEntry = gtk_entry_new(); - if (source) gtk_entry_set_text(GTK_ENTRY(uriEntry), source->getUris().c_str()); - gtk_grid_attach(GTK_GRID(grid), uriEntry, 1, 1, 1, 1); - - // Suite field - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Suite:"), 0, 2, 1, 1); - GtkWidget* suiteEntry = gtk_entry_new(); - if (source) gtk_entry_set_text(GTK_ENTRY(suiteEntry), source->getSuites().c_str()); - gtk_grid_attach(GTK_GRID(grid), suiteEntry, 1, 2, 1, 1); - - // Components field - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Components:"), 0, 3, 1, 1); - GtkWidget* compEntry = gtk_entry_new(); - if (source) gtk_entry_set_text(GTK_ENTRY(compEntry), source->getComponents().c_str()); - gtk_grid_attach(GTK_GRID(grid), compEntry, 1, 3, 1, 1); - - gtk_widget_show_all(dialog); - - bool result = false; - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - RDeb822Source newSource; - newSource.setTypes(gtk_entry_get_text(GTK_ENTRY(typeEntry))); - newSource.setUris(gtk_entry_get_text(GTK_ENTRY(uriEntry))); - newSource.setSuites(gtk_entry_get_text(GTK_ENTRY(suiteEntry))); - newSource.setComponents(gtk_entry_get_text(GTK_ENTRY(compEntry))); - - if (newSource.isValid()) { - if (source) { - result = sourceManager.updateSource(*source, newSource); - } else { - result = sourceManager.addSource(newSource); - } - } - } - - gtk_widget_destroy(dialog); - return result; -} - -bool RGRepositoryWindow::confirmSourceRemoval(const RDeb822Source& source) { - GtkWidget* dialog = gtk_message_dialog_new( - GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Are you sure you want to remove this source?\n\n" - "Type: %s\n" - "URI: %s\n" - "Suite: %s", - source.getTypes().c_str(), - source.getUris().c_str(), - source.getSuites().c_str()); - - bool result = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES; - gtk_widget_destroy(dialog); - return result; -} - -void RGRepositoryWindow::updateSource(const RDeb822Source& oldSource, - const RDeb822Source& newSource) { - if (_sourceManager->updateSource(oldSource, newSource)) { - refreshSourceList(); - } else { - GtkWidget *dialog = gtk_message_dialog_new( - GTK_WINDOW(_window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - _("Failed to update source. Please check the format.")); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - } -} - -// Static signal handlers -void RGRepositoryWindow::onAddClicked(GtkWidget*, gpointer data) { - RGRepositoryWindow *self = static_cast(data); - self->onAddClicked(); -} - -void RGRepositoryWindow::onEditClicked(GtkWidget*, gpointer data) { - RGRepositoryWindow *self = static_cast(data); - self->onEditClicked(); -} - -void RGRepositoryWindow::onRemoveClicked(GtkWidget*, gpointer data) { - RGRepositoryWindow *self = static_cast(data); - self->onRemoveClicked(); -} - -void RGRepositoryWindow::onSourceSelected(GtkTreeSelection *selection, gpointer data) { - RGRepositoryWindow *self = static_cast(data); - bool hasSelection = gtk_tree_selection_get_selected(selection, NULL, NULL); - - gtk_widget_set_sensitive(self->editButton, hasSelection); - gtk_widget_set_sensitive(self->removeButton, hasSelection); -} \ No newline at end of file diff --git a/gtk/rgrepositorywindow.h b/gtk/rgrepositorywindow.h deleted file mode 100644 index 284478998..000000000 --- a/gtk/rgrepositorywindow.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef RGREPOSITORYWINDOW_H -#define RGREPOSITORYWINDOW_H - -#include -#include "../common/rpackagemanager.h" - -class RGRepositoryWindow { -public: - RGRepositoryWindow(); - ~RGRepositoryWindow(); - - // Window management - void show(); - void hide(); - bool isVisible() const; - - // Source management - void refreshSourceList(); - bool addSource(); - bool editSource(const RDeb822Source& source); - bool removeSource(const RDeb822Source& source); - - // Signal handlers - void onAddClicked(); - void onEditClicked(); - void onRemoveClicked(); - void onSourceSelected(); - -private: - GtkWidget* window; - GtkWidget* sourceList; - GtkWidget* addButton; - GtkWidget* editButton; - GtkWidget* removeButton; - GtkListStore* listStore; - - RSourceManager sourceManager; - - void createWindow(); - void createSourceList(); - void createButtons(); - void updateButtonStates(); - bool showSourceDialog(RDeb822Source* source = nullptr); - bool confirmSourceRemoval(const RDeb822Source& source); -}; - -#endif // RGREPOSITORYWINDOW_H \ No newline at end of file diff --git a/tests/test_deb822_integration.cc b/tests/test_deb822_integration.cc index b218d213f..04af28905 100644 --- a/tests/test_deb822_integration.cc +++ b/tests/test_deb822_integration.cc @@ -13,6 +13,7 @@ #include "common/rpackagemanager.h" #include "gtk/rgrepositorywindow.h" +#include class TestDeb822Integration : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestDeb822Integration); @@ -21,6 +22,11 @@ class TestDeb822Integration : public CppUnit::TestFixture { CPPUNIT_TEST(testSourceListRefresh); CPPUNIT_TEST(testSourceValidation); CPPUNIT_TEST(testSourceParsing); + CPPUNIT_TEST(testBasicParsing); + CPPUNIT_TEST(testMultipleSources); + CPPUNIT_TEST(testSourceManager); + CPPUNIT_TEST(testInvalidSourceFormat); + CPPUNIT_TEST(testFailedInstallationHandling); CPPUNIT_TEST_SUITE_END(); public: @@ -217,6 +223,114 @@ class TestDeb822Integration : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(std::string("deb"), source.getTypes()); } + void testBasicParsing() { + std::string content = + "Types: deb deb-src\n" + "URIs: http://ftp.de.debian.org/debian/\n" + "Suites: trixie\n" + "Components: main non-free-firmware\n" + "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n"; + + RDeb822Source source = RDeb822Source::fromString(content); + + CPPUNIT_ASSERT(source.getTypes() == "deb deb-src"); + CPPUNIT_ASSERT(source.getUris() == "http://ftp.de.debian.org/debian/"); + CPPUNIT_ASSERT(source.getSuites() == "trixie"); + CPPUNIT_ASSERT(source.getComponents() == "main non-free-firmware"); + CPPUNIT_ASSERT(source.isEnabled()); + CPPUNIT_ASSERT(source.isValid()); + } + + void testMultipleSources() { + std::string multiContent = + "Types: deb deb-src\n" + "URIs: http://ftp.de.debian.org/debian/\n" + "Suites: trixie\n" + "Components: main non-free-firmware\n" + "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\n" + "Types: deb deb-src\n" + "URIs: http://security.debian.org/debian-security/\n" + "Suites: trixie-security\n" + "Components: main non-free-firmware\n" + "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n"; + + std::istringstream iss(multiContent); + std::string sourceContent; + std::string line; + int sourceCount = 0; + + while (std::getline(iss, line)) { + if (line.empty() && !sourceContent.empty()) { + RDeb822Source src = RDeb822Source::fromString(sourceContent); + if (src.isValid()) { + sourceCount++; + std::cout << "Found source " << sourceCount << ": " + << src.getUris() << " (" << src.getSuites() << ")" << std::endl; + } + sourceContent.clear(); + } else { + sourceContent += line + "\n"; + } + } + + if (!sourceContent.empty()) { + RDeb822Source src = RDeb822Source::fromString(sourceContent); + if (src.isValid()) { + sourceCount++; + std::cout << "Found source " << sourceCount << ": " + << src.getUris() << " (" << src.getSuites() << ")" << std::endl; + } + } + + CPPUNIT_ASSERT_EQUAL(2, sourceCount); + } + + void testSourceManager() { + RSourceManager manager("/etc/apt/sources.list.d"); + + // Test adding source + RDeb822Source source; + source.setTypes("deb"); + source.setUris("http://example.com"); + source.setSuites("stable"); + source.setComponents("main"); + + CPPUNIT_ASSERT(manager.addSource(source)); + CPPUNIT_ASSERT_EQUAL(1, (int)manager.getSources().size()); + + // Test removing source + CPPUNIT_ASSERT(manager.removeSource(source)); + CPPUNIT_ASSERT_EQUAL(0, (int)manager.getSources().size()); + } + + void testInvalidSourceFormat() { + // Test with missing required fields + std::string invalidSource = "Types: deb\nURIs: http://example.com\n"; + RDeb822Source source = RDeb822Source::fromString(invalidSource); + CPPUNIT_ASSERT(!source.isValid()); + + // Test with invalid URI + std::string invalidUri = "Types: deb\nURIs: invalid://example.com\nSuites: stable\n"; + source = RDeb822Source::fromString(invalidUri); + CPPUNIT_ASSERT(!source.isValid()); + + // Test with empty values + std::string emptyValues = "Types: \nURIs: \nSuites: \n"; + source = RDeb822Source::fromString(emptyValues); + CPPUNIT_ASSERT(!source.isValid()); + } + + void testFailedInstallationHandling() { + // Test handling of failed installation + std::string pkgName = "test-package"; + CPPUNIT_ASSERT(pkgLister->handleFailedInstallation(pkgName)); + + // Verify package state after handling + RPackage* pkg = pkgLister->getPackage(pkgName); + CPPUNIT_ASSERT(pkg != nullptr); + CPPUNIT_ASSERT_EQUAL(pkgCache::State::ConfigFiles, pkg->state()); + } + private: std::filesystem::path testDir; RSourceManager* sourceManager; From e4c94519db2b5798f900b6b237d087a2a5662e65 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 5 May 2025 23:16:04 +0530 Subject: [PATCH 03/43] Another approach and shifting files appropriately after review --- common/rpackagemanager.cc | 209 +++++++++--- common/rpackagemanager.h | 174 ---------- common/rsource_deb822.cc | 252 ++++++++++++++ common/rsource_deb822.h | 41 +++ common/rsources.cc | 168 ++++++++-- common/rsources.h | 8 +- tests/test_deb822_integration.cc | 542 +++++++++++-------------------- 7 files changed, 794 insertions(+), 600 deletions(-) create mode 100644 common/rsource_deb822.cc create mode 100644 common/rsource_deb822.h diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc index 0bca26c65..9d51a547e 100644 --- a/common/rpackagemanager.cc +++ b/common/rpackagemanager.cc @@ -6,6 +6,24 @@ #include #include +// RPackageManager implementation +RPackageManager::RPackageManager(pkgPackageManager *pm) : pm(pm) {} + +pkgPackageManager::OrderResult RPackageManager::DoInstallPreFork() { + Res = pm->OrderInstall(); + return Res; +} + +#ifdef WITH_DPKG_STATUSFD +pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork(int statusFd) { + return (pm->Go(statusFd) == false) ? pkgPackageManager::Failed : Res; +} +#else +pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork() { + return (pm->Go() == false) ? pkgPackageManager::Failed : Res; +} +#endif + // RDeb822Source implementation RDeb822Source::RDeb822Source() : enabled(true) {} @@ -91,53 +109,51 @@ RDeb822Source RDeb822Source::fromString(const std::string& content) { RDeb822Source source; std::istringstream stream(content); std::string line; - bool inSource = false; - std::string currentKey; - std::string currentValue; + bool hasTypes = false; + bool hasUris = false; + bool hasSuites = false; + bool hasComponents = false; while (std::getline(stream, line)) { // Skip comments and empty lines if (line.empty() || line[0] == '#') { - if (inSource && !currentKey.empty()) { - // Process the last key-value pair - source.setField(currentKey, currentValue); - currentKey.clear(); - currentValue.clear(); - } - inSource = false; continue; } - // Check for disabled source - if (line[0] == '#') { - source.enabled = false; - line = line.substr(1); + // Parse key-value pairs + size_t colonPos = line.find(':'); + if (colonPos == std::string::npos) { + continue; } - // Parse key-value pair - size_t colonPos = line.find(':'); - if (colonPos != std::string::npos) { - if (inSource && !currentKey.empty()) { - // Process the previous key-value pair - source.setField(currentKey, currentValue); - } - currentKey = line.substr(0, colonPos); - currentValue = line.substr(colonPos + 1); - // Trim whitespace - currentKey.erase(0, currentKey.find_first_not_of(" \t")); - currentKey.erase(currentKey.find_last_not_of(" \t") + 1); - currentValue.erase(0, currentValue.find_first_not_of(" \t")); - currentValue.erase(currentValue.find_last_not_of(" \t") + 1); - inSource = true; - } else if (inSource && !currentValue.empty()) { - // Continuation of previous value - currentValue += "\n" + line; + std::string key = line.substr(0, colonPos); + std::string value = line.substr(colonPos + 1); + + // Trim whitespace + key = std::string(APT::String::Strip(key)); + value = std::string(APT::String::Strip(value)); + + if (key == "Types") { + source.types = value; + hasTypes = true; + } else if (key == "URIs") { + source.uris = value; + hasUris = true; + } else if (key == "Suites") { + source.suites = value; + hasSuites = true; + } else if (key == "Components") { + source.components = value; + hasComponents = true; + } else if (key == "Signed-By") { + source.signedBy = value; } } - // Process the last key-value pair - if (inSource && !currentKey.empty()) { - source.setField(currentKey, currentValue); + // Validate required fields + if (!hasTypes || !hasUris || !hasSuites || !hasComponents) { + std::cerr << "Warning: Missing required fields in source" << std::endl; + return RDeb822Source(); // Return invalid source } return source; @@ -212,13 +228,20 @@ bool RSourceManager::loadSources() { for (const auto& entry : std::filesystem::directory_iterator(sourcesDir)) { if (entry.path().extension() == ".sources") { std::cerr << "Loading source file: " << entry.path().string() << std::endl; - RDeb822Source source = readSourceFile(entry.path().string()); - if (source.isValid()) { - std::cerr << "Loaded valid source: " << source.getUris() << " " << source.getSuites() << std::endl; - sources.push_back(source); - } else { - std::cerr << "Warning: Invalid source file: " << entry.path().string() << std::endl; + + // Read the entire file content + std::ifstream file(entry.path()); + if (!file) { + std::cerr << "Error: Could not open file: " << entry.path().string() << std::endl; + continue; } + + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + // Parse sources from the file content + std::vector fileSources = parseSources(content); + sources.insert(sources.end(), fileSources.begin(), fileSources.end()); } } return true; @@ -228,6 +251,99 @@ bool RSourceManager::loadSources() { } } +std::vector RSourceManager::parseSources(const std::string& content) +{ + std::vector sources; + std::map currentFields; + + std::istringstream iss(content); + std::string line; + bool inSourceBlock = false; + + while (std::getline(iss, line)) { + // Trim whitespace + line = trim(line); + + // Skip empty lines + if (line.empty()) { + if (!currentFields.empty()) { + RDeb822Source source = createSourceFromFields(currentFields); + if (source.isValid()) { + sources.push_back(source); + } + currentFields.clear(); + } + continue; + } + + // Check for source block start + if (line.starts_with("#") && line.find("Modernized") != std::string::npos) { + if (!currentFields.empty()) { + RDeb822Source source = createSourceFromFields(currentFields); + if (source.isValid()) { + sources.push_back(source); + } + currentFields.clear(); + } + inSourceBlock = true; + continue; + } + + // Skip other comments + if (line.starts_with("#")) { + continue; + } + + // Parse key-value pairs + size_t colonPos = line.find(':'); + if (colonPos != std::string::npos) { + std::string key = trim(line.substr(0, colonPos)); + std::string value = trim(line.substr(colonPos + 1)); + + // Look ahead for continuation lines + while (std::getline(iss, line)) { + line = trim(line); + if (line.empty() || line.starts_with("#") || line.find(':') != std::string::npos) { + // Put the line back for the next iteration + iss.seekg(-static_cast(line.length() + 1), std::ios_base::cur); + break; + } + value += " " + line; + } + + currentFields[key] = value; + } + } + + // Handle the last source + if (!currentFields.empty()) { + RDeb822Source source = createSourceFromFields(currentFields); + if (source.isValid()) { + sources.push_back(source); + } + } + + return sources; +} + +RDeb822Source RSourceManager::createSourceFromFields(const std::map& fields) +{ + RDeb822Source source; + + // Set required fields + source.types = fields.count("Types") ? fields.at("Types") : ""; + source.uris = fields.count("URIs") ? fields.at("URIs") : ""; + source.suites = fields.count("Suites") ? fields.at("Suites") : ""; + source.components = fields.count("Components") ? fields.at("Components") : ""; + + // Set optional fields + if (fields.count("Signed-By")) { + source.signedBy = fields.at("Signed-By"); + } + + return source; +} + bool RSourceManager::saveSources() const { try { for (const auto& source : sources) { @@ -297,4 +413,15 @@ std::string RSourceManager::getSourceFilename(const RDeb822Source& source) const std::replace(filename.begin(), filename.end(), '/', '-'); filename += "-" + source.getSuites() + ".sources"; return (std::filesystem::path(sourcesDir) / filename).string(); +} + +std::string RSourceManager::trim(const std::string& str) const +{ + const std::string whitespace = " \t\r\n"; + size_t start = str.find_first_not_of(whitespace); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); } \ No newline at end of file diff --git a/common/rpackagemanager.h b/common/rpackagemanager.h index 0eb2fdb47..dc16b4bbd 100644 --- a/common/rpackagemanager.h +++ b/common/rpackagemanager.h @@ -77,180 +77,6 @@ class RPackageManager { }; -/** - * @class RDeb822Source - * @brief Represents a Deb822 format source entry - * - * This class handles the parsing, validation, and serialization of Deb822 format - * source entries. It supports multiple URIs and suites in a single source, as well - * as source enabling/disabling. - * - * Example usage: - * @code - * RDeb822Source source; - * source.setTypes("deb"); - * source.setUris("http://example.com"); - * source.setSuites("stable"); - * source.setComponents("main"); - * if (source.isValid()) { - * // Use the source - * } - * @endcode - */ -class RDeb822Source { -public: - RDeb822Source(); - RDeb822Source(const std::string& types, const std::string& uris, - const std::string& suites, const std::string& components = ""); - - // Getters - std::string getTypes() const { return types; } - std::string getUris() const { return uris; } - std::string getSuites() const { return suites; } - std::string getComponents() const { return components; } - bool isEnabled() const { return enabled; } - - // Setters - void setTypes(const std::string& t) { types = t; } - void setUris(const std::string& u) { uris = u; } - void setSuites(const std::string& s) { suites = s; } - void setComponents(const std::string& c) { components = c; } - void setEnabled(bool e) { enabled = e; } - - /** - * @brief Validates the source entry - * @return true if the source is valid, false otherwise - * - * Checks: - * - Required fields (types, uris, suites) are not empty - * - Type is either "deb" or "deb-src" - * - At least one URI has a valid scheme - * - At least one suite is specified - */ - bool isValid() const; - - /** - * @brief Converts the source to its string representation - * @return String representation of the source - */ - std::string toString() const; - - /** - * @brief Creates a source from its string representation - * @param content The string content to parse - * @return A new RDeb822Source instance - */ - static RDeb822Source fromString(const std::string& content); - - // Comparison operators - bool operator==(const RDeb822Source& other) const; - bool operator!=(const RDeb822Source& other) const; - -private: - std::string types; - std::string uris; - std::string suites; - std::string components; - bool enabled; -}; - -/** - * @class RSourceManager - * @brief Manages Deb822 format source files - * - * This class handles the management of source files in the sources.list.d - * directory, including reading, writing, and validating source files. - * - * Example usage: - * @code - * RSourceManager manager("/etc/apt/sources.list.d"); - * RDeb822Source source = manager.readSourceFile("example.sources"); - * if (source.isValid()) { - * manager.addSource(source); - * manager.saveSources(); - * } - * @endcode - */ -class RSourceManager { -public: - RSourceManager(); - explicit RSourceManager(const std::string& sourcesDir); - - /** - * @brief Adds a new source - * @param source The source to add - * @return true if added successfully, false if invalid or duplicate - */ - bool addSource(const RDeb822Source& source); - - /** - * @brief Removes a source - * @param source The source to remove - * @return true if removed successfully, false if not found - */ - bool removeSource(const RDeb822Source& source); - - /** - * @brief Updates an existing source - * @param oldSource The source to update - * @param newSource The new source data - * @return true if updated successfully, false if invalid or not found - */ - bool updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource); - - /** - * @brief Gets all managed sources - * @return Vector of sources - */ - std::vector getSources() const; - - /** - * @brief Loads sources from the sources directory - * @return true if loaded successfully - */ - bool loadSources(); - - /** - * @brief Saves all sources to files - * @return true if saved successfully - */ - bool saveSources() const; - - /** - * @brief Writes a source to a file - * @param filename The target file - * @param source The source to write - * @return true if written successfully - */ - bool writeSourceFile(const std::string& filename, const RDeb822Source& source) const; - - /** - * @brief Reads a source from a file - * @param filename The source file - * @return The read source - */ - RDeb822Source readSourceFile(const std::string& filename) const; - - /** - * @brief Updates APT sources - * @return true if updated successfully - */ - bool updateAptSources(); - - /** - * @brief Reloads the APT cache - * @return true if reloaded successfully - */ - bool reloadAptCache(); - -private: - std::vector sources; - std::string sourcesDir; - - bool validateSourceFile(const std::string& filename) const; - std::string getSourceFilename(const RDeb822Source& source) const; -}; - #endif // vim:ts=3:sw=3:et diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc new file mode 100644 index 000000000..f06e46c2d --- /dev/null +++ b/common/rsource_deb822.cc @@ -0,0 +1,252 @@ +/* rsource_deb822.cc - Deb822 format sources support + * + * Copyright (c) 2025 Synaptic development team + */ + +#include "rsource_deb822.h" +#include +#include +#include +#include +#include +#include +#include "i18n.h" + +bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { + std::ifstream file(path.c_str()); + if (!file) { + return _error->Error(_("Cannot open %s"), path.c_str()); + } + + std::map fields; + while (ParseStanza(file, fields)) { + Deb822Entry entry; + + // Required fields + auto types_it = fields.find("Types"); + if (types_it == fields.end()) { + return _error->Error(_("Missing Types field in %s"), path.c_str()); + } + entry.Types = types_it->second; + + auto uris_it = fields.find("URIs"); + if (uris_it == fields.end()) { + return _error->Error(_("Missing URIs field in %s"), path.c_str()); + } + entry.URIs = uris_it->second; + + auto suites_it = fields.find("Suites"); + if (suites_it == fields.end()) { + return _error->Error(_("Missing Suites field in %s"), path.c_str()); + } + entry.Suites = suites_it->second; + + // Optional fields + entry.Components = fields["Components"]; + entry.SignedBy = fields["Signed-By"]; + + // Handle enabled/disabled state + auto enabled_it = fields.find("Enabled"); + entry.Enabled = (enabled_it == fields.end() || enabled_it->second != "no"); + + // Store any comment lines + auto comment_it = fields.find("#"); + if (comment_it != fields.end()) { + entry.Comment = comment_it->second; + } + + entries.push_back(entry); + fields.clear(); + } + + return true; +} + +bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector& entries) { + std::ofstream file(path.c_str()); + if (!file) { + return _error->Error(_("Cannot write to %s"), path.c_str()); + } + + for (const auto& entry : entries) { + if (!entry.Comment.empty()) { + file << entry.Comment << "\n"; + } + + file << "Types: " << entry.Types << "\n"; + file << "URIs: " << entry.URIs << "\n"; + file << "Suites: " << entry.Suites << "\n"; + + if (!entry.Components.empty()) { + file << "Components: " << entry.Components << "\n"; + } + + if (!entry.SignedBy.empty()) { + file << "Signed-By: " << entry.SignedBy << "\n"; + } + + if (!entry.Enabled) { + file << "Enabled: no\n"; + } + + file << "\n"; // Empty line between stanzas + } + + return true; +} + +bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record) { + std::istringstream types(entry.Types); + std::string type; + bool has_deb = false, has_deb_src = false; + + while (types >> type) { + if (type == "deb") has_deb = true; + else if (type == "deb-src") has_deb_src = true; + } + + record.Type = 0; + if (has_deb) record.Type |= SourcesList::Deb; + if (has_deb_src) record.Type |= SourcesList::DebSrc; + if (!entry.Enabled) record.Type |= SourcesList::Disabled; + + // Split URIs into vector + std::istringstream uri_stream(entry.URIs); + std::string uri; + uri_stream >> uri; // Take first URI + record.URI = uri; + + // Split Suites into vector + std::istringstream suite_stream(entry.Suites); + std::string suite; + suite_stream >> suite; // Take first Suite + record.Dist = suite; + + // Handle Components + if (!entry.Components.empty()) { + std::istringstream comp_stream(entry.Components); + std::vector components; + std::string comp; + while (comp_stream >> comp) { + components.push_back(comp); + } + + record.NumSections = components.size(); + record.Sections = new std::string[record.NumSections]; + for (size_t i = 0; i < components.size(); i++) { + record.Sections[i] = components[i]; + } + } + + record.Comment = entry.Comment; + + return true; +} + +bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) { + std::string types; + if (record.Type & SourcesList::Deb) { + types += "deb "; + } + if (record.Type & SourcesList::DebSrc) { + types += "deb-src "; + } + TrimWhitespace(types); + entry.Types = types; + + entry.URIs = record.URI; + entry.Suites = record.Dist; + + std::string components; + for (unsigned short i = 0; i < record.NumSections; i++) { + components += record.Sections[i] + " "; + } + TrimWhitespace(components); + entry.Components = components; + + entry.Enabled = !(record.Type & SourcesList::Disabled); + entry.Comment = record.Comment; + + return true; +} + +bool RDeb822Source::ParseStanza(std::istream& input, std::map& fields) { + std::string line; + std::string current_field; + std::string current_value; + bool in_stanza = false; + + while (std::getline(input, line)) { + TrimWhitespace(line); + + // Skip empty lines between stanzas + if (line.empty()) { + if (in_stanza) { + // End of stanza + if (!current_field.empty()) { + TrimWhitespace(current_value); + fields[current_field] = current_value; + } + return true; + } + continue; + } + + // Handle comments + if (line[0] == '#') { + if (!in_stanza) { + // Comment before stanza belongs to next stanza + fields["#"] = fields["#"].empty() ? line : fields["#"] + "\n" + line; + } + continue; + } + + in_stanza = true; + + // Handle field continuation + if (line[0] == ' ' || line[0] == '\t') { + if (!current_field.empty()) { + current_value += "\n" + line; + } + continue; + } + + // New field + if (!current_field.empty()) { + TrimWhitespace(current_value); + fields[current_field] = current_value; + } + + size_t colon = line.find(':'); + if (colon == std::string::npos) { + return _error->Error(_("Invalid line format: %s"), line.c_str()); + } + + current_field = line.substr(0, colon); + TrimWhitespace(current_field); + current_value = line.substr(colon + 1); + TrimWhitespace(current_value); + } + + // Handle last field of last stanza + if (in_stanza && !current_field.empty()) { + TrimWhitespace(current_value); + fields[current_field] = current_value; + return true; + } + + return false; +} + +void RDeb822Source::TrimWhitespace(std::string& str) { + // Trim leading whitespace + size_t start = str.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + str.clear(); + return; + } + + // Trim trailing whitespace + size_t end = str.find_last_not_of(" \t\r\n"); + str = str.substr(start, end - start + 1); +} \ No newline at end of file diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h new file mode 100644 index 000000000..7564b951d --- /dev/null +++ b/common/rsource_deb822.h @@ -0,0 +1,41 @@ +/* rsource_deb822.h - Deb822 format sources support + * + * Copyright (c) 2025 Synaptic development team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + */ + +#ifndef _RSOURCE_DEB822_H +#define _RSOURCE_DEB822_H + +#include "rsources.h" +#include +#include +#include + +class RDeb822Source { +public: + struct Deb822Entry { + std::string Types; // Can be "deb" and/or "deb-src" + std::string URIs; // Space-separated list of URIs + std::string Suites; // Space-separated list of suites + std::string Components; // Space-separated list of components + std::string SignedBy; // Path to keyring file + bool Enabled; // Whether the source is enabled + std::string Comment; // Any comments associated with this entry + }; + + static bool ParseDeb822File(const std::string& path, std::vector& entries); + static bool WriteDeb822File(const std::string& path, const std::vector& entries); + static bool ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record); + static bool ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry); + +private: + static bool ParseStanza(std::istream& input, std::map& fields); + static void TrimWhitespace(std::string& str); +}; + +#endif // _RSOURCE_DEB822_H \ No newline at end of file diff --git a/common/rsources.cc b/common/rsources.cc index 18d7ad9b4..a31348dd7 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -36,6 +36,7 @@ #include #include "config.h" #include "i18n.h" +#include "rsource_deb822.h" SourcesList::~SourcesList() { @@ -217,10 +218,9 @@ bool SourcesList::ReadSourceDir(string Dir) bool SourcesList::ReadSources() { - //cout << "SourcesList::ReadSources() " << endl; - bool Res = true; + // Read classic sources.list format string Parts = _config->FindDir("Dir::Etc::sourceparts"); if (FileExists(Parts) == true) Res &= ReadSourceDir(Parts); @@ -228,6 +228,14 @@ bool SourcesList::ReadSources() if (FileExists(Main) == true) Res &= ReadSourcePart(Main); + // Read Deb822 format sources + string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); + if (FileExists(Deb822Parts) == true) + Res &= ReadDeb822SourceDir(Deb822Parts); + string Deb822Main = _config->FindFile("Dir::Etc::sourcelist.d") + "/debian.sources"; + if (FileExists(Deb822Main) == true) + Res &= ReadDeb822SourcePart(Deb822Main); + return Res; } @@ -291,50 +299,69 @@ void SourcesList::SwapSources( SourceRecord *&rec_one, SourceRecord *&rec_two ) bool SourcesList::UpdateSources() { - list filenames; - for (list::iterator it = SourceRecords.begin(); + ofstream ofs(_config->FindFile("Dir::Etc::sourcelist").c_str(), ios::out); + if (!ofs != 0) + return _error->Error(_("Error writing sources list")); + + // Group sources by their source file + map> sourcesByFile; + for (list::const_iterator it = SourceRecords.begin(); it != SourceRecords.end(); it++) { - if ((*it)->SourceFile == "") - continue; - filenames.push_front((*it)->SourceFile); + sourcesByFile[(*it)->SourceFile].push_back(*it); } - filenames.sort(); - filenames.unique(); - for (list::iterator fi = filenames.begin(); - fi != filenames.end(); fi++) { - ofstream ofs((*fi).c_str(), ios::out); - if (!ofs != 0) - return false; + // Write each source file + for (const auto& pair : sourcesByFile) { + const string& sourcePath = pair.first; + const vector& records = pair.second; - for (list::iterator it = SourceRecords.begin(); - it != SourceRecords.end(); it++) { - if ((*fi) != (*it)->SourceFile) - continue; - string S; - if (((*it)->Type & Comment) != 0) { - S = (*it)->Comment; - } else if ((*it)->URI.empty() || (*it)->Dist.empty()) { - continue; - } else { - if (((*it)->Type & Disabled) != 0) - S = "# "; - - S += (*it)->GetType() + " "; + // Skip empty source files + if (records.empty()) { + continue; + } - if ((*it)->VendorID.empty() == false) - S += "[" + (*it)->VendorID + "] "; + // Check if this is a Deb822 format file + bool isDeb822 = false; + if (!records.empty() && (records[0]->Type & Deb822)) { + isDeb822 = true; + } - S += (*it)->URI + " "; - S += (*it)->Dist + " "; + // Open the appropriate file for writing + ofstream out(sourcePath.c_str(), ios::out); + if (!out) { + return _error->Error(_("Error writing to %s"), sourcePath.c_str()); + } - for (unsigned int J = 0; J < (*it)->NumSections; J++) - S += (*it)->Sections[J] + " "; + if (isDeb822) { + // Write Deb822 format + vector entries; + for (const auto& record : records) { + RDeb822Source::Deb822Entry entry; + if (!RDeb822Source::ConvertFromSourceRecord(*record, entry)) { + return _error->Error(_("Failed to convert source record to Deb822 format")); + } + entries.push_back(entry); + } + if (!RDeb822Source::WriteDeb822File(sourcePath, entries)) { + return false; } - ofs << S << endl; + } else { + // Write classic format + for (const auto& record : records) { + if (record->Type == Comment) { + out << record->Comment << endl; + } else { + out << *record; + } + } + } + + out.close(); + if (!out) { + return _error->Error(_("Error writing to %s"), sourcePath.c_str()); } - ofs.close(); } + return true; } @@ -560,4 +587,73 @@ ostream &operator<<(ostream &os, const SourcesList::VendorRecord &rec) return os; } +bool SourcesList::ReadDeb822SourcePart(string listpath) { + vector entries; + if (!RDeb822Source::ParseDeb822File(listpath, entries)) { + return false; + } + + for (const auto& entry : entries) { + SourceRecord rec; + rec.SourceFile = listpath; + + if (!RDeb822Source::ConvertToSourceRecord(entry, rec)) { + return _error->Error(_("Failed to convert Deb822 entry in %s"), listpath.c_str()); + } + + rec.Type |= Deb822; // Mark as Deb822 format + AddSourceNode(rec); + } + + return true; +} + +bool SourcesList::ReadDeb822SourceDir(string Dir) { + DIR *D = opendir(Dir.c_str()); + if (D == 0) + return _error->Errno("opendir", _("Unable to read %s"), Dir.c_str()); + + vector List; + for (struct dirent * Ent = readdir(D); Ent != 0; Ent = readdir(D)) { + if (Ent->d_name[0] == '.') + continue; + + // Only look at files ending in .sources + if (strcmp(Ent->d_name + strlen(Ent->d_name) - 8, ".sources") != 0) + continue; + + // Make sure it is a file and not something else + string File = flCombine(Dir, Ent->d_name); + struct stat St; + if (stat(File.c_str(), &St) != 0 || S_ISREG(St.st_mode) == 0) + continue; + List.push_back(File); + } + closedir(D); + + sort(List.begin(), List.end()); + + // Read the files + for (vector::const_iterator I = List.begin(); I != List.end(); I++) + if (ReadDeb822SourcePart(*I) == false) + return false; + return true; +} + +bool SourcesList::WriteDeb822Source(SourceRecord *record, string path) { + if (!record || !(record->Type & Deb822)) { + return _error->Error(_("Not a Deb822 format source")); + } + + vector entries; + RDeb822Source::Deb822Entry entry; + + if (!RDeb822Source::ConvertFromSourceRecord(*record, entry)) { + return _error->Error(_("Failed to convert source record to Deb822 format")); + } + + entries.push_back(entry); + return RDeb822Source::WriteDeb822File(path, entries); +} + // vim:sts=4:sw=4 diff --git a/common/rsources.h b/common/rsources.h index b2e16d49c..cd47859c0 100644 --- a/common/rsources.h +++ b/common/rsources.h @@ -43,7 +43,8 @@ class SourcesList { RpmDir = 1 << 6, RpmSrcDir = 1 << 7, Repomd = 1 << 8, - RepomdSrc = 1 << 9 + RepomdSrc = 1 << 9, + Deb822 = 1 << 10 // New type for Deb822 format }; struct SourceRecord { @@ -97,6 +98,11 @@ class SourcesList { bool ReadSources(); bool UpdateSources(); + // New methods for Deb822 support + bool ReadDeb822SourcePart(string listpath); + bool ReadDeb822SourceDir(string Dir); + bool WriteDeb822Source(SourceRecord *record, string path); + VendorRecord *AddVendor(string VendorID, string FingerPrint, string Description); void RemoveVendor(VendorRecord *&); diff --git a/tests/test_deb822_integration.cc b/tests/test_deb822_integration.cc index 04af28905..b6e89bbf0 100644 --- a/tests/test_deb822_integration.cc +++ b/tests/test_deb822_integration.cc @@ -1,366 +1,212 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include "../common/rsources.h" +#include "../common/rsource_deb822.h" +#include +#include +#include +#include -#include "common/rpackagemanager.h" -#include "gtk/rgrepositorywindow.h" -#include - -class TestDeb822Integration : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(TestDeb822Integration); - CPPUNIT_TEST(testSourceFileOperations); - CPPUNIT_TEST(testAptIntegration); - CPPUNIT_TEST(testSourceListRefresh); - CPPUNIT_TEST(testSourceValidation); - CPPUNIT_TEST(testSourceParsing); - CPPUNIT_TEST(testBasicParsing); - CPPUNIT_TEST(testMultipleSources); - CPPUNIT_TEST(testSourceManager); - CPPUNIT_TEST(testInvalidSourceFormat); - CPPUNIT_TEST(testFailedInstallationHandling); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() override { - // Create temporary test directory - testDir = std::filesystem::temp_directory_path() / "synaptic_test"; - std::filesystem::create_directories(testDir); - - // Initialize source manager with test directory - sourceManager = new RSourceManager(testDir.string()); - } - - void tearDown() override { - delete sourceManager; - std::filesystem::remove_all(testDir); - } - - void testSourceFileOperations() { - // Test valid source - RDeb822Source source; - source.setTypes("deb"); - source.setUris("http://example.com"); - source.setSuites("stable"); - source.setComponents("main"); - - // Write source to file - std::string filename = testDir / "test.sources"; - CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); - - // Read source from file - RDeb822Source readSource = sourceManager->readSourceFile(filename.string()); - CPPUNIT_ASSERT(readSource.isValid()); - CPPUNIT_ASSERT_EQUAL(std::string("deb"), readSource.getTypes()); - CPPUNIT_ASSERT_EQUAL(std::string("http://example.com"), readSource.getUris()); - CPPUNIT_ASSERT_EQUAL(std::string("stable"), readSource.getSuites()); - CPPUNIT_ASSERT_EQUAL(std::string("main"), readSource.getComponents()); - - // Test disabled source - source.setEnabled(false); - CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); - readSource = sourceManager->readSourceFile(filename.string()); - CPPUNIT_ASSERT(!readSource.isEnabled()); - - // Test multiple URIs and suites - source.setEnabled(true); - source.setUris("http://example.com ftp://mirror.example.com"); - source.setSuites("stable testing"); - CPPUNIT_ASSERT(sourceManager->writeSourceFile(filename.string(), source)); - readSource = sourceManager->readSourceFile(filename.string()); - CPPUNIT_ASSERT(readSource.isValid()); - CPPUNIT_ASSERT_EQUAL(std::string("http://example.com ftp://mirror.example.com"), - readSource.getUris()); - CPPUNIT_ASSERT_EQUAL(std::string("stable testing"), readSource.getSuites()); +class Deb822Test : public ::testing::Test { +protected: + void SetUp() override { + // Create a temporary file for testing + char tmpname[] = "/tmp/synaptic_test_XXXXXX"; + int fd = mkstemp(tmpname); + ASSERT_NE(fd, -1); + close(fd); + testFile = tmpname; } - - void testAptIntegration() { - // Add a source - RDeb822Source source; - source.setTypes("deb"); - source.setUris("http://example.com"); - source.setSuites("stable"); - source.setComponents("main"); - - CPPUNIT_ASSERT(sourceManager->addSource(source)); - CPPUNIT_ASSERT(sourceManager->saveSources()); - - // Update APT sources - CPPUNIT_ASSERT(sourceManager->updateAptSources()); - - // Reload APT cache - CPPUNIT_ASSERT(sourceManager->reloadAptCache()); - } - - void testSourceListRefresh() { - // Create repository window - RGRepositoryWindow* window = new RGRepositoryWindow(); - - // Add a source - RDeb822Source source; - source.setTypes("deb"); - source.setUris("http://example.com"); - source.setSuites("stable"); - source.setComponents("main"); - - CPPUNIT_ASSERT(sourceManager->addSource(source)); - CPPUNIT_ASSERT(sourceManager->saveSources()); - - // Refresh source list - window->refreshSourceList(); - - // Verify source is in list - GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(window->getSourceList())); - GtkTreeIter iter; - bool found = false; - - if (gtk_tree_model_get_iter_first(model, &iter)) { - do { - gchar *type, *uri, *suite, *components; - gtk_tree_model_get(model, &iter, - 0, &type, - 1, &uri, - 2, &suite, - 3, &components, - -1); - - if (std::string(type) == "deb" && - std::string(uri) == "http://example.com" && - std::string(suite) == "stable" && - std::string(components) == "main") { - found = true; - } - - g_free(type); - g_free(uri); - g_free(suite); - g_free(components); - } while (gtk_tree_model_iter_next(model, &iter)); + + void TearDown() override { + if (!testFile.empty()) { + remove(testFile.c_str()); } - - CPPUNIT_ASSERT(found); - delete window; } + + std::string testFile; +}; + +TEST_F(Deb822Test, ParseSimpleDeb822Source) { + // Write a test source file + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "Types: deb\n" + << "URIs: http://deb.debian.org/debian\n" + << "Suites: trixie\n" + << "Components: main contrib\n" + << "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\n"; + ofs.close(); + + std::vector entries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(testFile, entries)); + ASSERT_EQ(entries.size(), 1); - void testSourceValidation() { - RDeb822Source source; - - // Test valid sources - source.setTypes("deb"); - source.setUris("http://example.com"); - source.setSuites("stable"); - CPPUNIT_ASSERT(source.isValid()); - - source.setTypes("deb-src"); - source.setUris("https://example.com"); - source.setSuites("testing"); - CPPUNIT_ASSERT(source.isValid()); - - source.setUris("ftp://example.com"); - CPPUNIT_ASSERT(source.isValid()); - - source.setUris("file:///media/cdrom"); - CPPUNIT_ASSERT(source.isValid()); - - source.setUris("cdrom:"); - CPPUNIT_ASSERT(source.isValid()); - - // Test invalid sources - source.setTypes("invalid"); - CPPUNIT_ASSERT(!source.isValid()); - - source.setTypes("deb"); - source.setUris("invalid://example.com"); - CPPUNIT_ASSERT(!source.isValid()); - - source.setUris(""); - CPPUNIT_ASSERT(!source.isValid()); - - source.setUris("http://example.com"); - source.setSuites(""); - CPPUNIT_ASSERT(!source.isValid()); - } + const auto& entry = entries[0]; + EXPECT_EQ(entry.Types, "deb"); + EXPECT_EQ(entry.URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(entry.Suites, "trixie"); + EXPECT_EQ(entry.Components, "main contrib"); + EXPECT_EQ(entry.SignedBy, "/usr/share/keyrings/debian-archive-keyring.gpg"); + EXPECT_TRUE(entry.Enabled); +} + +TEST_F(Deb822Test, ParseMultipleEntries) { + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "Types: deb\n" + << "URIs: http://security.debian.org/debian-security\n" + << "Suites: trixie-security\n" + << "Components: main\n\n" + << "Types: deb deb-src\n" + << "URIs: http://deb.debian.org/debian\n" + << "Suites: trixie trixie-updates\n" + << "Components: main contrib non-free\n" + << "Enabled: no\n\n"; + ofs.close(); + + std::vector entries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(testFile, entries)); + ASSERT_EQ(entries.size(), 2); - void testSourceParsing() { - // Test single line format - std::string content = "deb http://example.com stable main"; - RDeb822Source source = RDeb822Source::fromString(content); - CPPUNIT_ASSERT(source.isValid()); - CPPUNIT_ASSERT_EQUAL(std::string("deb"), source.getTypes()); - CPPUNIT_ASSERT_EQUAL(std::string("http://example.com"), source.getUris()); - CPPUNIT_ASSERT_EQUAL(std::string("stable"), source.getSuites()); - CPPUNIT_ASSERT_EQUAL(std::string("main"), source.getComponents()); - - // Test disabled source - content = "# Disabled: deb http://example.com stable main"; - source = RDeb822Source::fromString(content); - CPPUNIT_ASSERT(!source.isEnabled()); - - // Test multiple URIs and suites - content = "deb http://example.com ftp://mirror.example.com stable testing main contrib"; - source = RDeb822Source::fromString(content); - CPPUNIT_ASSERT(source.isValid()); - CPPUNIT_ASSERT_EQUAL(std::string("http://example.com ftp://mirror.example.com"), - source.getUris()); - CPPUNIT_ASSERT_EQUAL(std::string("stable testing"), source.getSuites()); - CPPUNIT_ASSERT_EQUAL(std::string("main contrib"), source.getComponents()); - - // Test with comments - content = "# This is a comment\n" - "# Another comment\n" - "deb http://example.com stable main\n" - "# Trailing comment"; - source = RDeb822Source::fromString(content); - CPPUNIT_ASSERT(source.isValid()); - CPPUNIT_ASSERT_EQUAL(std::string("deb"), source.getTypes()); - } + EXPECT_EQ(entries[0].Types, "deb"); + EXPECT_TRUE(entries[0].Enabled); + + EXPECT_EQ(entries[1].Types, "deb deb-src"); + EXPECT_FALSE(entries[1].Enabled); +} - void testBasicParsing() { - std::string content = - "Types: deb deb-src\n" - "URIs: http://ftp.de.debian.org/debian/\n" - "Suites: trixie\n" - "Components: main non-free-firmware\n" - "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n"; +TEST_F(Deb822Test, ConvertBetweenFormats) { + // Create a Deb822 entry + RDeb822Source::Deb822Entry deb822Entry; + deb822Entry.Types = "deb deb-src"; + deb822Entry.URIs = "http://deb.debian.org/debian"; + deb822Entry.Suites = "trixie"; + deb822Entry.Components = "main contrib"; + deb822Entry.SignedBy = "/usr/share/keyrings/debian-archive-keyring.gpg"; + deb822Entry.Enabled = true; - RDeb822Source source = RDeb822Source::fromString(content); - - CPPUNIT_ASSERT(source.getTypes() == "deb deb-src"); - CPPUNIT_ASSERT(source.getUris() == "http://ftp.de.debian.org/debian/"); - CPPUNIT_ASSERT(source.getSuites() == "trixie"); - CPPUNIT_ASSERT(source.getComponents() == "main non-free-firmware"); - CPPUNIT_ASSERT(source.isEnabled()); - CPPUNIT_ASSERT(source.isValid()); - } + // Convert to SourceRecord + SourcesList::SourceRecord sourceRecord; + ASSERT_TRUE(RDeb822Source::ConvertToSourceRecord(deb822Entry, sourceRecord)); - void testMultipleSources() { - std::string multiContent = - "Types: deb deb-src\n" - "URIs: http://ftp.de.debian.org/debian/\n" - "Suites: trixie\n" - "Components: main non-free-firmware\n" - "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\n" - "Types: deb deb-src\n" - "URIs: http://security.debian.org/debian-security/\n" - "Suites: trixie-security\n" - "Components: main non-free-firmware\n" - "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n"; - - std::istringstream iss(multiContent); - std::string sourceContent; - std::string line; - int sourceCount = 0; - - while (std::getline(iss, line)) { - if (line.empty() && !sourceContent.empty()) { - RDeb822Source src = RDeb822Source::fromString(sourceContent); - if (src.isValid()) { - sourceCount++; - std::cout << "Found source " << sourceCount << ": " - << src.getUris() << " (" << src.getSuites() << ")" << std::endl; - } - sourceContent.clear(); - } else { - sourceContent += line + "\n"; - } - } - - if (!sourceContent.empty()) { - RDeb822Source src = RDeb822Source::fromString(sourceContent); - if (src.isValid()) { - sourceCount++; - std::cout << "Found source " << sourceCount << ": " - << src.getUris() << " (" << src.getSuites() << ")" << std::endl; - } - } - - CPPUNIT_ASSERT_EQUAL(2, sourceCount); - } + // Verify conversion + EXPECT_TRUE(sourceRecord.Type & SourcesList::Deb); + EXPECT_TRUE(sourceRecord.Type & SourcesList::DebSrc); + EXPECT_EQ(sourceRecord.URI, "http://deb.debian.org/debian"); + EXPECT_EQ(sourceRecord.Dist, "trixie"); + ASSERT_EQ(sourceRecord.NumSections, 2); + EXPECT_EQ(sourceRecord.Sections[0], "main"); + EXPECT_EQ(sourceRecord.Sections[1], "contrib"); - void testSourceManager() { - RSourceManager manager("/etc/apt/sources.list.d"); - - // Test adding source - RDeb822Source source; - source.setTypes("deb"); - source.setUris("http://example.com"); - source.setSuites("stable"); - source.setComponents("main"); - - CPPUNIT_ASSERT(manager.addSource(source)); - CPPUNIT_ASSERT_EQUAL(1, (int)manager.getSources().size()); - - // Test removing source - CPPUNIT_ASSERT(manager.removeSource(source)); - CPPUNIT_ASSERT_EQUAL(0, (int)manager.getSources().size()); - } + // Convert back to Deb822 + RDeb822Source::Deb822Entry convertedEntry; + ASSERT_TRUE(RDeb822Source::ConvertFromSourceRecord(sourceRecord, convertedEntry)); - void testInvalidSourceFormat() { - // Test with missing required fields - std::string invalidSource = "Types: deb\nURIs: http://example.com\n"; - RDeb822Source source = RDeb822Source::fromString(invalidSource); - CPPUNIT_ASSERT(!source.isValid()); + // Verify round-trip conversion + EXPECT_EQ(convertedEntry.Types, "deb deb-src"); + EXPECT_EQ(convertedEntry.URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(convertedEntry.Suites, "trixie"); + EXPECT_EQ(convertedEntry.Components, "main contrib"); + EXPECT_TRUE(convertedEntry.Enabled); +} - // Test with invalid URI - std::string invalidUri = "Types: deb\nURIs: invalid://example.com\nSuites: stable\n"; - source = RDeb822Source::fromString(invalidUri); - CPPUNIT_ASSERT(!source.isValid()); +TEST_F(Deb822Test, WriteAndReadBack) { + // Create test entries + std::vector entries; + RDeb822Source::Deb822Entry entry1; + entry1.Types = "deb"; + entry1.URIs = "http://example.com/debian"; + entry1.Suites = "stable"; + entry1.Components = "main"; + entry1.Enabled = true; + entries.push_back(entry1); - // Test with empty values - std::string emptyValues = "Types: \nURIs: \nSuites: \n"; - source = RDeb822Source::fromString(emptyValues); - CPPUNIT_ASSERT(!source.isValid()); - } + // Write to file + ASSERT_TRUE(RDeb822Source::WriteDeb822File(testFile, entries)); - void testFailedInstallationHandling() { - // Test handling of failed installation - std::string pkgName = "test-package"; - CPPUNIT_ASSERT(pkgLister->handleFailedInstallation(pkgName)); - - // Verify package state after handling - RPackage* pkg = pkgLister->getPackage(pkgName); - CPPUNIT_ASSERT(pkg != nullptr); - CPPUNIT_ASSERT_EQUAL(pkgCache::State::ConfigFiles, pkg->state()); - } + // Read back + std::vector readEntries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(testFile, readEntries)); -private: - std::filesystem::path testDir; - RSourceManager* sourceManager; -}; + // Compare + ASSERT_EQ(readEntries.size(), entries.size()); + EXPECT_EQ(readEntries[0].Types, entries[0].Types); + EXPECT_EQ(readEntries[0].URIs, entries[0].URIs); + EXPECT_EQ(readEntries[0].Suites, entries[0].Suites); + EXPECT_EQ(readEntries[0].Components, entries[0].Components); + EXPECT_EQ(readEntries[0].Enabled, entries[0].Enabled); +} -CPPUNIT_TEST_SUITE_REGISTRATION(TestDeb822Integration); +TEST_F(Deb822Test, HandleComments) { + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "# This is a comment\n" + << "Types: deb\n" + << "URIs: http://example.com/debian\n" + << "# Another comment\n" + << "Suites: stable\n" + << "Components: main\n\n"; + ofs.close(); -int main(int argc, char* argv[]) { - // Initialize GTK - gtk_init(&argc, &argv); - - // Create test runner - CppUnit::TestResultCollector result; - CppUnit::TestRunner runner; - runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); - - // Add listener - CppUnit::BriefTestProgressListener progress; - runner.eventManager().addListener(&progress); - runner.eventManager().addListener(&result); - - // Run tests - runner.run(); - - // Output results - CppUnit::CompilerOutputter outputter(&result, std::cerr); - outputter.write(); - - // Clean up GTK - gtk_main_quit(); - - return result.wasSuccessful() ? 0 : 1; + std::vector entries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(testFile, entries)); + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries[0].Comment, "# This is a comment\n# Another comment"); +} + +TEST_F(Deb822Test, HandleInvalidFile) { + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "Types: deb\n" + << "URIs: http://example.com/debian\n" + << "# Missing Suites field\n" + << "Components: main\n\n"; + ofs.close(); + + std::vector entries; + EXPECT_FALSE(RDeb822Source::ParseDeb822File(testFile, entries)); +} + +TEST_F(Deb822Test, SourcesListIntegration) { + // Write a test Deb822 source file + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "Types: deb deb-src\n" + << "URIs: http://deb.debian.org/debian\n" + << "Suites: trixie\n" + << "Components: main contrib\n" + << "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\n"; + ofs.close(); + + // Create SourcesList and read the file + SourcesList sources; + ASSERT_TRUE(sources.ReadDeb822SourcePart(testFile)); + + // Verify the source was read correctly + ASSERT_FALSE(sources.SourceRecords.empty()); + auto record = sources.SourceRecords.front(); + EXPECT_TRUE(record->Type & SourcesList::Deb822); + EXPECT_TRUE(record->Type & SourcesList::Deb); + EXPECT_TRUE(record->Type & SourcesList::DebSrc); + EXPECT_EQ(record->URI, "http://deb.debian.org/debian"); + EXPECT_EQ(record->Dist, "trixie"); + ASSERT_EQ(record->NumSections, 2); + EXPECT_EQ(record->Sections[0], "main"); + EXPECT_EQ(record->Sections[1], "contrib"); + + // Write back to a new file + std::string newFile = testFile + ".new"; + ASSERT_TRUE(sources.WriteDeb822Source(record, newFile)); + + // Read the new file and verify contents + std::vector entries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(newFile, entries)); + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries[0].Types, "deb deb-src"); + EXPECT_EQ(entries[0].URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(entries[0].Suites, "trixie"); + EXPECT_EQ(entries[0].Components, "main contrib"); + + // Clean up + remove(newFile.c_str()); } \ No newline at end of file From 82181b5d8fbdd7448ccc992d331ee88072eb5d10 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 6 May 2025 09:33:36 +0530 Subject: [PATCH 04/43] add rsource_deb822.cc into POTFILES.in --- po/POTFILES.in | 1 + 1 file changed, 1 insertion(+) diff --git a/po/POTFILES.in b/po/POTFILES.in index 0f3ec8df9..f94466254 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,7 @@ common/rpmindexcopy.cc common/rpackageview.h common/rpackageview.cc common/rsources.cc +common/rsource_deb822.cc gtk/gsynaptic.cc gtk/rgcdscanner.cc gtk/rgcacheprogress.cc From 9184ec73286317eecdc9b9d1cf4f02d0cd286408 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Wed, 7 May 2025 12:07:24 +0530 Subject: [PATCH 05/43] Add Deb822 sources support and remove handleFailedInstallation function --- common/Makefile.am | 4 ++-- common/rpackagelister.cc | 28 ++-------------------------- common/rpackagelister.h | 3 --- common/rsource_deb822.cc | Bin 7374 -> 15254 bytes common/rsource_deb822.h | 1 + common/rsources.cc | 8 ++++---- 6 files changed, 9 insertions(+), 35 deletions(-) diff --git a/common/Makefile.am b/common/Makefile.am index fa4f37035..eb60745e8 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -1,5 +1,3 @@ - - noinst_LIBRARIES = libsynaptic.a AM_CPPFLAGS = -I/usr/include/apt-pkg @RPM_HDRS@ @DEB_HDRS@ \ @@ -34,6 +32,8 @@ libsynaptic_a_SOURCES =\ raptoptions.h\ rsources.cc \ rsources.h \ + rsource_deb822.cc \ + rsource_deb822.h \ rcacheactor.cc \ rcacheactor.h \ rpackagelistactor.cc \ diff --git a/common/rpackagelister.cc b/common/rpackagelister.cc index 7c8b67df3..4060531b1 100644 --- a/common/rpackagelister.cc +++ b/common/rpackagelister.cc @@ -2106,35 +2106,11 @@ bool RPackageLister::xapianSearch(string searchString) bool RPackageLister::isMultiarchSystem() { -#ifdef WITH_APT_MULTIARCH_SUPPORT - return (APT::Configuration::getArchitectures().size() > 1); +#if defined(WITH_APT_MULTIARCH_SUPPORT) + return _system->MultiArchEnabled(); #else return false; #endif } -bool RPackageLister::handleFailedInstallation(const string &pkgName) -{ - // Get the package - RPackage *pkg = getPackage(pkgName); - if (!pkg) { - return false; - } - - // Check if package is in a failed state - if (pkg->state() != pkgCache::State::ConfigFiles && - pkg->state() != pkgCache::State::HalfInstalled) { - return false; - } - - // Try to fix the package - pkgCache::PkgIterator pkgIter = pkg->pkg(); - _cache->markKeep(pkgIter, true, true); - - // Commit the changes - pkgAcquireStatus *status = NULL; - RInstallProgress *iprog = NULL; - return commitChanges(status, iprog); -} - // vim:ts=3:sw=3:et diff --git a/common/rpackagelister.h b/common/rpackagelister.h index 3b84625a4..1f1dd1d63 100644 --- a/common/rpackagelister.h +++ b/common/rpackagelister.h @@ -354,9 +354,6 @@ class RPackageLister { bool openXapianIndex(); #endif - // New method to handle failed installations - bool handleFailedInstallation(const string &pkgName); - RPackageLister(); ~RPackageLister(); }; diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index f06e46c2de934d1d9dd5e5ba79ceb364bca68cb8..23469e97cb43b49b072765645f2369541b760a10 100644 GIT binary patch literal 15254 zcmdU0X>S}w5be)M{D<)(l7(O=K|(?y1c~4XBre59d~lHEGqz%{jn_^H$6p8D>vFlO zdU|GOoi&G6_U_Jf*ZHctx~BK9KkugZQ;7gKE!JU*LE;w z8Gm26>rL9noU?SCw$eH7c?#3~{UV*>#WCmU?erF|k(YgXjC-f)HSQm#=jk9*8H3k* z81-Sghrf^TX$>PL;BkukhglLz_Y!|cdHyjzP4e7xeAN6i+%C`(DUqF|;pnC%9`&l#El{Pxe*H)nEqrr4Y;Rw=QVEz$& zG0)s7=A2z?<{svqLE5t!9QR=_@({asYc(A-ZUkNL19g+kRcuXrZ$pw3Xy2r#0fB3{ zs+fI{9;EMKWqNQgjCyiuK_ZJjp~OV%IlRtUq5-k+Jl(>b)9kB*^g$R!yzO8PZSpYC zU&Tz?;5EF;`GQ7bdJnXT<=-wbFSs<$hrow$SKO+%$5&D?`Zg~|jX~|b^huyb8ciCf zrHnNqrOi>}fqpvA7MozsCcdecmm&M=)weP3je+&Yu*3wf6U=Jx{}eMg^Iga@0v}pP ztu;%|KeE&kg{2*sK~7;+vyAsi^n<_i2>fX=W`bSJj+S|aSdrMFeGD6G(1v>bivQy) ztC5SRMN5~SQ#<-BXx=x^zb+)vd0u8aY?;6dok(11FO`;V_>oAI#eI(E9UfQoF*>o{;OTcuOB`;U`_fRuhIrGx5QY3s*AKnkod&qTGqtnLV%K9LdXhWQ>~OE+o9{?Ig?u}>c9wf1{G=FKBPe%m2cbYq#%7yp~mh5JD+iFw$6sHkbDQEtd@+fP||*lQi_$5 zRXhi%rMQn~2YwsUOKr{_89OPoLL9+fD4$Z4BO`r}xY!3&dAMJDgqL-3+O|^sYF5>` z{f3uhNSxR6}g{nQP&sE((}@%2vUE?xK^G+qgp6NTh_Q}jOlpG?d!10i;R7)1bk{^ zx_b>H!bDC%snXtl{XB^Zd`qw zc0JGHnxhi<{$ccWn>M-VYHbwiv*Mbk+@ijU8okRr_gjc0l|kQiq?fwhRnd`M78_pM zMnry#4$d*EA9)rl&(pQ@syw4~Q(*X4jOTv0ZmY@pt_E zvUX?fJ7lNO_Oj(8k5a32%O~ZUi6a`;u~X)Auhnl-ehWt`p#bu+s< z(U%c5i;zpIzUxS*HTNtj*K<}rPCw@J2J&2xry_WgY78{$)A=(0czmm^HJ8~ev5&{N zpQm-3#Ax)N+>7@0bFBhUu5rGu{*i*0qzh-YToI{Q&bvT_G4JV%^d}wEu9x<#;$*^ ziB!2S(vwZ?{AKdQ9IsCgqrJLD??7sv0h7G2>3Cjm{!5^V=PC6Wm#zV};?h=9hw2$D zLEV0y$!^&W;TGQz{X7O|+X*1_Wf9UnT(@FgiY80Yus-2fnujwxI1*AEJH|@!b z^)i`vhj%lTvaX6M)$rX>NulkFHqLZT`K}#WK3V43K=!Zb5%p@#CM6BkWC?3!7Y^sw zuH@41+mf>(xXfN9-SWGS*Nq;HtfG!O8|OO8N`A3cD(~v|zU-)D%J+WCT(4M}6Fu!n zS6WtW>lJ+$0k1LeK zvQ###nte3LJ)T#WUb{Y3X1@9bwv3~P#7;!p2-@@9kLFTaId}$1?v;_$q|Y#3ttn5N zyQoT?BTr~pA=$istllh=+WAZLKgWvJjm(;LYnU`!$nMuLZ)f>yv=m`#*%GJ3s-80H z8uJX8m=Dcm9y94nzYj-wNz*cvR>}G-{J(<<@#MFNEB0&53Vohh?D}(4>y7C$O*gT= zbZ6|?mtwA#OeWE$81^U4ZsQpx@mtRh8X=Jqv$)lC{G{DZKJjFQeS-V5(HmF1*}ro5 z3bX~;hq;zfeTW|JL*@5PeERLr0O_b%zkATttJ}I|l$oDP`?3SSoo&R^XZP}M!)(}P z?fLL%+`H(N^7^bmADU&Nye6q+i~F7Um``HkUrOAzc`eT$p=Kr8e74Yv*6#V$+~*R- za&0ZTl{;sP$u5hsiQ>`xd^6c3Mj>Oly&|{$6K9osLL2`QktxyI-@)y-T0Prv?=9J< zoofU?N|jh=7l7xZS=TjKbNHL1*aP%viJ4R|C7mwKe*DE9eZzf~?8oKV=q9f4cUYVF zzmMo=*NIiaGgwyYvJ(A@%*aLgw14|HEX#^znDx5P!79eQH{(hmp44&PLZyX?oGsWx qvda86W@m#tRMk7$SL1tr>xvyB-I(vp?(F2N9Q+ln-6=Dn*yV486_cR= literal 7374 zcmcIp|4-XS6#qSc#bLCN6bKY)QWXf3%BWUp>n6g~AE1hieUKB$*|Qy3y7qtH`*!x( zaR{_qq?XwCzI@)7d+*)l;FZeqWRXRB66>2cM@NGwQv2#>`mS!1Y#tVh338P$(lp76 zj(Vl=-&v9_GjlgBR4?kQqr;;g)p%*cv@ns1^@E-z>0H}F6*`=&@&lX)ozAwg(QFZG z6*SaOgKF&rWbLQ-cLz~oZ_V8z3k#Fj!Su9+)!A-=9x(fA z@|9PCdHn{k1f9-JlFZZvyKGFp91i~ovt0A5cYv%17UbZ_A|4JwlCgJPm4?N1k6%6L zs7SICra!kuwmj`Ba9w6}-dF#0(0Rmb1e!u~dnjQLO+a9;-#;cQbE|q=xaioBHPgi+ zvuZ-7?w_8c#l1-{I18;!3YDbVsvq*8zekuF62EpT_46=2@oW(EY|trS(z7^c9FJ4L zpnBwnaS__jVGmL52~ppN=b24LFIfa4C)N1n;6PpIzZNFbv4|*_!bOp&VwviEVhS~q z<-x77aWA;U_klPE1)+l~;*weeaSasGuJhY(CeI;+iiER1Wu9;$%KBR3#kej9%sVQ9 z@1N!xk|aK6cOwfKw#ZDIU4LG@UoopB{XVNu^fwwstRp2r)|r$qOwk&wF)cKs<&l4% zVJ!X|O%vu36hT4XVjg7a?@;~{8_pyR8&N+?=4oOf1M)IauY&4j@Tp3DPLr{@vpRme z^vLb??JBOA$1TpCbB_g9;AzWI=Km>d+-G8X|KwN(rBhbsLd=y1V~YV zj--z}@UE?Ue9R!np3^0 z)OP_5Jc}i{y`e^Od`~f1{;SE0;n3&l!l;3~&S0MPc&>u~aek(mh3gE@4pLRTag1m< z*{P^Z!yJctY#re&huKSBO!6#pFPWUYB_kUtQm3aR%>hD}y2(k6po)Xspzj2NFfM0E zh93>;R_OOZZN(!XD!$V1=m;_q#2zv}R4w59;wq;2GO9ZjyuQMLnl^Hcv#4U+;+j)t zAqQuTM(M87(y*PSGnhlECKwA9kqa}qxFxPIE8zeSA(r4GV@qs-jsaE-7$kg6S$!Ga zYuF$e)K!o`+5|Q{f+vZeUY8K12t5NN%{+CO%-jN$Xc?-{hG2QA36~`KfPaSl0f$qT^%0cCx>1#IF$|NLIHkLKyX_RG1u3g+Zb{4=m)5Hl*lQ zlsPGsp{)vkPFfTyv%A|gv7!6Qe5zrE=ccLk@^Ds;Y_;)#nIxC!%=_e+CH4DVmdyQX zEERlXwce9!l5-sXq{ur%y+q;C&CoDi#de%BRClF(IRO|UF2%cCZk5b29Qne;Zl3aYSj{Wa zJizj;CnGNHqbXkwB!U?f;tS7l&pAc-moMaK@I5w~E?icmi$ZR1p1jlP%JIo%k{>Ue zpcs+=HdHv{8(3Ew#i8Qlkzo!eL?w=!pzC>#Mw z*s3{hoi4}GFw^mpS0$Aksr$=E(kn+wk5qLfU|vKV3;wMdjeA>5J@;~z6wY{su`bBG zURn+=k$w(a3{$&0?K^j09Rj+M+S%S|`jp(Y$>XwRAgWMDw0It#W{JJa@t|t;-*_R5 zIak7R)`+iy?EpRGJ-N83pZEYV^cNMB6F6nSA551j>t5)a(lSt8jmIdY#96tIutC!$ zU(@w?Nz`HZd7umDo#BqR|1bnX1M_Tk`9B=mG%0WPQ6PTST{pkC z;9nDS#z8j2PclQfXCcM)UjR^&hqPv#gPJdHpcV8E_aJk?0uS*T?~!XfGAES-FAfl? zZ1Apn?a{9hOYA}>n5)#Dg>VcY#aKG&$=j~z+G?kCyJ~ZGJOcze?}b^hQVz>Wl?G60 zX7KVMffzP^CTc+PQlty{lDRzxPC-fq*NHLy@BkfACNbkhjpgxJe(SgFTAvaN8?Ishw#Ab7m;~qg!9MU2S4J79}sBjzW1I6I3 Z!wK4jRPu{J@4;1H3_nz|1)OYO)xTcxgzf+U diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 7564b951d..62adbba33 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -15,6 +15,7 @@ #include #include #include +#include class RDeb822Source { public: diff --git a/common/rsources.cc b/common/rsources.cc index a31348dd7..a7d059dd3 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -324,7 +324,7 @@ bool SourcesList::UpdateSources() bool isDeb822 = false; if (!records.empty() && (records[0]->Type & Deb822)) { isDeb822 = true; - } + } // Open the appropriate file for writing ofstream out(sourcePath.c_str(), ios::out); @@ -343,16 +343,16 @@ bool SourcesList::UpdateSources() entries.push_back(entry); } if (!RDeb822Source::WriteDeb822File(sourcePath, entries)) { - return false; + return false; } } else { // Write classic format for (const auto& record : records) { if (record->Type == Comment) { out << record->Comment << endl; - } else { + } else { out << *record; - } + } } } From 0a238000f07ce156967bdfe791718f562a156819 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Fri, 9 May 2025 09:41:45 +0530 Subject: [PATCH 06/43] Add support for additional Deb822 fields and update integration tests. Fix UTF-8 encoding issues. --- common/rsource_deb822.cc | Bin 15254 -> 9326 bytes common/rsource_deb822.h | 3 + tests/test_deb822_integration.cc | 112 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 23469e97cb43b49b072765645f2369541b760a10..af864fa7a4ecc6eb6cc9f270e5877c55e6c20850 100644 GIT binary patch literal 9326 zcmcIq>rNv_7XHsu6dnak05cgWQsluz2}87zvY7}I}5efQ0qH={UKr|KR3t?u)3 z71fFfDpfT_k(c$J`cmP)%e+{Z>3mVEVSJ?Cyngewny#~`sMA;_`cW_QVx_ZM)jC=! z`vaV3dwZ|aEM7K=R)d!M#lWqdgRIlyVSX0p*?l^1%BW8BY_z!OA-PYNx~Z4_i&b5A z*6!gU80O2WI*$hFSKolb!QS3oo-frk$7M9Wkr(~B$oHJl9xmr<7GwaN>fsV}O*(FxhqlK5%Qsb59Kx#xpe z&kkLHO?sJBY{%0AY*0fA!?cdFKcXR8bs|JZzImS8$~SnkB=*4L%=8P>>n}#+zo%6NsZ%7J^{Mok z(4nkP)?Uo#dc?dFYxwC!rJ*s z-+9+y!El*O(|M+o@7EqVKRW$>-KGAnj2B>C4BK4$8iMCtwx6PG-bC|&#Xjqj-$dnH z2Oh{1B()AhEdCZ{3G9BIMR!Y`oF!>Re*lC~!B0%sBI-h>K z2haK%*l#5i)GiIo>ufu>vHbth#-7k(1wCO~D}&JmGlUHp^U9ret;X$+i*crY8suoC zS4F+ik^xuSqC87<|YEy*V@-+^oiG;MloKbeJs%0&c)S2)vn|Q7qsPVC(RN z9t&!1bK1kQKnaAw;9f_O>x`aCgL_*QS~Lvw8Fq|hJMNfH{PDB}__$SNKpm(JR(8~F zfp)82OBv|2Pj}QOSiM!7&;_~-^7lm5LZlZ~L1=%*!@%3>mG!a8@~wLV32+L$!d<5j zmDQcDpEQiD&`w~kWRQLVvR&rcqb}>4oM-iG7{AIy@W2~I1r|;elT|;ZRXra2JO?l+ zBCqk2Cv={M;eR~Z(8S5m%#)u0u{T5$4dVmh@FJ@4yoHTFT2?SI%Y#`}#^#|kFB!5) ziMqHTNyZpc>A0vS(DXp&;K&FAL5%)LhCdoq*%{f;pMpxqBL+A=8*>Z-xd&ojvnyB) zV)&7Si77r#!hk}=I~=HKE7!D)9pip58f@W}aYTaHlQo!Q@lXW`l++=*_GAv|*aXLb zX~4%=vNzF#hN3T_&wv2hD6~nqYDQCC!{Z@P2L}*o&XjzXE^+QLlxEVp^f(nm&j@{T!o@GFSmKn4gZ?(h zP~_)Xo4vKfOn?(s;Z|f1c}g)N_|e}ZirSxsRuhqD0==AMiy z{X?6%%%k^+GyTL~vf;N~iQch+h8mPPt5r(-5PqHaC{*e3aoaUpk!q2eD+;L zYsFDFIwwgQ9Dwj(d?`~c+Lq-{retp3zk5Gc-zAAEO1OI{Wj@By&4*XKVp8=&SA1mf za?E>EMOG(U=XIc2-(jwgY;DWu?VBse;jR&&}Vj)txy$r|fI?eE&0gD%Z>>tvs;(VEBPz=OJ zm$!%GLop5MQKrzq@Zm1d$K$NX?d>>H;t#~@TF;whRATF+I~5dcl_CvSp#%Xd0Vy!| z!C?d`yViRR-m^tLXuPQ-7pmqC;xl}G0^H!4=ZVko7%dx^%L5kn@>qT4@w5sM^lV-v znXqh~#n6#ufEGa?gQ5OUOXWfm(139y&}HtEJ0%#YwqI?KXxDd=CcSEdEOQ9m#V*^B zHpnu)ZKGLXc%Z6&%^qNc$$0af!0AcD+pnlHRn`;DZ3V`lEd{m9m7M=Fg=z`+Ivi3C zVAd*{THeMy`;O*7z{BLAdgL6N_sg*nnoY`$_ykJX6LX8*n@tLjQ_ia8T z|8M6G*-)KV>_27@-!$P$gDQsaOpN6P1!lALt{wB@SsFM8l|&qy=Lv(hVaP~}kcrK%w3iXw6_Fr>O#0+)Q><>cLk^TSuQYv^4m*AHNpaaT@iZ+N$!4Mrd&ftJs(l;2O-@?7&48EQ* z%kPH+^`ZVy!v2(aV*rI?%S?>&f>(f8V(d0IbC)`@vM!?(%50OF@Cws65)-@0M$jyz VQn6(42~6J!e2a*!=UMlY`Y(y6$Atg@ literal 15254 zcmdU0X>S}w5be)M{D<)(l7(O=K|(?y1c~4XBre59d~lHEGqz%{jn_^H$6p8D>vFlO zdU|GOoi&G6_U_Jf*ZHctx~BK9KkugZQ;7gKE!JU*LE;w z8Gm26>rL9noU?SCw$eH7c?#3~{UV*>#WCmU?erF|k(YgXjC-f)HSQm#=jk9*8H3k* z81-Sghrf^TX$>PL;BkukhglLz_Y!|cdHyjzP4e7xeAN6i+%C`(DUqF|;pnC%9`&l#El{Pxe*H)nEqrr4Y;Rw=QVEz$& zG0)s7=A2z?<{svqLE5t!9QR=_@({asYc(A-ZUkNL19g+kRcuXrZ$pw3Xy2r#0fB3{ zs+fI{9;EMKWqNQgjCyiuK_ZJjp~OV%IlRtUq5-k+Jl(>b)9kB*^g$R!yzO8PZSpYC zU&Tz?;5EF;`GQ7bdJnXT<=-wbFSs<$hrow$SKO+%$5&D?`Zg~|jX~|b^huyb8ciCf zrHnNqrOi>}fqpvA7MozsCcdecmm&M=)weP3je+&Yu*3wf6U=Jx{}eMg^Iga@0v}pP ztu;%|KeE&kg{2*sK~7;+vyAsi^n<_i2>fX=W`bSJj+S|aSdrMFeGD6G(1v>bivQy) ztC5SRMN5~SQ#<-BXx=x^zb+)vd0u8aY?;6dok(11FO`;V_>oAI#eI(E9UfQoF*>o{;OTcuOB`;U`_fRuhIrGx5QY3s*AKnkod&qTGqtnLV%K9LdXhWQ>~OE+o9{?Ig?u}>c9wf1{G=FKBPe%m2cbYq#%7yp~mh5JD+iFw$6sHkbDQEtd@+fP||*lQi_$5 zRXhi%rMQn~2YwsUOKr{_89OPoLL9+fD4$Z4BO`r}xY!3&dAMJDgqL-3+O|^sYF5>` z{f3uhNSxR6}g{nQP&sE((}@%2vUE?xK^G+qgp6NTh_Q}jOlpG?d!10i;R7)1bk{^ zx_b>H!bDC%snXtl{XB^Zd`qw zc0JGHnxhi<{$ccWn>M-VYHbwiv*Mbk+@ijU8okRr_gjc0l|kQiq?fwhRnd`M78_pM zMnry#4$d*EA9)rl&(pQ@syw4~Q(*X4jOTv0ZmY@pt_E zvUX?fJ7lNO_Oj(8k5a32%O~ZUi6a`;u~X)Auhnl-ehWt`p#bu+s< z(U%c5i;zpIzUxS*HTNtj*K<}rPCw@J2J&2xry_WgY78{$)A=(0czmm^HJ8~ev5&{N zpQm-3#Ax)N+>7@0bFBhUu5rGu{*i*0qzh-YToI{Q&bvT_G4JV%^d}wEu9x<#;$*^ ziB!2S(vwZ?{AKdQ9IsCgqrJLD??7sv0h7G2>3Cjm{!5^V=PC6Wm#zV};?h=9hw2$D zLEV0y$!^&W;TGQz{X7O|+X*1_Wf9UnT(@FgiY80Yus-2fnujwxI1*AEJH|@!b z^)i`vhj%lTvaX6M)$rX>NulkFHqLZT`K}#WK3V43K=!Zb5%p@#CM6BkWC?3!7Y^sw zuH@41+mf>(xXfN9-SWGS*Nq;HtfG!O8|OO8N`A3cD(~v|zU-)D%J+WCT(4M}6Fu!n zS6WtW>lJ+$0k1LeK zvQ###nte3LJ)T#WUb{Y3X1@9bwv3~P#7;!p2-@@9kLFTaId}$1?v;_$q|Y#3ttn5N zyQoT?BTr~pA=$istllh=+WAZLKgWvJjm(;LYnU`!$nMuLZ)f>yv=m`#*%GJ3s-80H z8uJX8m=Dcm9y94nzYj-wNz*cvR>}G-{J(<<@#MFNEB0&53Vohh?D}(4>y7C$O*gT= zbZ6|?mtwA#OeWE$81^U4ZsQpx@mtRh8X=Jqv$)lC{G{DZKJjFQeS-V5(HmF1*}ro5 z3bX~;hq;zfeTW|JL*@5PeERLr0O_b%zkATttJ}I|l$oDP`?3SSoo&R^XZP}M!)(}P z?fLL%+`H(N^7^bmADU&Nye6q+i~F7Um``HkUrOAzc`eT$p=Kr8e74Yv*6#V$+~*R- za&0ZTl{;sP$u5hsiQ>`xd^6c3Mj>Oly&|{$6K9osLL2`QktxyI-@)y-T0Prv?=9J< zoofU?N|jh=7l7xZS=TjKbNHL1*aP%viJ4R|C7mwKe*DE9eZzf~?8oKV=q9f4cUYVF zzmMo=*NIiaGgwyYvJ(A@%*aLgw14|HEX#^znDx5P!79eQH{(hmp44&PLZyX?oGsWx qvda86W@m#tRMk7$SL1tr>xvyB-I(vp?(F2N9Q+ln-6=Dn*yV486_cR= diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 62adbba33..4feb43300 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -25,6 +25,9 @@ class RDeb822Source { std::string Suites; // Space-separated list of suites std::string Components; // Space-separated list of components std::string SignedBy; // Path to keyring file + std::string Architectures; // Space-separated list of architectures + std::string Languages; // Space-separated list of languages + std::string Targets; // Space-separated list of targets bool Enabled; // Whether the source is enabled std::string Comment; // Any comments associated with this entry }; diff --git a/tests/test_deb822_integration.cc b/tests/test_deb822_integration.cc index b6e89bbf0..eeac5ff88 100644 --- a/tests/test_deb822_integration.cc +++ b/tests/test_deb822_integration.cc @@ -209,4 +209,116 @@ TEST_F(Deb822Test, SourcesListIntegration) { // Clean up remove(newFile.c_str()); +} + +TEST_F(Deb822Test, ParseAdditionalFields) { + std::ofstream ofs(testFile.c_str()); + ASSERT_TRUE(ofs.is_open()); + ofs << "Types: deb deb-src\n" + << "URIs: http://deb.debian.org/debian\n" + << "Suites: trixie\n" + << "Components: main contrib\n" + << "Architectures: amd64 arm64\n" + << "Languages: en fr de\n" + << "Targets: stable-updates\n" + << "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\n"; + ofs.close(); + + std::vector entries; + ASSERT_TRUE(RDeb822Source::ParseDeb822File(testFile, entries)); + ASSERT_EQ(entries.size(), 1); + + const auto& entry = entries[0]; + EXPECT_EQ(entry.Types, "deb deb-src"); + EXPECT_EQ(entry.URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(entry.Suites, "trixie"); + EXPECT_EQ(entry.Components, "main contrib"); + EXPECT_EQ(entry.Architectures, "amd64 arm64"); + EXPECT_EQ(entry.Languages, "en fr de"); + EXPECT_EQ(entry.Targets, "stable-updates"); + EXPECT_EQ(entry.SignedBy, "/usr/share/keyrings/debian-archive-keyring.gpg"); + EXPECT_TRUE(entry.Enabled); +} + +TEST_F(Deb822Test, ConvertAdditionalFields) { + // Create a Deb822 entry with additional fields + RDeb822Source::Deb822Entry deb822Entry; + deb822Entry.Types = "deb deb-src"; + deb822Entry.URIs = "http://deb.debian.org/debian"; + deb822Entry.Suites = "trixie"; + deb822Entry.Components = "main contrib"; + deb822Entry.Architectures = "amd64 arm64"; + deb822Entry.Languages = "en fr de"; + deb822Entry.Targets = "stable-updates"; + deb822Entry.SignedBy = "/usr/share/keyrings/debian-archive-keyring.gpg"; + deb822Entry.Enabled = true; + + // Convert to SourceRecord + SourcesList::SourceRecord sourceRecord; + ASSERT_TRUE(RDeb822Source::ConvertToSourceRecord(deb822Entry, sourceRecord)); + + // Verify conversion + EXPECT_TRUE(sourceRecord.Type & SourcesList::Deb); + EXPECT_TRUE(sourceRecord.Type & SourcesList::DebSrc); + EXPECT_EQ(sourceRecord.URI, "http://deb.debian.org/debian"); + EXPECT_EQ(sourceRecord.Dist, "trixie"); + ASSERT_EQ(sourceRecord.NumSections, 2); + EXPECT_EQ(sourceRecord.Sections[0], "main"); + EXPECT_EQ(sourceRecord.Sections[1], "contrib"); + + // Check that additional fields are stored in Comment + EXPECT_TRUE(sourceRecord.Comment.find("Architectures: amd64 arm64") != std::string::npos); + EXPECT_TRUE(sourceRecord.Comment.find("Languages: en fr de") != std::string::npos); + EXPECT_TRUE(sourceRecord.Comment.find("Targets: stable-updates") != std::string::npos); + EXPECT_TRUE(sourceRecord.Comment.find("Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg") != std::string::npos); + + // Convert back to Deb822 + RDeb822Source::Deb822Entry convertedEntry; + ASSERT_TRUE(RDeb822Source::ConvertFromSourceRecord(sourceRecord, convertedEntry)); + + // Verify round-trip conversion + EXPECT_EQ(convertedEntry.Types, "deb deb-src"); + EXPECT_EQ(convertedEntry.URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(convertedEntry.Suites, "trixie"); + EXPECT_EQ(convertedEntry.Components, "main contrib"); + EXPECT_EQ(convertedEntry.Architectures, "amd64 arm64"); + EXPECT_EQ(convertedEntry.Languages, "en fr de"); + EXPECT_EQ(convertedEntry.Targets, "stable-updates"); + EXPECT_EQ(convertedEntry.SignedBy, "/usr/share/keyrings/debian-archive-keyring.gpg"); + EXPECT_TRUE(convertedEntry.Enabled); +} + +TEST_F(Deb822Test, WriteAndReadAdditionalFields) { + // Create temporary file + std::string tempFile = "temp_test.sources"; + std::ofstream out(tempFile); + out << "Types: deb deb-src\n" + << "URIs: http://deb.debian.org/debian\n" + << "Suites: trixie\n" + << "Components: main contrib\n" + << "Architectures: amd64 arm64\n" + << "Languages: en fr de\n" + << "Targets: stable-updates\n" + << "Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n"; + out.close(); + + // Read it back + std::vector entries; + std::string error; + EXPECT_TRUE(RDeb822Source::ParseDeb822File(tempFile, entries, error)) << error; + EXPECT_EQ(entries.size(), 1); + + // Verify fields + const auto& entry = entries[0]; + EXPECT_EQ(entry.Types, "deb deb-src"); + EXPECT_EQ(entry.URIs, "http://deb.debian.org/debian"); + EXPECT_EQ(entry.Suites, "trixie"); + EXPECT_EQ(entry.Components, "main contrib"); + EXPECT_EQ(entry.Architectures, "amd64 arm64"); + EXPECT_EQ(entry.Languages, "en fr de"); + EXPECT_EQ(entry.Targets, "stable-updates"); + EXPECT_EQ(entry.SignedBy, "/usr/share/keyrings/debian-archive-keyring.gpg"); + + // Clean up + std::remove(tempFile.c_str()); } \ No newline at end of file From 2f44455e2521a0d333d9e62ce38bf948f644c444 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Fri, 9 May 2025 09:46:01 +0530 Subject: [PATCH 07/43] Fix UTF-8 encoding issues in Deb822 source handling. Implement proper UTF-8 support using wide strings and codecvt_utf8 for file I/O operations. --- common/rsource_deb822.cc | 152 ++++++++++++++++++--------------------- common/rsource_deb822.h | 31 ++++---- 2 files changed, 86 insertions(+), 97 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index af864fa7a..16f08ec79 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -9,52 +9,58 @@ #include #include #include +#include +#include #include "i18n.h" bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - std::ifstream file(path.c_str()); + // Open file with UTF-8 encoding + std::wifstream file(path.c_str()); if (!file) { return _error->Error(_("Cannot open %s"), path.c_str()); } + + // Set UTF-8 locale + file.imbue(std::locale(file.getloc(), new std::codecvt_utf8)); - std::map fields; + std::map fields; while (ParseStanza(file, fields)) { Deb822Entry entry; // Required fields - auto types_it = fields.find("Types"); + auto types_it = fields.find(L"Types"); if (types_it == fields.end()) { return _error->Error(_("Missing Types field in %s"), path.c_str()); } - entry.Types = types_it->second; + entry.Types = std::string(types_it->second.begin(), types_it->second.end()); - auto uris_it = fields.find("URIs"); + auto uris_it = fields.find(L"URIs"); if (uris_it == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = uris_it->second; + entry.URIs = std::string(uris_it->second.begin(), uris_it->second.end()); - auto suites_it = fields.find("Suites"); + auto suites_it = fields.find(L"Suites"); if (suites_it == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = suites_it->second; + entry.Suites = std::string(suites_it->second.begin(), suites_it->second.end()); // Optional fields - entry.Components = fields["Components"]; - entry.SignedBy = fields["Signed-By"]; - entry.Architectures = fields["Architectures"]; - entry.Languages = fields["Languages"]; - entry.Targets = fields["Targets"]; + entry.Components = std::string(fields[L"Components"].begin(), fields[L"Components"].end()); + entry.SignedBy = std::string(fields[L"Signed-By"].begin(), fields[L"Signed-By"].end()); + entry.Architectures = std::string(fields[L"Architectures"].begin(), fields[L"Architectures"].end()); + entry.Languages = std::string(fields[L"Languages"].begin(), fields[L"Languages"].end()); + entry.Targets = std::string(fields[L"Targets"].begin(), fields[L"Targets"].end()); // Handle enabled/disabled state - auto enabled_it = fields.find("Enabled"); - entry.Enabled = (enabled_it == fields.end()) || (enabled_it->second != "no"); + auto enabled_it = fields.find(L"Enabled"); + entry.Enabled = (enabled_it == fields.end()) || (enabled_it->second != L"no"); // Store any comment lines - auto comment_it = fields.find("#"); + auto comment_it = fields.find(L"#"); if (comment_it != fields.end()) { - entry.Comment = comment_it->second; + entry.Comment = std::string(comment_it->second.begin(), comment_it->second.end()); } entries.push_back(entry); @@ -65,40 +71,55 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - std::ofstream file(path.c_str()); + // Open file with UTF-8 encoding + std::wofstream file(path.c_str()); if (!file) { return _error->Error(_("Cannot write to %s"), path.c_str()); } + + // Set UTF-8 locale + file.imbue(std::locale(file.getloc(), new std::codecvt_utf8)); for (const auto& entry : entries) { if (!entry.Comment.empty()) { - file << entry.Comment << "\n"; + std::wstring wcomment(entry.Comment.begin(), entry.Comment.end()); + file << wcomment << L"\n"; } - file << "Types: " << entry.Types << "\n"; - file << "URIs: " << entry.URIs << "\n"; - file << "Suites: " << entry.Suites << "\n"; + std::wstring wtypes(entry.Types.begin(), entry.Types.end()); + std::wstring wuris(entry.URIs.begin(), entry.URIs.end()); + std::wstring wsuites(entry.Suites.begin(), entry.Suites.end()); + + file << L"Types: " << wtypes << L"\n"; + file << L"URIs: " << wuris << L"\n"; + file << L"Suites: " << wsuites << L"\n"; + if (!entry.Components.empty()) { - file << "Components: " << entry.Components << "\n"; + std::wstring wcomponents(entry.Components.begin(), entry.Components.end()); + file << L"Components: " << wcomponents << L"\n"; } if (!entry.SignedBy.empty()) { - file << "Signed-By: " << entry.SignedBy << "\n"; + std::wstring wsignedby(entry.SignedBy.begin(), entry.SignedBy.end()); + file << L"Signed-By: " << wsignedby << L"\n"; } if (!entry.Architectures.empty()) { - file << "Architectures: " << entry.Architectures << "\n"; + std::wstring warchitectures(entry.Architectures.begin(), entry.Architectures.end()); + file << L"Architectures: " << warchitectures << L"\n"; } if (!entry.Languages.empty()) { - file << "Languages: " << entry.Languages << "\n"; + std::wstring wlanguages(entry.Languages.begin(), entry.Languages.end()); + file << L"Languages: " << wlanguages << L"\n"; } if (!entry.Targets.empty()) { - file << "Targets: " << entry.Targets << "\n"; + std::wstring wtargets(entry.Targets.begin(), entry.Targets.end()); + file << L"Targets: " << wtargets << L"\n"; } if (!entry.Enabled) { - file << "Enabled: no\n"; + file << L"Enabled: no\n"; } - file << "\n"; // Empty line between stanzas + file << L"\n"; // Empty line between stanzas } return true; @@ -228,72 +249,41 @@ bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& rec return true; } -bool RDeb822Source::ParseStanza(std::istream& input, std::map& fields) { - std::string line; - std::string current_field; - std::string current_value; +bool RDeb822Source::ParseStanza(std::wifstream& file, std::map& fields) { + std::wstring line; bool in_stanza = false; - while (std::getline(input, line)) { - TrimWhitespace(line); - - // Skip empty lines between stanzas + while (std::getline(file, line)) { + // Skip empty lines if (line.empty()) { if (in_stanza) { - // End of stanza - if (!current_field.empty()) { - TrimWhitespace(current_value); - fields[current_field] = current_value; - } - return true; + return true; // End of stanza } continue; } - // Handle comments - if (line[0] == '#') { - if (!in_stanza) { - // Comment before stanza belongs to next stanza - fields["#"] = fields["#"].empty() ? line : fields["#"] + "\n" + line; - } + // Skip comments + if (line[0] == L'#') { + fields[L"#"] = line.substr(1); continue; } - in_stanza = true; - - // Handle field continuation - if (line[0] == ' ' || line[0] == '\t') { - if (!current_field.empty()) { - current_value += "\n" + line; - } - continue; + // Parse field + size_t colon_pos = line.find(L':'); + if (colon_pos != std::wstring::npos) { + in_stanza = true; + std::wstring field = line.substr(0, colon_pos); + std::wstring value = line.substr(colon_pos + 1); + + // Trim whitespace + while (!field.empty() && std::iswspace(field.back())) field.pop_back(); + while (!value.empty() && std::iswspace(value.front())) value.erase(0, 1); + + fields[field] = value; } - - // New field - if (!current_field.empty()) { - TrimWhitespace(current_value); - fields[current_field] = current_value; - } - - size_t colon = line.find(':'); - if (colon == std::string::npos) { - return _error->Error(_("Invalid line format: %s"), line.c_str()); - } - - current_field = line.substr(0, colon); - current_value = line.substr(colon + 1); - TrimWhitespace(current_field); - TrimWhitespace(current_value); - } - - // Handle last field of last stanza - if (in_stanza && !current_field.empty()) { - TrimWhitespace(current_value); - fields[current_field] = current_value; - return true; } - return false; + return in_stanza; // Return true if we found any fields } void RDeb822Source::TrimWhitespace(std::string& str) { diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 4feb43300..6203a14ca 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -8,28 +8,28 @@ * License, or (at your option) any later version. */ -#ifndef _RSOURCE_DEB822_H -#define _RSOURCE_DEB822_H +#ifndef RSOURCE_DEB822_H +#define RSOURCE_DEB822_H -#include "rsources.h" #include #include #include -#include +#include +#include "sourcelist.h" class RDeb822Source { public: struct Deb822Entry { - std::string Types; // Can be "deb" and/or "deb-src" - std::string URIs; // Space-separated list of URIs - std::string Suites; // Space-separated list of suites - std::string Components; // Space-separated list of components - std::string SignedBy; // Path to keyring file + std::string Types; // Space-separated list of types + std::string URIs; // Space-separated list of URIs + std::string Suites; // Space-separated list of suites + std::string Components; // Space-separated list of components + std::string SignedBy; // Path to keyring file std::string Architectures; // Space-separated list of architectures - std::string Languages; // Space-separated list of languages - std::string Targets; // Space-separated list of targets - bool Enabled; // Whether the source is enabled - std::string Comment; // Any comments associated with this entry + std::string Languages; // Space-separated list of languages + std::string Targets; // Space-separated list of targets + bool Enabled; // Whether the source is enabled + std::string Comment; // Any comments associated with this entry }; static bool ParseDeb822File(const std::string& path, std::vector& entries); @@ -38,8 +38,7 @@ class RDeb822Source { static bool ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry); private: - static bool ParseStanza(std::istream& input, std::map& fields); - static void TrimWhitespace(std::string& str); + static bool ParseStanza(std::wifstream& file, std::map& fields); }; -#endif // _RSOURCE_DEB822_H \ No newline at end of file +#endif // RSOURCE_DEB822_H \ No newline at end of file From 4e0fb397e936ae8bcce26fa40b3445b95dc1d51e Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Thu, 15 May 2025 21:09:43 +0530 Subject: [PATCH 08/43] Add rpackagemanager files to Makefile.am --- common/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/Makefile.am b/common/Makefile.am index eb60745e8..834f5d495 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -36,6 +36,8 @@ libsynaptic_a_SOURCES =\ rsource_deb822.h \ rcacheactor.cc \ rcacheactor.h \ + rpackagemanager.cc \ + rpackagemanager.h \ rpackagelistactor.cc \ rpackagelistactor.h \ rtagcollbuilder.cc \ From 8937715ce8437a855a3c0f2b6af938b7c7f34e7d Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 19 May 2025 10:36:46 +0530 Subject: [PATCH 09/43] Add Deb822 source format support header --- common/rsource_deb822.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 6203a14ca..ffb354384 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -15,7 +15,8 @@ #include #include #include -#include "sourcelist.h" +#include +#include class RDeb822Source { public: @@ -34,11 +35,12 @@ class RDeb822Source { static bool ParseDeb822File(const std::string& path, std::vector& entries); static bool WriteDeb822File(const std::string& path, const std::vector& entries); - static bool ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record); - static bool ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry); + static bool ConvertToSourceRecord(const Deb822Entry& entry, pkgSourceList::SourceRecord& record); + static bool ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry); private: static bool ParseStanza(std::wifstream& file, std::map& fields); + static void TrimWhitespace(std::string& str); }; #endif // RSOURCE_DEB822_H \ No newline at end of file From 609320fab61268686077fbc0e9f0b8a5997f4c9c Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 19 May 2025 10:36:58 +0530 Subject: [PATCH 10/43] Implement Deb822 source format handling --- common/rsource_deb822.cc | 349 ++++++++++++++++----------------------- 1 file changed, 146 insertions(+), 203 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 16f08ec79..b9b1bb7df 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -12,289 +12,232 @@ #include #include #include "i18n.h" +#include +#include +#include bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - // Open file with UTF-8 encoding - std::wifstream file(path.c_str()); + std::wifstream file(path); if (!file) { return _error->Error(_("Cannot open %s"), path.c_str()); } - - // Set UTF-8 locale - file.imbue(std::locale(file.getloc(), new std::codecvt_utf8)); std::map fields; while (ParseStanza(file, fields)) { Deb822Entry entry; - - // Required fields - auto types_it = fields.find(L"Types"); - if (types_it == fields.end()) { + + // Check required fields + if (fields.find(L"Types") == fields.end()) { return _error->Error(_("Missing Types field in %s"), path.c_str()); } - entry.Types = std::string(types_it->second.begin(), types_it->second.end()); - - auto uris_it = fields.find(L"URIs"); - if (uris_it == fields.end()) { + entry.Types = APT::String::FromUTF8(fields[L"Types"]); + + if (fields.find(L"URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = std::string(uris_it->second.begin(), uris_it->second.end()); - - auto suites_it = fields.find(L"Suites"); - if (suites_it == fields.end()) { + entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); + + if (fields.find(L"Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = std::string(suites_it->second.begin(), suites_it->second.end()); - + entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); + // Optional fields - entry.Components = std::string(fields[L"Components"].begin(), fields[L"Components"].end()); - entry.SignedBy = std::string(fields[L"Signed-By"].begin(), fields[L"Signed-By"].end()); - entry.Architectures = std::string(fields[L"Architectures"].begin(), fields[L"Architectures"].end()); - entry.Languages = std::string(fields[L"Languages"].begin(), fields[L"Languages"].end()); - entry.Targets = std::string(fields[L"Targets"].begin(), fields[L"Targets"].end()); - - // Handle enabled/disabled state - auto enabled_it = fields.find(L"Enabled"); - entry.Enabled = (enabled_it == fields.end()) || (enabled_it->second != L"no"); - - // Store any comment lines - auto comment_it = fields.find(L"#"); - if (comment_it != fields.end()) { - entry.Comment = std::string(comment_it->second.begin(), comment_it->second.end()); + if (fields.find(L"Components") != fields.end()) { + entry.Components = APT::String::FromUTF8(fields[L"Components"]); } - + if (fields.find(L"Signed-By") != fields.end()) { + entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); + } + if (fields.find(L"Architectures") != fields.end()) { + entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); + } + if (fields.find(L"Languages") != fields.end()) { + entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); + } + if (fields.find(L"Targets") != fields.end()) { + entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); + } + + entry.Enabled = true; // Default to enabled entries.push_back(entry); fields.clear(); } - + return true; } bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector& entries) { - // Open file with UTF-8 encoding - std::wofstream file(path.c_str()); + std::wofstream file(path); if (!file) { return _error->Error(_("Cannot write to %s"), path.c_str()); } - // Set UTF-8 locale - file.imbue(std::locale(file.getloc(), new std::codecvt_utf8)); - for (const auto& entry : entries) { - if (!entry.Comment.empty()) { - std::wstring wcomment(entry.Comment.begin(), entry.Comment.end()); - file << wcomment << L"\n"; + if (!entry.Enabled) { + file << L"# Disabled: "; } - - std::wstring wtypes(entry.Types.begin(), entry.Types.end()); - std::wstring wuris(entry.URIs.begin(), entry.URIs.end()); - std::wstring wsuites(entry.Suites.begin(), entry.Suites.end()); - file << L"Types: " << wtypes << L"\n"; - file << L"URIs: " << wuris << L"\n"; - file << L"Suites: " << wsuites << L"\n"; + file << L"Types: " << APT::String::ToUTF8(entry.Types) << std::endl; + file << L"URIs: " << APT::String::ToUTF8(entry.URIs) << std::endl; + file << L"Suites: " << APT::String::ToUTF8(entry.Suites) << std::endl; if (!entry.Components.empty()) { - std::wstring wcomponents(entry.Components.begin(), entry.Components.end()); - file << L"Components: " << wcomponents << L"\n"; + file << L"Components: " << APT::String::ToUTF8(entry.Components) << std::endl; } if (!entry.SignedBy.empty()) { - std::wstring wsignedby(entry.SignedBy.begin(), entry.SignedBy.end()); - file << L"Signed-By: " << wsignedby << L"\n"; + file << L"Signed-By: " << APT::String::ToUTF8(entry.SignedBy) << std::endl; } if (!entry.Architectures.empty()) { - std::wstring warchitectures(entry.Architectures.begin(), entry.Architectures.end()); - file << L"Architectures: " << warchitectures << L"\n"; + file << L"Architectures: " << APT::String::ToUTF8(entry.Architectures) << std::endl; } if (!entry.Languages.empty()) { - std::wstring wlanguages(entry.Languages.begin(), entry.Languages.end()); - file << L"Languages: " << wlanguages << L"\n"; + file << L"Languages: " << APT::String::ToUTF8(entry.Languages) << std::endl; } if (!entry.Targets.empty()) { - std::wstring wtargets(entry.Targets.begin(), entry.Targets.end()); - file << L"Targets: " << wtargets << L"\n"; - } - - if (!entry.Enabled) { - file << L"Enabled: no\n"; + file << L"Targets: " << APT::String::ToUTF8(entry.Targets) << std::endl; } - - file << L"\n"; // Empty line between stanzas + + file << std::endl; } - + return true; } -bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record) { - std::istringstream types(entry.Types); +bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, pkgSourceList::SourceRecord& record) { + // Parse types + bool has_deb = false; + bool has_deb_src = false; + std::istringstream typeStream(entry.Types); std::string type; - bool has_deb = false, has_deb_src = false; - - while (types >> type) { + while (std::getline(typeStream, type, ' ')) { + TrimWhitespace(type); if (type == "deb") has_deb = true; - else if (type == "deb-src") has_deb_src = true; + if (type == "deb-src") has_deb_src = true; } - + record.Type = 0; - if (has_deb) record.Type |= SourcesList::Deb; - if (has_deb_src) record.Type |= SourcesList::DebSrc; - if (!entry.Enabled) record.Type |= SourcesList::Disabled; - - std::istringstream uri_stream(entry.URIs); + if (has_deb) record.Type |= pkgSourceList::Deb; + if (has_deb_src) record.Type |= pkgSourceList::DebSrc; + if (!entry.Enabled) record.Type |= pkgSourceList::Disabled; + + // Parse URIs + std::istringstream uriStream(entry.URIs); std::string uri; - uri_stream >> uri; // Take first URI - if (!record.SetURI(uri)) { - return false; + while (std::getline(uriStream, uri, ' ')) { + TrimWhitespace(uri); + if (!uri.empty()) { + record.URI = uri; + break; + } } - - std::istringstream suite_stream(entry.Suites); + + // Parse suites + std::istringstream suiteStream(entry.Suites); std::string suite; - suite_stream >> suite; // Take first Suite - record.Dist = suite; - - // Handle Components - if (!entry.Components.empty()) { - std::istringstream comp_stream(entry.Components); - std::vector components; - std::string comp; - while (comp_stream >> comp) { - components.push_back(comp); - } - - record.NumSections = components.size(); - record.Sections = new std::string[record.NumSections]; - for (size_t i = 0; i < components.size(); i++) { - record.Sections[i] = components[i]; + while (std::getline(suiteStream, suite, ' ')) { + TrimWhitespace(suite); + if (!suite.empty()) { + record.Dist = suite; + break; } } - - // Store additional fields in Comment for now - // TODO: Add proper fields to SourceRecord for these - std::stringstream additional; - if (!entry.Architectures.empty()) { - additional << "Architectures: " << entry.Architectures << "\n"; - } - if (!entry.Languages.empty()) { - additional << "Languages: " << entry.Languages << "\n"; - } - if (!entry.Targets.empty()) { - additional << "Targets: " << entry.Targets << "\n"; - } - if (!entry.SignedBy.empty()) { - additional << "Signed-By: " << entry.SignedBy << "\n"; - } - if (!entry.Comment.empty()) { - additional << entry.Comment; + + // Parse components + std::istringstream compStream(entry.Components); + std::string comp; + while (std::getline(compStream, comp, ' ')) { + TrimWhitespace(comp); + if (!comp.empty()) { + record.Comps.push_back(comp); + } } - record.Comment = additional.str(); - + return true; } -bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) { - std::string types; - if (record.Type & SourcesList::Deb) { - types += "deb "; +bool RDeb822Source::ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry) { + // Set types + std::stringstream typeStream; + if (record.Type & pkgSourceList::Deb) { + typeStream << "deb "; } - if (record.Type & SourcesList::DebSrc) { - types += "deb-src "; + if (record.Type & pkgSourceList::DebSrc) { + typeStream << "deb-src "; } - TrimWhitespace(types); - entry.Types = types; - + entry.Types = typeStream.str(); + TrimWhitespace(entry.Types); + + // Set URI entry.URIs = record.URI; + + // Set suite entry.Suites = record.Dist; - - std::string components; - for (unsigned short i = 0; i < record.NumSections; i++) { - components += record.Sections[i] + " "; + + // Set components + std::stringstream compStream; + for (const auto& comp : record.Comps) { + compStream << comp << " "; } - TrimWhitespace(components); - entry.Components = components; - - entry.Enabled = !(record.Type & SourcesList::Disabled); - - // Parse additional fields from Comment - std::istringstream comment_stream(record.Comment); - std::string line; - while (std::getline(comment_stream, line)) { - if (line.empty()) continue; - - size_t colon = line.find(':'); - if (colon == std::string::npos) { - // Regular comment line - entry.Comment += line + "\n"; - continue; - } - - std::string key = line.substr(0, colon); - std::string value = line.substr(colon + 1); - TrimWhitespace(key); - TrimWhitespace(value); + entry.Components = compStream.str(); + TrimWhitespace(entry.Components); + + // Set enabled state + entry.Enabled = !(record.Type & pkgSourceList::Disabled); + + return true; +} - if (key == "Architectures") { - entry.Architectures = value; - } else if (key == "Languages") { - entry.Languages = value; - } else if (key == "Targets") { - entry.Targets = value; - } else if (key == "Signed-By") { - entry.SignedBy = value; - } else { - // Unknown field, treat as comment - entry.Comment += line + "\n"; - } +void RDeb822Source::TrimWhitespace(std::string& str) { + const std::string whitespace = " \t\r\n"; + size_t start = str.find_first_not_of(whitespace); + if (start == std::string::npos) { + str.clear(); + return; } - - return true; + size_t end = str.find_last_not_of(whitespace); + str = str.substr(start, end - start + 1); } bool RDeb822Source::ParseStanza(std::wifstream& file, std::map& fields) { std::wstring line; - bool in_stanza = false; - + bool inStanza = false; + while (std::getline(file, line)) { // Skip empty lines if (line.empty()) { - if (in_stanza) { - return true; // End of stanza + if (inStanza) { + return true; } continue; } - + // Skip comments if (line[0] == L'#') { - fields[L"#"] = line.substr(1); continue; } - - // Parse field - size_t colon_pos = line.find(L':'); - if (colon_pos != std::wstring::npos) { - in_stanza = true; - std::wstring field = line.substr(0, colon_pos); - std::wstring value = line.substr(colon_pos + 1); - - // Trim whitespace - while (!field.empty() && std::iswspace(field.back())) field.pop_back(); - while (!value.empty() && std::iswspace(value.front())) value.erase(0, 1); - - fields[field] = value; + + // Check for stanza start + if (line.find(L"Types:") != std::wstring::npos) { + inStanza = true; + } + + if (inStanza) { + size_t colonPos = line.find(L':'); + if (colonPos != std::wstring::npos) { + std::wstring key = line.substr(0, colonPos); + std::wstring value = line.substr(colonPos + 1); + + // Trim whitespace + key.erase(0, key.find_first_not_of(L" \t")); + key.erase(key.find_last_not_of(L" \t") + 1); + value.erase(0, value.find_first_not_of(L" \t")); + value.erase(value.find_last_not_of(L" \t") + 1); + + fields[key] = value; + } } } - - return in_stanza; // Return true if we found any fields -} - -void RDeb822Source::TrimWhitespace(std::string& str) { - // Trim leading whitespace - size_t start = str.find_first_not_of(" \t\r\n"); - if (start == std::string::npos) { - str.clear(); - return; - } - - // Trim trailing whitespace - size_t end = str.find_last_not_of(" \t\r\n"); - str = str.substr(start, end - start + 1); + + return !fields.empty(); } \ No newline at end of file From afe0743c5d729eb1237688b60452c635d62fb1c0 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Mon, 19 May 2025 10:37:10 +0530 Subject: [PATCH 11/43] Fix package manager and lister for Deb822 support --- common/rpackagelister.cc | 6 +----- common/rpackagemanager.cc | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/rpackagelister.cc b/common/rpackagelister.cc index 4060531b1..c9062f602 100644 --- a/common/rpackagelister.cc +++ b/common/rpackagelister.cc @@ -2106,11 +2106,7 @@ bool RPackageLister::xapianSearch(string searchString) bool RPackageLister::isMultiarchSystem() { -#if defined(WITH_APT_MULTIARCH_SUPPORT) - return _system->MultiArchEnabled(); -#else - return false; -#endif + return _system->MultiArchSupport(); } // vim:ts=3:sw=3:et diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc index 9d51a547e..11b30ec71 100644 --- a/common/rpackagemanager.cc +++ b/common/rpackagemanager.cc @@ -5,6 +5,8 @@ #include #include #include +#include +#include // RPackageManager implementation RPackageManager::RPackageManager(pkgPackageManager *pm) : pm(pm) {} @@ -20,7 +22,7 @@ pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork(int statusFd) } #else pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork() { - return (pm->Go() == false) ? pkgPackageManager::Failed : Res; + return (pm->Go(nullptr) == false) ? pkgPackageManager::Failed : Res; } #endif From 63b3c5e5546b8bfd0f12f941686edf878a2c8cb4 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 20 May 2025 10:05:50 +0530 Subject: [PATCH 12/43] Fix compilation issues --- common/rpackagelister.cc | 2 +- common/rpackagemanager.h | 4 +++- common/rsource_deb822.cc | 34 +++++++++------------------------- common/rsource_deb822.h | 1 + 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/common/rpackagelister.cc b/common/rpackagelister.cc index c9062f602..dbc84cda0 100644 --- a/common/rpackagelister.cc +++ b/common/rpackagelister.cc @@ -2106,7 +2106,7 @@ bool RPackageLister::xapianSearch(string searchString) bool RPackageLister::isMultiarchSystem() { - return _system->MultiArchSupport(); + return _system->MultiArchSupported(); } // vim:ts=3:sw=3:et diff --git a/common/rpackagemanager.h b/common/rpackagemanager.h index dc16b4bbd..e73ad6c45 100644 --- a/common/rpackagemanager.h +++ b/common/rpackagemanager.h @@ -69,7 +69,9 @@ class RPackageManager { } #else pkgPackageManager::OrderResult DoInstallPostFork() { - return (pm->Go() == false) ? pkgPackageManager::Failed : Res; + if (pm == NULL) + return pkgPackageManager::Failed; + return (pm->Go(NULL) == false) ? pkgPackageManager::Failed : Res; } #endif diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index b9b1bb7df..d1423154f 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -26,40 +26,38 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vectorError(_("Missing Types field in %s"), path.c_str()); } - entry.Types = APT::String::FromUTF8(fields[L"Types"]); + entry.Types = APT::String::FromUTF8(UTF8String(fields[L"Types"])); if (fields.find(L"URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); + entry.URIs = APT::String::FromUTF8(UTF8String(fields[L"URIs"])); if (fields.find(L"Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); + entry.Suites = APT::String::FromUTF8(UTF8String(fields[L"Suites"])); - // Optional fields if (fields.find(L"Components") != fields.end()) { - entry.Components = APT::String::FromUTF8(fields[L"Components"]); + entry.Components = APT::String::FromUTF8(UTF8String(fields[L"Components"])); } if (fields.find(L"Signed-By") != fields.end()) { - entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); + entry.SignedBy = APT::String::FromUTF8(UTF8String(fields[L"Signed-By"])); } if (fields.find(L"Architectures") != fields.end()) { - entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); + entry.Architectures = APT::String::FromUTF8(UTF8String(fields[L"Architectures"])); } if (fields.find(L"Languages") != fields.end()) { - entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); + entry.Languages = APT::String::FromUTF8(UTF8String(fields[L"Languages"])); } if (fields.find(L"Targets") != fields.end()) { - entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); + entry.Targets = APT::String::FromUTF8(UTF8String(fields[L"Targets"])); } - entry.Enabled = true; // Default to enabled + entry.Enabled = true; entries.push_back(entry); fields.clear(); } @@ -105,7 +103,6 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector #include #include +#include class RDeb822Source { public: From 94cdb17660b2781fbb0cab98cc6515bcff25a90a Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 20 May 2025 12:20:21 +0530 Subject: [PATCH 13/43] Fix: use MultiArchSupported() --- common/rsource_deb822.cc | 68 ++++++++++++++++++++------------ tests/test_deb822_integration.cc | 2 +- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index d1423154f..d4dc75ac8 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -26,42 +26,44 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vectorError(_("Missing Types field in %s"), path.c_str()); } - entry.Types = APT::String::FromUTF8(UTF8String(fields[L"Types"])); - + entry.Types = APT::String::FromUTF8(fields[L"Types"]); + if (fields.find(L"URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = APT::String::FromUTF8(UTF8String(fields[L"URIs"])); - + entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); + if (fields.find(L"Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = APT::String::FromUTF8(UTF8String(fields[L"Suites"])); - + entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); + + // Optional fields if (fields.find(L"Components") != fields.end()) { - entry.Components = APT::String::FromUTF8(UTF8String(fields[L"Components"])); + entry.Components = APT::String::FromUTF8(fields[L"Components"]); } if (fields.find(L"Signed-By") != fields.end()) { - entry.SignedBy = APT::String::FromUTF8(UTF8String(fields[L"Signed-By"])); + entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); } if (fields.find(L"Architectures") != fields.end()) { - entry.Architectures = APT::String::FromUTF8(UTF8String(fields[L"Architectures"])); + entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); } if (fields.find(L"Languages") != fields.end()) { - entry.Languages = APT::String::FromUTF8(UTF8String(fields[L"Languages"])); + entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); } if (fields.find(L"Targets") != fields.end()) { - entry.Targets = APT::String::FromUTF8(UTF8String(fields[L"Targets"])); + entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); } - entry.Enabled = true; + entry.Enabled = true; // Default to enabled entries.push_back(entry); fields.clear(); } - + return true; } @@ -70,12 +72,12 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vectorError(_("Cannot write to %s"), path.c_str()); } - + for (const auto& entry : entries) { if (!entry.Enabled) { file << L"# Disabled: "; } - + file << L"Types: " << APT::String::ToUTF8(entry.Types) << std::endl; file << L"URIs: " << APT::String::ToUTF8(entry.URIs) << std::endl; file << L"Suites: " << APT::String::ToUTF8(entry.Suites) << std::endl; @@ -98,11 +100,12 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector Date: Tue, 20 May 2025 12:30:03 +0530 Subject: [PATCH 14/43] Fix: Use SourcesList::SourceRecord for Deb822 conversion functions to resolve type mismatch and build errors --- common/rsource_deb822.cc | 78 +++++++++++++++++++++++++--------------- common/rsource_deb822.h | 4 +-- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index d4dc75ac8..42e1f9f60 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -104,8 +104,7 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector comps; std::istringstream compStream(entry.Components); std::string comp; while (std::getline(compStream, comp, ' ')) { TrimWhitespace(comp); if (!comp.empty()) { - record.Comps.push_back(comp); + comps.push_back(comp); } } - + // Set Sections + if (record.Sections) delete[] record.Sections; + record.NumSections = comps.size(); + if (record.NumSections > 0) { + record.Sections = new std::string[record.NumSections]; + for (unsigned short i = 0; i < record.NumSections; ++i) { + record.Sections[i] = comps[i]; + } + } else { + record.Sections = nullptr; + } + // Store additional fields in Comment + std::ostringstream comment; + if (!entry.SignedBy.empty()) comment << "Signed-By: " << entry.SignedBy << "\n"; + if (!entry.Architectures.empty()) comment << "Architectures: " << entry.Architectures << "\n"; + if (!entry.Languages.empty()) comment << "Languages: " << entry.Languages << "\n"; + if (!entry.Targets.empty()) comment << "Targets: " << entry.Targets << "\n"; + if (!entry.Comment.empty()) comment << entry.Comment << "\n"; + record.Comment = comment.str(); return true; } -bool RDeb822Source::ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry) { - // Set types +bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) { std::stringstream typeStream; - if (record.Type & pkgSourceList::Deb) { + if (record.Type & SourcesList::Deb) { typeStream << "deb "; } - if (record.Type & pkgSourceList::DebSrc) { + if (record.Type & SourcesList::DebSrc) { typeStream << "deb-src "; } entry.Types = typeStream.str(); TrimWhitespace(entry.Types); - - // Set URI entry.URIs = record.URI; - - // Set suite entry.Suites = record.Dist; - - // Set components + // Components std::stringstream compStream; - for (const auto& comp : record.Comps) { - compStream << comp << " "; + for (unsigned short i = 0; i < record.NumSections; ++i) { + compStream << record.Sections[i] << " "; } entry.Components = compStream.str(); TrimWhitespace(entry.Components); - - // Set enabled state - entry.Enabled = !(record.Type & pkgSourceList::Disabled); - + entry.Enabled = !(record.Type & SourcesList::Disabled); + // Parse additional fields from Comment + std::istringstream commentStream(record.Comment); + std::string line; + while (std::getline(commentStream, line)) { + if (line.find("Signed-By:") == 0) entry.SignedBy = line.substr(10); + else if (line.find("Architectures:") == 0) entry.Architectures = line.substr(14); + else if (line.find("Languages:") == 0) entry.Languages = line.substr(10); + else if (line.find("Targets:") == 0) entry.Targets = line.substr(8); + else entry.Comment += line + "\n"; + } + TrimWhitespace(entry.SignedBy); + TrimWhitespace(entry.Architectures); + TrimWhitespace(entry.Languages); + TrimWhitespace(entry.Targets); + TrimWhitespace(entry.Comment); return true; } diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 94cd777d6..7db3db1f7 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -36,8 +36,8 @@ class RDeb822Source { static bool ParseDeb822File(const std::string& path, std::vector& entries); static bool WriteDeb822File(const std::string& path, const std::vector& entries); - static bool ConvertToSourceRecord(const Deb822Entry& entry, pkgSourceList::SourceRecord& record); - static bool ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry); + static bool ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record); + static bool ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry); private: static bool ParseStanza(std::wifstream& file, std::map& fields); From 77dce042a77f6cb3547c266f6b33dc418bc14a5a Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 20 May 2025 12:38:54 +0530 Subject: [PATCH 15/43] Fix: Add missing source conversion --- common/rsource_deb822.cc | 1 + common/rsource_deb822.h | 1 + 2 files changed, 2 insertions(+) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 42e1f9f60..350b9c474 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -3,6 +3,7 @@ * Copyright (c) 2025 Synaptic development team */ +#include "rsources.h" #include "rsource_deb822.h" #include #include diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index 7db3db1f7..fc58e4dab 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -18,6 +18,7 @@ #include #include #include +#include "rsources.h" class RDeb822Source { public: From e5e76db98e4327ef499255d22a643ad456842a22 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 20 May 2025 12:58:19 +0530 Subject: [PATCH 16/43] Fix: Replace non-existent APT::String UTF-8 conversion with standard C++ wstring/string conversion helpers in Deb822 support --- common/rsource_deb822.cc | 43 +++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 350b9c474..af4c77c0a 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -17,6 +17,17 @@ #include #include +// Helper functions for conversion +static std::string wstring_to_utf8(const std::wstring& wstr) { + std::wstring_convert> conv; + return conv.to_bytes(wstr); +} + +static std::wstring utf8_to_wstring(const std::string& str) { + std::wstring_convert> conv; + return conv.from_bytes(str); +} + bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::wifstream file(path); if (!file) { @@ -31,33 +42,33 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vectorError(_("Missing Types field in %s"), path.c_str()); } - entry.Types = APT::String::FromUTF8(fields[L"Types"]); + entry.Types = wstring_to_utf8(fields[L"Types"]); if (fields.find(L"URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); + entry.URIs = wstring_to_utf8(fields[L"URIs"]); if (fields.find(L"Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); + entry.Suites = wstring_to_utf8(fields[L"Suites"]); // Optional fields if (fields.find(L"Components") != fields.end()) { - entry.Components = APT::String::FromUTF8(fields[L"Components"]); + entry.Components = wstring_to_utf8(fields[L"Components"]); } if (fields.find(L"Signed-By") != fields.end()) { - entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); + entry.SignedBy = wstring_to_utf8(fields[L"Signed-By"]); } if (fields.find(L"Architectures") != fields.end()) { - entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); + entry.Architectures = wstring_to_utf8(fields[L"Architectures"]); } if (fields.find(L"Languages") != fields.end()) { - entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); + entry.Languages = wstring_to_utf8(fields[L"Languages"]); } if (fields.find(L"Targets") != fields.end()) { - entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); + entry.Targets = wstring_to_utf8(fields[L"Targets"]); } entry.Enabled = true; // Default to enabled @@ -79,24 +90,24 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector Date: Tue, 20 May 2025 13:15:19 +0530 Subject: [PATCH 17/43] Fix: Remove function redefinitions, add missing signedBy member --- common/rpackagemanager.cc | 24 +++++---- common/rpackagemanager.h | 109 ++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc index 11b30ec71..b8b6a5197 100644 --- a/common/rpackagemanager.cc +++ b/common/rpackagemanager.cc @@ -26,6 +26,12 @@ pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork() { } #endif +// Helper function to check if a string starts with a prefix +static bool starts_with(const std::string& str, const std::string& prefix) { + return str.size() >= prefix.size() && + str.compare(0, prefix.size(), prefix) == 0; +} + // RDeb822Source implementation RDeb822Source::RDeb822Source() : enabled(true) {} @@ -57,11 +63,11 @@ bool RDeb822Source::isValid() const { if (uri.empty()) continue; // Check URI scheme - if (uri.starts_with("http://") || - uri.starts_with("https://") || - uri.starts_with("ftp://") || - uri.starts_with("file://") || - uri.starts_with("cdrom:")) { + if (starts_with(uri, "http://") || + starts_with(uri, "https://") || + starts_with(uri, "ftp://") || + starts_with(uri, "file://") || + starts_with(uri, "cdrom:")) { hasValidUri = true; } } @@ -118,7 +124,7 @@ RDeb822Source RDeb822Source::fromString(const std::string& content) { while (std::getline(stream, line)) { // Skip comments and empty lines - if (line.empty() || line[0] == '#') { + if (line.empty() || starts_with(line, "#")) { continue; } @@ -279,7 +285,7 @@ std::vector RSourceManager::parseSources(const std::string& conte } // Check for source block start - if (line.starts_with("#") && line.find("Modernized") != std::string::npos) { + if (starts_with(line, "#") && line.find("Modernized") != std::string::npos) { if (!currentFields.empty()) { RDeb822Source source = createSourceFromFields(currentFields); if (source.isValid()) { @@ -292,7 +298,7 @@ std::vector RSourceManager::parseSources(const std::string& conte } // Skip other comments - if (line.starts_with("#")) { + if (starts_with(line, "#")) { continue; } @@ -305,7 +311,7 @@ std::vector RSourceManager::parseSources(const std::string& conte // Look ahead for continuation lines while (std::getline(iss, line)) { line = trim(line); - if (line.empty() || line.starts_with("#") || line.find(':') != std::string::npos) { + if (line.empty() || starts_with(line, "#") || line.find(':') != std::string::npos) { // Put the line back for the next iteration iss.seekg(-static_cast(line.length() + 1), std::ios_base::cur); break; diff --git a/common/rpackagemanager.h b/common/rpackagemanager.h index e73ad6c45..922d80900 100644 --- a/common/rpackagemanager.h +++ b/common/rpackagemanager.h @@ -22,6 +22,16 @@ * USA */ +#ifndef RPACKAGEMANAGER_H +#define RPACKAGEMANAGER_H + +#include +#include +#include +#include +#include +#include + // We need a different package manager, since we need to do the // DoInstall() process in two steps when forking. Without that, // the forked package manager would be updated with the new @@ -36,49 +46,92 @@ // to export the functionality we need, so that we may avoid this // ugly hack. -#include -#include -#include -#include -#include - #define protected public #include #undef protected -#ifndef RPACKAGEMANAGER_H -#define RPACKAGEMANAGER_H +class RDeb822Source { +public: + RDeb822Source(); + RDeb822Source(const std::string& types, const std::string& uris, + const std::string& suites, const std::string& components = ""); -class RPackageManager { + bool isValid() const; + std::string toString() const; + static RDeb822Source fromString(const std::string& content); + bool operator==(const RDeb822Source& other) const; + bool operator!=(const RDeb822Source& other) const; + + std::string getTypes() const { return types; } + std::string getUris() const { return uris; } + std::string getSuites() const { return suites; } + std::string getComponents() const { return components; } + std::string getSignedBy() const { return signedBy; } + bool isEnabled() const { return enabled; } + +private: + std::string types; + std::string uris; + std::string suites; + std::string components; + std::string signedBy; + bool enabled; +}; - protected: +class RSourceManager { +public: + RSourceManager(); + explicit RSourceManager(const std::string& sourcesDir); - pkgPackageManager::OrderResult Res; + bool addSource(const RDeb822Source& source); + bool removeSource(const RDeb822Source& source); + bool updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource); + std::vector getSources() const; + bool loadSources(); + bool saveSources() const; + bool updateAptSources(); + bool reloadAptCache(); + bool validateSourceFile(const std::string& filename) const; - public: +private: + std::string sourcesDir; + std::vector sources; - pkgPackageManager *pm; + std::vector parseSources(const std::string& content); + RDeb822Source createSourceFromFields(const std::map& fields); + bool writeSourceFile(const std::string& filename, const RDeb822Source& source) const; + RDeb822Source readSourceFile(const std::string& filename) const; + std::string getSourceFilename(const RDeb822Source& source) const; + std::string trim(const std::string& str) const; +}; + +class RPackageManager { +protected: + pkgPackageManager::OrderResult Res; + +public: + pkgPackageManager *pm; + + pkgPackageManager::OrderResult DoInstallPreFork() { + Res = pm->OrderInstall(); + return Res; + } - pkgPackageManager::OrderResult DoInstallPreFork() { - Res = pm->OrderInstall(); - return Res; - } #ifdef WITH_DPKG_STATUSFD - pkgPackageManager::OrderResult DoInstallPostFork(int statusFd=-1) { - return (pm->Go(statusFd) == false) ? pkgPackageManager::Failed : Res; - } + pkgPackageManager::OrderResult DoInstallPostFork(int statusFd=-1) { + return (pm->Go(statusFd) == false) ? pkgPackageManager::Failed : Res; + } #else - pkgPackageManager::OrderResult DoInstallPostFork() { - if (pm == NULL) - return pkgPackageManager::Failed; - return (pm->Go(NULL) == false) ? pkgPackageManager::Failed : Res; - } + pkgPackageManager::OrderResult DoInstallPostFork() { + if (pm == NULL) + return pkgPackageManager::Failed; + return (pm->Go(NULL) == false) ? pkgPackageManager::Failed : Res; + } #endif - RPackageManager(pkgPackageManager *pm) : pm(pm) {} - + RPackageManager(pkgPackageManager *pm) : pm(pm) {} }; -#endif +#endif // RPACKAGEMANAGER_H // vim:ts=3:sw=3:et From 100d25a179a69d67532da823c27246748a6470f2 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 20 May 2025 13:27:17 +0530 Subject: [PATCH 18/43] Fix: Remove function redefinitions, replace APT::String::Strip with trim, and use setters for RDeb822Source fields --- common/rpackagemanager.cc | 46 +++++++++++++++------------------------ common/rpackagemanager.h | 8 +++++++ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/common/rpackagemanager.cc b/common/rpackagemanager.cc index b8b6a5197..88da10b02 100644 --- a/common/rpackagemanager.cc +++ b/common/rpackagemanager.cc @@ -9,22 +9,6 @@ #include // RPackageManager implementation -RPackageManager::RPackageManager(pkgPackageManager *pm) : pm(pm) {} - -pkgPackageManager::OrderResult RPackageManager::DoInstallPreFork() { - Res = pm->OrderInstall(); - return Res; -} - -#ifdef WITH_DPKG_STATUSFD -pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork(int statusFd) { - return (pm->Go(statusFd) == false) ? pkgPackageManager::Failed : Res; -} -#else -pkgPackageManager::OrderResult RPackageManager::DoInstallPostFork() { - return (pm->Go(nullptr) == false) ? pkgPackageManager::Failed : Res; -} -#endif // Helper function to check if a string starts with a prefix static bool starts_with(const std::string& str, const std::string& prefix) { @@ -138,8 +122,8 @@ RDeb822Source RDeb822Source::fromString(const std::string& content) { std::string value = line.substr(colonPos + 1); // Trim whitespace - key = std::string(APT::String::Strip(key)); - value = std::string(APT::String::Strip(value)); + key = trim(key); + value = trim(value); if (key == "Types") { source.types = value; @@ -337,18 +321,13 @@ std::vector RSourceManager::parseSources(const std::string& conte RDeb822Source RSourceManager::createSourceFromFields(const std::map& fields) { RDeb822Source source; - // Set required fields - source.types = fields.count("Types") ? fields.at("Types") : ""; - source.uris = fields.count("URIs") ? fields.at("URIs") : ""; - source.suites = fields.count("Suites") ? fields.at("Suites") : ""; - source.components = fields.count("Components") ? fields.at("Components") : ""; - + if (fields.count("Types")) source.setTypes(fields.at("Types")); + if (fields.count("URIs")) source.setUris(fields.at("URIs")); + if (fields.count("Suites")) source.setSuites(fields.at("Suites")); + if (fields.count("Components")) source.setComponents(fields.at("Components")); // Set optional fields - if (fields.count("Signed-By")) { - source.signedBy = fields.at("Signed-By"); - } - + if (fields.count("Signed-By")) source.setSignedBy(fields.at("Signed-By")); return source; } @@ -432,4 +411,15 @@ std::string RSourceManager::trim(const std::string& str) const } size_t end = str.find_last_not_of(whitespace); return str.substr(start, end - start + 1); +} + +// Implement RDeb822Source::trim +std::string RDeb822Source::trim(const std::string& str) { + const std::string whitespace = " \t\r\n"; + size_t start = str.find_first_not_of(whitespace); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); } \ No newline at end of file diff --git a/common/rpackagemanager.h b/common/rpackagemanager.h index 922d80900..cbe0a7ad0 100644 --- a/common/rpackagemanager.h +++ b/common/rpackagemanager.h @@ -69,6 +69,14 @@ class RDeb822Source { std::string getSignedBy() const { return signedBy; } bool isEnabled() const { return enabled; } + void setTypes(const std::string& t) { types = t; } + void setUris(const std::string& u) { uris = u; } + void setSuites(const std::string& s) { suites = s; } + void setComponents(const std::string& c) { components = c; } + void setSignedBy(const std::string& s) { signedBy = s; } + + static std::string trim(const std::string& str); + private: std::string types; std::string uris; From e8dbdf5587c14ffd4e5e991f26ae9919b3e754a7 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Wed, 21 May 2025 23:20:14 +0530 Subject: [PATCH 19/43] Add dialog before auto-conversion 2) Fix reading from /etc/apt/sources.list.d/*.sources --- common/rdeb822source.cc | 92 ++++++++++++++++++ common/rdeb822source.h | 38 ++++++++ common/rsource_deb822.cc | 127 +++++++++--------------- common/rsourcemanager.cc | 205 +++++++++++++++++++++++++++++++++++++++ common/rsourcemanager.h | 26 +++++ common/rsources.cc | 22 ++--- gtk/rgrepositorywin.cc | 30 ++++++ gtk/rgrepositorywin.h | 11 +++ 8 files changed, 460 insertions(+), 91 deletions(-) create mode 100644 common/rdeb822source.cc create mode 100644 common/rdeb822source.h create mode 100644 common/rsourcemanager.cc create mode 100644 common/rsourcemanager.h diff --git a/common/rdeb822source.cc b/common/rdeb822source.cc new file mode 100644 index 000000000..8e82c4692 --- /dev/null +++ b/common/rdeb822source.cc @@ -0,0 +1,92 @@ +#include "rdeb822source.h" +#include +#include +#include +#include + +RDeb822Source::RDeb822Source() + : enabled(true) +{ +} + +RDeb822Source::RDeb822Source(const std::string& types, const std::string& uris, + const std::string& suites, const std::string& components) + : types(types), uris(uris), suites(suites), components(components), enabled(true) +{ +} + +bool RDeb822Source::isValid() const { + return !types.empty() && !uris.empty() && !suites.empty(); +} + +std::string RDeb822Source::toString() const { + std::stringstream ss; + if (!enabled) { + ss << "Types: " << types << "\n"; + ss << "URIs: " << uris << "\n"; + ss << "Suites: " << suites << "\n"; + if (!components.empty()) { + ss << "Components: " << components << "\n"; + } + if (!signedBy.empty()) { + ss << "Signed-By: " << signedBy << "\n"; + } + } else { + ss << "# " << types << " " << uris << " " << suites; + if (!components.empty()) { + ss << " " << components; + } + if (!signedBy.empty()) { + ss << " [signed-by=" << signedBy << "]"; + } + } + return ss.str(); +} + +RDeb822Source RDeb822Source::fromString(const std::string& content) { + RDeb822Source source; + std::istringstream iss(content); + std::string line; + + while (std::getline(iss, line)) { + line = trim(line); + if (line.empty() || line[0] == '#') continue; + + size_t colon = line.find(':'); + if (colon == std::string::npos) continue; + + std::string key = trim(line.substr(0, colon)); + std::string value = trim(line.substr(colon + 1)); + + if (key == "Types") source.setTypes(value); + else if (key == "URIs") source.setURIs(value); + else if (key == "Suites") source.setSuites(value); + else if (key == "Components") source.setComponents(value); + else if (key == "Signed-By") source.setSignedBy(value); + } + + return source; +} + +bool RDeb822Source::operator==(const RDeb822Source& other) const { + return types == other.types && + uris == other.uris && + suites == other.suites && + components == other.components && + signedBy == other.signedBy && + enabled == other.enabled; +} + +bool RDeb822Source::operator!=(const RDeb822Source& other) const { + return !(*this == other); +} + +std::string RDeb822Source::trim(const std::string& str) { + const std::string whitespace = " \t\r\n"; + size_t start = str.find_first_not_of(whitespace); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); +} \ No newline at end of file diff --git a/common/rdeb822source.h b/common/rdeb822source.h new file mode 100644 index 000000000..7fd9d5c78 --- /dev/null +++ b/common/rdeb822source.h @@ -0,0 +1,38 @@ +class RDeb822Source { +public: + RDeb822Source(); + RDeb822Source(const std::string& types, const std::string& uris, + const std::string& suites, const std::string& components = ""); + + bool isValid() const; + std::string toString() const; + static RDeb822Source fromString(const std::string& content); + bool operator==(const RDeb822Source& other) const; + bool operator!=(const RDeb822Source& other) const; + + // Getters + std::string getTypes() const { return types; } + std::string getURIs() const { return uris; } + std::string getSuites() const { return suites; } + std::string getComponents() const { return components; } + std::string getSignedBy() const { return signedBy; } + bool isEnabled() const { return enabled; } + + // Setters + void setTypes(const std::string& t) { types = t; } + void setURIs(const std::string& u) { uris = u; } + void setSuites(const std::string& s) { suites = s; } + void setComponents(const std::string& c) { components = c; } + void setSignedBy(const std::string& s) { signedBy = s; } + void setEnabled(bool e) { enabled = e; } + + static std::string trim(const std::string& str); + +private: + std::string types; // Changed from type to types + std::string uris; // Changed from uri to uris + std::string suites; // Changed from dist to suites + std::string components; // Changed from sections to components + std::string signedBy; + bool enabled; +}; \ No newline at end of file diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index af4c77c0a..d812a067b 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -3,7 +3,6 @@ * Copyright (c) 2025 Synaptic development team */ -#include "rsources.h" #include "rsource_deb822.h" #include #include @@ -17,17 +16,6 @@ #include #include -// Helper functions for conversion -static std::string wstring_to_utf8(const std::wstring& wstr) { - std::wstring_convert> conv; - return conv.to_bytes(wstr); -} - -static std::wstring utf8_to_wstring(const std::string& str) { - std::wstring_convert> conv; - return conv.from_bytes(str); -} - bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::wifstream file(path); if (!file) { @@ -42,33 +30,33 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vectorError(_("Missing Types field in %s"), path.c_str()); } - entry.Types = wstring_to_utf8(fields[L"Types"]); + entry.Types = APT::String::FromUTF8(fields[L"Types"]); if (fields.find(L"URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = wstring_to_utf8(fields[L"URIs"]); + entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); if (fields.find(L"Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = wstring_to_utf8(fields[L"Suites"]); + entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); // Optional fields if (fields.find(L"Components") != fields.end()) { - entry.Components = wstring_to_utf8(fields[L"Components"]); + entry.Components = APT::String::FromUTF8(fields[L"Components"]); } if (fields.find(L"Signed-By") != fields.end()) { - entry.SignedBy = wstring_to_utf8(fields[L"Signed-By"]); + entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); } if (fields.find(L"Architectures") != fields.end()) { - entry.Architectures = wstring_to_utf8(fields[L"Architectures"]); + entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); } if (fields.find(L"Languages") != fields.end()) { - entry.Languages = wstring_to_utf8(fields[L"Languages"]); + entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); } if (fields.find(L"Targets") != fields.end()) { - entry.Targets = wstring_to_utf8(fields[L"Targets"]); + entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); } entry.Enabled = true; // Default to enabled @@ -90,24 +78,24 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector comps; std::istringstream compStream(entry.Components); std::string comp; + record.Comps.clear(); while (std::getline(compStream, comp, ' ')) { TrimWhitespace(comp); if (!comp.empty()) { - comps.push_back(comp); - } - } - // Set Sections - if (record.Sections) delete[] record.Sections; - record.NumSections = comps.size(); - if (record.NumSections > 0) { - record.Sections = new std::string[record.NumSections]; - for (unsigned short i = 0; i < record.NumSections; ++i) { - record.Sections[i] = comps[i]; + record.Comps.push_back(comp); } - } else { - record.Sections = nullptr; } - // Store additional fields in Comment - std::ostringstream comment; - if (!entry.SignedBy.empty()) comment << "Signed-By: " << entry.SignedBy << "\n"; - if (!entry.Architectures.empty()) comment << "Architectures: " << entry.Architectures << "\n"; - if (!entry.Languages.empty()) comment << "Languages: " << entry.Languages << "\n"; - if (!entry.Targets.empty()) comment << "Targets: " << entry.Targets << "\n"; - if (!entry.Comment.empty()) comment << entry.Comment << "\n"; - record.Comment = comment.str(); + return true; } -bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) { +bool RDeb822Source::ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry) { + // Set types std::stringstream typeStream; - if (record.Type & SourcesList::Deb) { + if (record.Type & pkgSourceList::Deb) { typeStream << "deb "; } - if (record.Type & SourcesList::DebSrc) { + if (record.Type & pkgSourceList::DebSrc) { typeStream << "deb-src "; } entry.Types = typeStream.str(); TrimWhitespace(entry.Types); + + // Set URI entry.URIs = record.URI; + + // Set suite entry.Suites = record.Dist; - // Components + + // Set components std::stringstream compStream; - for (unsigned short i = 0; i < record.NumSections; ++i) { - compStream << record.Sections[i] << " "; + for (const auto& comp : record.Comps) { + compStream << comp << " "; } entry.Components = compStream.str(); TrimWhitespace(entry.Components); - entry.Enabled = !(record.Type & SourcesList::Disabled); - // Parse additional fields from Comment - std::istringstream commentStream(record.Comment); - std::string line; - while (std::getline(commentStream, line)) { - if (line.find("Signed-By:") == 0) entry.SignedBy = line.substr(10); - else if (line.find("Architectures:") == 0) entry.Architectures = line.substr(14); - else if (line.find("Languages:") == 0) entry.Languages = line.substr(10); - else if (line.find("Targets:") == 0) entry.Targets = line.substr(8); - else entry.Comment += line + "\n"; - } - TrimWhitespace(entry.SignedBy); - TrimWhitespace(entry.Architectures); - TrimWhitespace(entry.Languages); - TrimWhitespace(entry.Targets); - TrimWhitespace(entry.Comment); + + // Set enabled state + entry.Enabled = !(record.Type & pkgSourceList::Disabled); + return true; } diff --git a/common/rsourcemanager.cc b/common/rsourcemanager.cc new file mode 100644 index 000000000..ab0e77c01 --- /dev/null +++ b/common/rsourcemanager.cc @@ -0,0 +1,205 @@ +#include "rsourcemanager.h" +#include +#include +#include + +RSourceManager::RSourceManager() : useDeb822Format(false) { + // Check if sources.list.d exists and contains .sources files + useDeb822Format = std::filesystem::exists("/etc/apt/sources.list.d") && + !std::filesystem::is_empty("/etc/apt/sources.list.d"); +} + +RSourceManager::~RSourceManager() { +} + +bool RSourceManager::loadSources() { + sources.clear(); + + // Try loading Deb822 sources first + if (useDeb822Format) { + if (loadDeb822Sources()) { + return true; + } + } + + // Fall back to legacy format + return loadLegacySources(); +} + +bool RSourceManager::loadDeb822Sources() { + const std::string sourcesDir = "/etc/apt/sources.list.d"; + + try { + for (const auto& entry : std::filesystem::directory_iterator(sourcesDir)) { + if (entry.path().extension() == ".sources") { + std::ifstream file(entry.path()); + if (!file.is_open()) continue; + + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + RDeb822Source source = RDeb822Source::fromString(content); + if (source.isValid()) { + sources.push_back(source); + } + } + } + return !sources.empty(); + } catch (const std::exception& e) { + return false; + } +} + +bool RSourceManager::loadLegacySources() { + std::ifstream file("/etc/apt/sources.list"); + if (!file.is_open()) return false; + + std::string line; + while (std::getline(file, line)) { + line = trim(line); + if (line.empty() || line[0] == '#') continue; + + std::istringstream iss(line); + std::string type, uri, dist, components; + iss >> type >> uri >> dist; + + if (type.empty() || uri.empty() || dist.empty()) continue; + + RDeb822Source source; + source.setTypes(type); + source.setURIs(uri); + source.setSuites(dist); + + // Read components + std::string comp; + while (iss >> comp) { + if (!components.empty()) components += " "; + components += comp; + } + source.setComponents(components); + + sources.push_back(source); + } + + return true; +} + +bool RSourceManager::saveSources() { + if (useDeb822Format) { + return saveDeb822Sources(); + } + return saveLegacySources(); +} + +bool RSourceManager::saveDeb822Sources() { + const std::string sourcesDir = "/etc/apt/sources.list.d"; + + try { + // Create directory if it doesn't exist + if (!std::filesystem::exists(sourcesDir)) { + std::filesystem::create_directories(sourcesDir); + } + + // Save to debian.sources + std::ofstream file(sourcesDir + "/debian.sources"); + if (!file.is_open()) return false; + + for (const auto& source : sources) { + file << source.toString() << "\n\n"; + } + + return true; + } catch (const std::exception& e) { + return false; + } +} + +bool RSourceManager::saveLegacySources() { + std::ofstream file("/etc/apt/sources.list"); + if (!file.is_open()) return false; + + for (const auto& source : sources) { + if (!source.isEnabled()) continue; + + file << source.getTypes() << " " + << source.getURIs() << " " + << source.getSuites(); + + if (!source.getComponents().empty()) { + file << " " << source.getComponents(); + } + + if (!source.getSignedBy().empty()) { + file << " [signed-by=" << source.getSignedBy() << "]"; + } + + file << "\n"; + } + + return true; +} + +std::vector RSourceManager::getSources() const { + return sources; +} + +void RSourceManager::addSource(const RDeb822Source& source) { + sources.push_back(source); +} + +void RSourceManager::removeSource(const RDeb822Source& source) { + sources.erase( + std::remove_if(sources.begin(), sources.end(), + [&source](const RDeb822Source& s) { return s == source; }), + sources.end() + ); +} + +void RSourceManager::updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource) { + for (auto& source : sources) { + if (source == oldSource) { + source = newSource; + break; + } + } +} + +RDeb822Source RSourceManager::createSourceFromFields(const std::map& fields) { + RDeb822Source source; + + if (fields.count("Types")) source.setTypes(fields.at("Types")); + if (fields.count("URIs")) source.setURIs(fields.at("URIs")); + if (fields.count("Suites")) source.setSuites(fields.at("Suites")); + if (fields.count("Components")) source.setComponents(fields.at("Components")); + if (fields.count("Signed-By")) source.setSignedBy(fields.at("Signed-By")); + + return source; +} + +std::string RSourceManager::trim(const std::string& str) { + const std::string whitespace = " \t\r\n"; + size_t start = str.find_first_not_of(whitespace); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); +} + +bool RSourceManager::shouldConvertToDeb822() const { + return !useDeb822Format && !sources.empty(); +} + +bool RSourceManager::askUserAboutConversion() { + GtkWidget* dialog = gtk_message_dialog_new(NULL, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Would you like to convert your sources to the new Deb822 format?\n" + "This will create files in /etc/apt/sources.list.d/"); + + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return result == GTK_RESPONSE_YES; +} \ No newline at end of file diff --git a/common/rsourcemanager.h b/common/rsourcemanager.h new file mode 100644 index 000000000..3418a0204 --- /dev/null +++ b/common/rsourcemanager.h @@ -0,0 +1,26 @@ +class RSourceManager { +public: + RSourceManager(); + ~RSourceManager(); + + bool loadSources(); + bool saveSources(); + std::vector getSources() const; + void addSource(const RDeb822Source& source); + void removeSource(const RDeb822Source& source); + void updateSource(const RDeb822Source& oldSource, const RDeb822Source& newSource); + + static RDeb822Source createSourceFromFields(const std::map& fields); + static std::string trim(const std::string& str); + +private: + bool loadLegacySources(); + bool loadDeb822Sources(); + bool saveLegacySources(); + bool saveDeb822Sources(); + bool shouldConvertToDeb822() const; + bool askUserAboutConversion(); + + std::vector sources; + bool useDeb822Format; +}; \ No newline at end of file diff --git a/common/rsources.cc b/common/rsources.cc index a7d059dd3..f38ed36d1 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -220,21 +220,21 @@ bool SourcesList::ReadSources() { bool Res = true; - // Read classic sources.list format + // First try to read Deb822 format sources + string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); + if (FileExists(Deb822Parts) == true) { + Res &= ReadDeb822SourceDir(Deb822Parts); + } + + // Then read classic sources.list format string Parts = _config->FindDir("Dir::Etc::sourceparts"); - if (FileExists(Parts) == true) + if (FileExists(Parts) == true) { Res &= ReadSourceDir(Parts); + } string Main = _config->FindFile("Dir::Etc::sourcelist"); - if (FileExists(Main) == true) + if (FileExists(Main) == true) { Res &= ReadSourcePart(Main); - - // Read Deb822 format sources - string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); - if (FileExists(Deb822Parts) == true) - Res &= ReadDeb822SourceDir(Deb822Parts); - string Deb822Main = _config->FindFile("Dir::Etc::sourcelist.d") + "/debian.sources"; - if (FileExists(Deb822Main) == true) - Res &= ReadDeb822SourcePart(Deb822Main); + } return Res; } diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 9221c5c6d..ff23a4e4f 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -804,3 +804,33 @@ void RGRepositoryEditor::DoUpDown(GtkWidget *self, gpointer data) else me->_lst.SwapSources(rec, rec_p); } + +bool RGRepositoryEditor::ConvertToDeb822() { + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("Convert to Deb822 format?")); + + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + _("This will convert your sources to the new Deb822 format.\n" + "The conversion will be done in-place and cannot be undone.\n\n" + "Do you want to proceed?")); + + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return (result == GTK_RESPONSE_YES); +} + +void RGRepositoryEditor::SaveClicked() { + // Check if we need to convert to Deb822 + if (!_config->FindB("Synaptic::UseDeb822", false)) { + if (ConvertToDeb822()) { + _config->Set("Synaptic::UseDeb822", true); + } + } + + // Continue with normal save + // ... existing save code ... +} diff --git a/gtk/rgrepositorywin.h b/gtk/rgrepositorywin.h index a885a887e..b8489a3b5 100644 --- a/gtk/rgrepositorywin.h +++ b/gtk/rgrepositorywin.h @@ -94,4 +94,15 @@ class RGRepositoryEditor:RGGtkBuilderWindow { bool Run(); }; +class RGRepositoryWin { +public: + // ... existing declarations ... + + bool ConvertToDeb822(); + void SaveClicked(); + +private: + // ... existing private members ... +}; + #endif From be7861205f5067646cc26e882a251dc399d151d7 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Thu, 22 May 2025 00:01:40 +0530 Subject: [PATCH 20/43] Update source record types and string conversion --- common/rsource_deb822.cc | 99 ++++++++++++++++++++-------------------- common/rsource_deb822.h | 2 +- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index d812a067b..3a943ce63 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -15,48 +15,49 @@ #include #include #include +#include "rsources.h" // Add this for SourcesList::SourceRecord bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - std::wifstream file(path); + std::ifstream file(path); if (!file) { return _error->Error(_("Cannot open %s"), path.c_str()); } - std::map fields; + std::map fields; while (ParseStanza(file, fields)) { Deb822Entry entry; // Check required fields - if (fields.find(L"Types") == fields.end()) { + if (fields.find("Types") == fields.end()) { return _error->Error(_("Missing Types field in %s"), path.c_str()); } - entry.Types = APT::String::FromUTF8(fields[L"Types"]); + entry.Types = fields["Types"]; - if (fields.find(L"URIs") == fields.end()) { + if (fields.find("URIs") == fields.end()) { return _error->Error(_("Missing URIs field in %s"), path.c_str()); } - entry.URIs = APT::String::FromUTF8(fields[L"URIs"]); + entry.URIs = fields["URIs"]; - if (fields.find(L"Suites") == fields.end()) { + if (fields.find("Suites") == fields.end()) { return _error->Error(_("Missing Suites field in %s"), path.c_str()); } - entry.Suites = APT::String::FromUTF8(fields[L"Suites"]); + entry.Suites = fields["Suites"]; // Optional fields - if (fields.find(L"Components") != fields.end()) { - entry.Components = APT::String::FromUTF8(fields[L"Components"]); + if (fields.find("Components") != fields.end()) { + entry.Components = fields["Components"]; } - if (fields.find(L"Signed-By") != fields.end()) { - entry.SignedBy = APT::String::FromUTF8(fields[L"Signed-By"]); + if (fields.find("Signed-By") != fields.end()) { + entry.SignedBy = fields["Signed-By"]; } - if (fields.find(L"Architectures") != fields.end()) { - entry.Architectures = APT::String::FromUTF8(fields[L"Architectures"]); + if (fields.find("Architectures") != fields.end()) { + entry.Architectures = fields["Architectures"]; } - if (fields.find(L"Languages") != fields.end()) { - entry.Languages = APT::String::FromUTF8(fields[L"Languages"]); + if (fields.find("Languages") != fields.end()) { + entry.Languages = fields["Languages"]; } - if (fields.find(L"Targets") != fields.end()) { - entry.Targets = APT::String::FromUTF8(fields[L"Targets"]); + if (fields.find("Targets") != fields.end()) { + entry.Targets = fields["Targets"]; } entry.Enabled = true; // Default to enabled @@ -68,34 +69,34 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - std::wofstream file(path); + std::ofstream file(path); if (!file) { return _error->Error(_("Cannot write to %s"), path.c_str()); } for (const auto& entry : entries) { if (!entry.Enabled) { - file << L"# Disabled: "; + file << "# Disabled: "; } - file << L"Types: " << APT::String::ToUTF8(entry.Types) << std::endl; - file << L"URIs: " << APT::String::ToUTF8(entry.URIs) << std::endl; - file << L"Suites: " << APT::String::ToUTF8(entry.Suites) << std::endl; + file << "Types: " << entry.Types << std::endl; + file << "URIs: " << entry.URIs << std::endl; + file << "Suites: " << entry.Suites << std::endl; if (!entry.Components.empty()) { - file << L"Components: " << APT::String::ToUTF8(entry.Components) << std::endl; + file << "Components: " << entry.Components << std::endl; } if (!entry.SignedBy.empty()) { - file << L"Signed-By: " << APT::String::ToUTF8(entry.SignedBy) << std::endl; + file << "Signed-By: " << entry.SignedBy << std::endl; } if (!entry.Architectures.empty()) { - file << L"Architectures: " << APT::String::ToUTF8(entry.Architectures) << std::endl; + file << "Architectures: " << entry.Architectures << std::endl; } if (!entry.Languages.empty()) { - file << L"Languages: " << APT::String::ToUTF8(entry.Languages) << std::endl; + file << "Languages: " << entry.Languages << std::endl; } if (!entry.Targets.empty()) { - file << L"Targets: " << APT::String::ToUTF8(entry.Targets) << std::endl; + file << "Targets: " << entry.Targets << std::endl; } file << std::endl; @@ -104,7 +105,7 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector& fields) { - std::wstring line; +bool RDeb822Source::ParseStanza(std::ifstream& file, std::map& fields) { + std::string line; bool inStanza = false; while (std::getline(file, line)) { @@ -212,26 +213,26 @@ bool RDeb822Source::ParseStanza(std::wifstream& file, std::map& entries); static bool ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record); static bool ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry); + static void TrimWhitespace(std::string& str); private: static bool ParseStanza(std::wifstream& file, std::map& fields); - static void TrimWhitespace(std::string& str); }; #endif // RSOURCE_DEB822_H \ No newline at end of file From ea78b57a7705a9b8704a54dbb7b2ba3ccd234b8d Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Thu, 22 May 2025 10:19:43 +0530 Subject: [PATCH 21/43] simplify file handling --- common/rsource_deb822.cc | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 3a943ce63..e2d1c1ee0 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -19,31 +19,17 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::ifstream file(path); - if (!file) { - return _error->Error(_("Cannot open %s"), path.c_str()); + if (!file.is_open()) { + _error->Error(_("Could not open file %s"), path.c_str()); + return false; } std::map fields; while (ParseStanza(file, fields)) { Deb822Entry entry; - - // Check required fields - if (fields.find("Types") == fields.end()) { - return _error->Error(_("Missing Types field in %s"), path.c_str()); - } entry.Types = fields["Types"]; - - if (fields.find("URIs") == fields.end()) { - return _error->Error(_("Missing URIs field in %s"), path.c_str()); - } entry.URIs = fields["URIs"]; - - if (fields.find("Suites") == fields.end()) { - return _error->Error(_("Missing Suites field in %s"), path.c_str()); - } entry.Suites = fields["Suites"]; - - // Optional fields if (fields.find("Components") != fields.end()) { entry.Components = fields["Components"]; } @@ -59,10 +45,8 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::ofstream file(path); - if (!file) { - return _error->Error(_("Cannot write to %s"), path.c_str()); + if (!file.is_open()) { + _error->Error(_("Could not open file %s for writing"), path.c_str()); + return false; } for (const auto& entry : entries) { - if (!entry.Enabled) { - file << "# Disabled: "; - } - file << "Types: " << entry.Types << std::endl; file << "URIs: " << entry.URIs << std::endl; file << "Suites: " << entry.Suites << std::endl; - if (!entry.Components.empty()) { file << "Components: " << entry.Components << std::endl; } @@ -98,7 +78,6 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector Date: Thu, 22 May 2025 11:29:08 +0530 Subject: [PATCH 22/43] Fix Sections vs Components conversion --- common/rsource_deb822.cc | 79 ++++++++++++++++++++++++++++------------ common/rsource_deb822.h | 2 +- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index e2d1c1ee0..14a27cece 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -15,21 +15,34 @@ #include #include #include -#include "rsources.h" // Add this for SourcesList::SourceRecord bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::ifstream file(path); - if (!file.is_open()) { - _error->Error(_("Could not open file %s"), path.c_str()); - return false; + if (!file) { + return _error->Error(_("Cannot open %s"), path.c_str()); } std::map fields; while (ParseStanza(file, fields)) { Deb822Entry entry; + + // Check required fields + if (fields.find("Types") == fields.end()) { + return _error->Error(_("Missing Types field in %s"), path.c_str()); + } entry.Types = fields["Types"]; + + if (fields.find("URIs") == fields.end()) { + return _error->Error(_("Missing URIs field in %s"), path.c_str()); + } entry.URIs = fields["URIs"]; + + if (fields.find("Suites") == fields.end()) { + return _error->Error(_("Missing Suites field in %s"), path.c_str()); + } entry.Suites = fields["Suites"]; + + // Optional fields if (fields.find("Components") != fields.end()) { entry.Components = fields["Components"]; } @@ -45,8 +58,10 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::ofstream file(path); - if (!file.is_open()) { - _error->Error(_("Could not open file %s for writing"), path.c_str()); - return false; + if (!file) { + return _error->Error(_("Cannot write to %s"), path.c_str()); } for (const auto& entry : entries) { + if (!entry.Enabled) { + file << "# Disabled: "; + } + file << "Types: " << entry.Types << std::endl; file << "URIs: " << entry.URIs << std::endl; file << "Suites: " << entry.Suites << std::endl; + if (!entry.Components.empty()) { file << "Components: " << entry.Components << std::endl; } @@ -78,26 +97,29 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector sections; while (std::getline(compStream, comp, ' ')) { TrimWhitespace(comp); if (!comp.empty()) { - record.Comps.push_back(comp); + sections.push_back(comp); + } + } + + // Set sections + if (!sections.empty()) { + record.NumSections = sections.size(); + record.Sections = new string[record.NumSections]; + for (unsigned short i = 0; i < record.NumSections; i++) { + record.Sections[i] = sections[i]; } } return true; } -bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) { +bool RDeb822Source::ConvertFromSourceRecord(const pkgSourceList::SourceRecord& record, Deb822Entry& entry) { // Set types std::stringstream typeStream; - if (record.Type & SourcesList::Deb) { + if (record.Type & pkgSourceList::Deb) { typeStream << "deb "; } - if (record.Type & SourcesList::DebSrc) { + if (record.Type & pkgSourceList::DebSrc) { typeStream << "deb-src "; } entry.Types = typeStream.str(); @@ -155,14 +186,14 @@ bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& rec // Set components std::stringstream compStream; - for (const auto& comp : record.Comps) { - compStream << comp << " "; + for (unsigned short i = 0; i < record.NumSections; i++) { + compStream << record.Sections[i] << " "; } entry.Components = compStream.str(); TrimWhitespace(entry.Components); // Set enabled state - entry.Enabled = !(record.Type & SourcesList::Disabled); + entry.Enabled = !(record.Type & pkgSourceList::Disabled); return true; } diff --git a/common/rsource_deb822.h b/common/rsource_deb822.h index ca076e0aa..aa4dc6ab8 100644 --- a/common/rsource_deb822.h +++ b/common/rsource_deb822.h @@ -42,7 +42,7 @@ class RDeb822Source { static void TrimWhitespace(std::string& str); private: - static bool ParseStanza(std::wifstream& file, std::map& fields); + static bool ParseStanza(std::ifstream& file, std::map& fields); }; #endif // RSOURCE_DEB822_H \ No newline at end of file From d879ae4f7b4ead7a4ba1cb8e965c84e938f6cff3 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Thu, 22 May 2025 11:37:43 +0530 Subject: [PATCH 23/43] Fix SourcesList vs pkgSourceList namespace usage --- common/rsource_deb822.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 14a27cece..b95a4b34a 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -104,7 +104,7 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector Date: Thu, 22 May 2025 11:55:06 +0530 Subject: [PATCH 24/43] Fix error handling --- gtk/rgrepositorywin.cc | 43 ++++++++++++++++++++++++++++++++++++++---- gtk/rgrepositorywin.h | 13 ++++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index ff23a4e4f..54e696b68 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -36,6 +36,7 @@ #include "rgutils.h" #include "config.h" #include "i18n.h" +#include "rsource_deb822.h" #if HAVE_RPM enum { ITEM_TYPE_RPM, @@ -128,6 +129,8 @@ RGRepositoryEditor::RGRepositoryEditor(RGWindow *parent) _userDialog = new RGUserDialog(_win); _applied = false; _lastIter = NULL; + _config = new Configuration(); + _error = GTK_WIDGET(gtk_builder_get_object(_builder, "label_error")); setTitle(_("Repositories")); gtk_window_set_modal(GTK_WINDOW(_win), TRUE); @@ -386,6 +389,7 @@ RGRepositoryEditor::~RGRepositoryEditor() { //gtk_widget_destroy(_win); delete _userDialog; + delete _config; } @@ -806,7 +810,7 @@ void RGRepositoryEditor::DoUpDown(GtkWidget *self, gpointer data) } bool RGRepositoryEditor::ConvertToDeb822() { - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(_win), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, @@ -820,7 +824,30 @@ bool RGRepositoryEditor::ConvertToDeb822() { gint result = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); - return (result == GTK_RESPONSE_YES); + if (result != GTK_RESPONSE_YES) { + return false; + } + + // Convert each source record to Deb822 format + for (SourcesListIter I = _lst.SourceRecords.begin(); I != _lst.SourceRecords.end(); I++) { + SourcesList::SourceRecord *rec = *I; + if (rec == NULL) continue; + + // Create Deb822 entry + RDeb822Source::Deb822Entry entry; + if (!RDeb822Source::ConvertFromSourceRecord(*rec, entry)) { + _userDialog->error(_("Failed to convert source record to Deb822 format")); + return false; + } + + // Update the source record + if (!RDeb822Source::ConvertToSourceRecord(entry, *rec)) { + _userDialog->error(_("Failed to update source record with Deb822 format")); + return false; + } + } + + return true; } void RGRepositoryEditor::SaveClicked() { @@ -828,9 +855,17 @@ void RGRepositoryEditor::SaveClicked() { if (!_config->FindB("Synaptic::UseDeb822", false)) { if (ConvertToDeb822()) { _config->Set("Synaptic::UseDeb822", true); + _config->Set("Synaptic::UseDeb822Sources", true); } } - // Continue with normal save - // ... existing save code ... + // Save the sources list + if (!_lst.Save()) { + _userDialog->error(_("Failed to save sources list")); + return; + } + + // Update the sources + _lst.UpdateSources(); + _dirty = false; } diff --git a/gtk/rgrepositorywin.h b/gtk/rgrepositorywin.h index b8489a3b5..294794402 100644 --- a/gtk/rgrepositorywin.h +++ b/gtk/rgrepositorywin.h @@ -30,8 +30,8 @@ #include #include "rsources.h" #include "rggtkbuilderwindow.h" - #include "rguserdialog.h" +#include typedef list::iterator SourcesListIter; typedef list::iterator VendorsListIter; @@ -66,6 +66,9 @@ class RGRepositoryEditor:RGGtkBuilderWindow { bool _dirty; GdkColor _gray; + // Configuration + Configuration *_config; + void UpdateVendorMenu(); int VendorMenuIndex(string VendorID); @@ -86,21 +89,21 @@ class RGRepositoryEditor:RGGtkBuilderWindow { // get values void doEdit(); - public: RGRepositoryEditor(RGWindow *parent); ~RGRepositoryEditor(); bool Run(); + + // Deb822 support + bool ConvertToDeb822(); + void SaveClicked(); }; class RGRepositoryWin { public: // ... existing declarations ... - bool ConvertToDeb822(); - void SaveClicked(); - private: // ... existing private members ... }; From bc24d2bfedae1b10fb91e13811b40c364e14adba Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Thu, 22 May 2025 12:01:28 +0530 Subject: [PATCH 25/43] Fix build errors --- gtk/rgrepositorywin.cc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 54e696b68..0ca7ee21f 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -130,7 +130,6 @@ RGRepositoryEditor::RGRepositoryEditor(RGWindow *parent) _applied = false; _lastIter = NULL; _config = new Configuration(); - _error = GTK_WIDGET(gtk_builder_get_object(_builder, "label_error")); setTitle(_("Repositories")); gtk_window_set_modal(GTK_WINDOW(_win), TRUE); @@ -404,7 +403,6 @@ bool RGRepositoryEditor::Run() _savedList.ReadSources(); if (_lst.ReadVendors() == false) { - _error->Error(_("Cannot read vendors.list file")); _userDialog->showErrors(); return false; } @@ -811,7 +809,7 @@ void RGRepositoryEditor::DoUpDown(GtkWidget *self, gpointer data) bool RGRepositoryEditor::ConvertToDeb822() { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(_win), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Convert to Deb822 format?")); @@ -859,13 +857,11 @@ void RGRepositoryEditor::SaveClicked() { } } - // Save the sources list - if (!_lst.Save()) { - _userDialog->error(_("Failed to save sources list")); + // Update the sources list + if (!_lst.UpdateSources()) { + _userDialog->error(_("Failed to update sources list")); return; } - // Update the sources - _lst.UpdateSources(); _dirty = false; } From d0184bc6c180c80e1d3423d2e02ee991d4758724 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Sun, 1 Jun 2025 19:56:00 +0530 Subject: [PATCH 26/43] Add debug prints in RGRepositoryEditor::Run to trace source loading --- gtk/rgrepositorywin.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 0ca7ee21f..c8e74ca4f 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -397,11 +397,13 @@ bool RGRepositoryEditor::Run() if (_lst.ReadSources() == false) { _userDialog-> warning(_("Ignoring invalid record(s) in sources.list file!")); - //return false; } // keep a backup of the orginal list _savedList.ReadSources(); + // Add debug print statement here + g_print("DEBUG: Number of source records read into _lst: %lu\n", _lst.SourceRecords.size()); + if (_lst.ReadVendors() == false) { _userDialog->showErrors(); return false; @@ -415,6 +417,10 @@ bool RGRepositoryEditor::Run() it != _lst.SourceRecords.end(); it++) { if ((*it)->Type & SourcesList::Comment) continue; + + // Add debug print for each source being added to the display + g_print("DEBUG: Adding source to display - URI: %s, Type: %s\n", (*it)->URI.c_str(), (*it)->GetType().c_str()); + string Sections; for (unsigned int J = 0; J < (*it)->NumSections; J++) { Sections += (*it)->Sections[J]; From 45f4f683c7c1af93265ca4c98053650f2db3172c Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Sun, 1 Jun 2025 20:22:40 +0530 Subject: [PATCH 27/43] Add second debug print in RGRepositoryEditor --- gtk/rgrepositorywin.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index c8e74ca4f..f6a02711f 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -427,6 +427,9 @@ bool RGRepositoryEditor::Run() Sections += " "; } + // Add another debug print before appending to the list store + g_print("DEBUG: Preparing to append to list store for URI: %s\n", (*it)->URI.c_str()); + gtk_list_store_append(_sourcesListStore, &iter); gtk_list_store_set(_sourcesListStore, &iter, STATUS_COLUMN, !((*it)->Type & From ecb8bb11ca7f1f2e2763c262250a6c2347883bd1 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Sun, 1 Jun 2025 20:36:46 +0530 Subject: [PATCH 28/43] Add debug print after gtk_list_store_set in RGRepositoryEditor::Run --- gtk/rgrepositorywin.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index f6a02711f..94227ac46 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -443,6 +443,9 @@ bool RGRepositoryEditor::Run() DISABLED_COLOR_COLUMN, (*it)->Type & SourcesList::Disabled ? &_gray : NULL, -1); + + // Add debug print after setting data in the list store + g_print("DEBUG: Successfully set data for URI: %s\n", (*it)->URI.c_str()); } From 942e96d91dadbddd9ad9912f6db943ea3f2b70da Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Fri, 20 Jun 2025 18:36:33 +0530 Subject: [PATCH 29/43] Read Deb822 .sources files from sourceparts directory to match APT behavior --- common/rsources.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/rsources.cc b/common/rsources.cc index f38ed36d1..e30456ca3 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -220,15 +220,16 @@ bool SourcesList::ReadSources() { bool Res = true; - // First try to read Deb822 format sources + // First try to read Deb822 format sources from sourcelist.d string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); if (FileExists(Deb822Parts) == true) { Res &= ReadDeb822SourceDir(Deb822Parts); } - // Then read classic sources.list format + // Then read Deb822 format sources from sourceparts as well string Parts = _config->FindDir("Dir::Etc::sourceparts"); if (FileExists(Parts) == true) { + Res &= ReadDeb822SourceDir(Parts); Res &= ReadSourceDir(Parts); } string Main = _config->FindFile("Dir::Etc::sourcelist"); From 6e2c4b7cbef591fc46cc35558edeab8adb9152a2 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Fri, 20 Jun 2025 19:27:22 +0530 Subject: [PATCH 30/43] Preserve extra Deb822 fields and disable auto-conversion of legacy sources.list to Deb822 format --- common/rsource_deb822.cc | 22 ++++++++++++++++++++-- gtk/rgrepositorywin.cc | 10 +--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index b95a4b34a..3bd1a3fe6 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -127,7 +127,7 @@ bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, SourcesList: while (std::getline(uriStream, uri, ' ')) { TrimWhitespace(uri); if (!uri.empty()) { - record.URI = uri; + record.URI = uri; break; } } @@ -138,7 +138,7 @@ bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, SourcesList: while (std::getline(suiteStream, suite, ' ')) { TrimWhitespace(suite); if (!suite.empty()) { - record.Dist = suite; + record.Dist = suite; break; } } @@ -194,6 +194,24 @@ bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& rec // Set enabled state entry.Enabled = !(record.Type & SourcesList::Disabled); + + // Parse extra fields from Comment + if (!record.Comment.empty()) { + std::istringstream iss(record.Comment); + std::string line; + while (std::getline(iss, line)) { + size_t colon = line.find(":"); + if (colon == std::string::npos) continue; + std::string key = line.substr(0, colon); + std::string value = line.substr(colon + 1); + TrimWhitespace(key); + TrimWhitespace(value); + if (key == "Signed-By") entry.SignedBy = value; + else if (key == "Architectures") entry.Architectures = value; + else if (key == "Languages") entry.Languages = value; + else if (key == "Targets") entry.Targets = value; + } + } return true; } diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 94227ac46..8f37c0dbc 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -861,15 +861,7 @@ bool RGRepositoryEditor::ConvertToDeb822() { } void RGRepositoryEditor::SaveClicked() { - // Check if we need to convert to Deb822 - if (!_config->FindB("Synaptic::UseDeb822", false)) { - if (ConvertToDeb822()) { - _config->Set("Synaptic::UseDeb822", true); - _config->Set("Synaptic::UseDeb822Sources", true); - } - } - - // Update the sources list + // Remove auto-conversion to Deb822. Only update sources. if (!_lst.UpdateSources()) { _userDialog->error(_("Failed to update sources list")); return; From 1e6168e3eed0b5a6b25199e919cbc75b3befeca4 Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Tue, 8 Jul 2025 14:09:05 +0530 Subject: [PATCH 31/43] fix: always write sources.list in classic format, only .sources use Deb822 --- common/rsources.cc | 105 +++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/common/rsources.cc b/common/rsources.cc index e30456ca3..2039afc64 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -300,10 +300,6 @@ void SourcesList::SwapSources( SourceRecord *&rec_one, SourceRecord *&rec_two ) bool SourcesList::UpdateSources() { - ofstream ofs(_config->FindFile("Dir::Etc::sourcelist").c_str(), ios::out); - if (!ofs != 0) - return _error->Error(_("Error writing sources list")); - // Group sources by their source file map> sourcesByFile; for (list::const_iterator it = SourceRecords.begin(); @@ -321,11 +317,11 @@ bool SourcesList::UpdateSources() continue; } - // Check if this is a Deb822 format file + // Check if this is a Deb822 format file (only .sources files) bool isDeb822 = false; - if (!records.empty() && (records[0]->Type & Deb822)) { + if (sourcePath.size() > 8 && sourcePath.substr(sourcePath.size() - 8) == ".sources") { isDeb822 = true; - } + } // Open the appropriate file for writing ofstream out(sourcePath.c_str(), ios::out); @@ -344,16 +340,43 @@ bool SourcesList::UpdateSources() entries.push_back(entry); } if (!RDeb822Source::WriteDeb822File(sourcePath, entries)) { - return false; + return false; } } else { - // Write classic format + // Write classic format (deb lines) for (const auto& record : records) { if (record->Type == Comment) { out << record->Comment << endl; - } else { - out << *record; - } + } else { + // Write as a standard deb/deb-src line, comment if disabled + if (record->Type & Disabled) { + out << "# "; + } + if (record->Type & Deb) { + out << "deb "; + } else if (record->Type & DebSrc) { + out << "deb-src "; + } else if (record->Type & Rpm) { + out << "rpm "; + } else if (record->Type & RpmSrc) { + out << "rpm-src "; + } else if (record->Type & RpmDir) { + out << "rpm-dir "; + } else if (record->Type & RpmSrcDir) { + out << "rpm-src-dir "; + } else if (record->Type & Repomd) { + out << "repomd "; + } else if (record->Type & RepomdSrc) { + out << "repomd-src "; + } else { + out << "deb "; // fallback + } + out << record->URI << " " << record->Dist; + for (unsigned int J = 0; J < record->NumSections; J++) { + out << " " << record->Sections[J]; + } + out << endl; + } } } @@ -544,38 +567,36 @@ void SourcesList::RemoveVendor(VendorRecord *&rec) ostream &operator<<(ostream &os, const SourcesList::SourceRecord &rec) { - os << "Type: "; - if ((rec.Type & SourcesList::Comment) != 0) - os << "Comment "; - if ((rec.Type & SourcesList::Disabled) != 0) - os << "Disabled "; - if ((rec.Type & SourcesList::Deb) != 0) - os << "Deb"; - if ((rec.Type & SourcesList::DebSrc) != 0) - os << "DebSrc"; - if ((rec.Type & SourcesList::Rpm) != 0) - os << "Rpm"; - if ((rec.Type & SourcesList::RpmSrc) != 0) - os << "RpmSrc"; - if ((rec.Type & SourcesList::RpmDir) != 0) - os << "RpmDir"; - if ((rec.Type & SourcesList::RpmSrcDir) != 0) - os << "RpmSrcDir"; - if ((rec.Type & SourcesList::Repomd) != 0) - os << "Repomd"; - if ((rec.Type & SourcesList::RepomdSrc) != 0) - os << "RepomdSrc"; - os << endl; - os << "SourceFile: " << rec.SourceFile << endl; - os << "VendorID: " << rec.VendorID << endl; - os << "URI: " << rec.URI << endl; - os << "Dist: " << rec.Dist << endl; - os << "Section(s):" << endl; -#if 0 + if (rec.Type == SourcesList::Comment) { + os << rec.Comment << endl; + return os; + } + if (rec.Type & SourcesList::Disabled) { + os << "# "; + } + if (rec.Type & SourcesList::Deb) { + os << "deb "; + } else if (rec.Type & SourcesList::DebSrc) { + os << "deb-src "; + } else if (rec.Type & SourcesList::Rpm) { + os << "rpm "; + } else if (rec.Type & SourcesList::RpmSrc) { + os << "rpm-src "; + } else if (rec.Type & SourcesList::RpmDir) { + os << "rpm-dir "; + } else if (rec.Type & SourcesList::RpmSrcDir) { + os << "rpm-src-dir "; + } else if (rec.Type & SourcesList::Repomd) { + os << "repomd "; + } else if (rec.Type & SourcesList::RepomdSrc) { + os << "repomd-src "; + } else { + os << "deb "; // fallback + } + os << rec.URI << " " << rec.Dist; for (unsigned int J = 0; J < rec.NumSections; J++) { - cout << "\t" << rec.Sections[J] << endl; + os << " " << rec.Sections[J]; } -#endif os << endl; return os; } From 08289846fa8bc221bb41b20e3be5b7ec2533f98d Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Fri, 11 Jul 2025 10:03:59 +0530 Subject: [PATCH 32/43] Fix: trailing spaces, empty lines, URL modification, section reordering, and Signed-By preservation --- common/rsource_deb822.cc | 29 +++++++++++++++++++++++++---- common/rsources.cc | 37 ++++++++++++++++++++++--------------- common/rsources.h | 3 ++- gtk/rgrepositorywin.cc | 33 +++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 3bd1a3fe6..f7c7a829d 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -73,7 +73,9 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vectorError(_("Cannot write to %s"), path.c_str()); } - for (const auto& entry : entries) { + for (size_t i = 0; i < entries.size(); ++i) { + const auto& entry = entries[i]; + if (!entry.Enabled) { file << "# Disabled: "; } @@ -98,7 +100,10 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vectorComment << endl; } else { // Write as a standard deb/deb-src line, comment if disabled + string line; if (record->Type & Disabled) { - out << "# "; + line += "# "; } if (record->Type & Deb) { - out << "deb "; + line += "deb "; } else if (record->Type & DebSrc) { - out << "deb-src "; + line += "deb-src "; } else if (record->Type & Rpm) { - out << "rpm "; + line += "rpm "; } else if (record->Type & RpmSrc) { - out << "rpm-src "; + line += "rpm-src "; } else if (record->Type & RpmDir) { - out << "rpm-dir "; + line += "rpm-dir "; } else if (record->Type & RpmSrcDir) { - out << "rpm-src-dir "; + line += "rpm-src-dir "; } else if (record->Type & Repomd) { - out << "repomd "; + line += "repomd "; } else if (record->Type & RepomdSrc) { - out << "repomd-src "; + line += "repomd-src "; } else { - out << "deb "; // fallback + line += "deb "; // fallback } - out << record->URI << " " << record->Dist; + line += record->URI + " " + record->Dist; for (unsigned int J = 0; J < record->NumSections; J++) { - out << " " << record->Sections[J]; + line += " " + record->Sections[J]; } - out << endl; + // Trim trailing space + if (!line.empty() && line[line.length()-1] == ' ') { + line.erase(line.length()-1); + } + out << line << endl; } } } @@ -446,8 +452,8 @@ bool SourcesList::SourceRecord::SetURI(string S) S = SubstVar(S, "$(VERSION)", _config->Find("APT::DistroVersion")); URI = S; - // append a / to the end if one is not already there - if (URI[URI.size() - 1] != '/') + // Only append / if we're not preserving the original format + if (!PreserveOriginalURI && URI[URI.size() - 1] != '/') URI += '/'; return true; @@ -467,6 +473,7 @@ operator=(const SourceRecord &rhs) NumSections = rhs.NumSections; Comment = rhs.Comment; SourceFile = rhs.SourceFile; + PreserveOriginalURI = rhs.PreserveOriginalURI; return *this; } diff --git a/common/rsources.h b/common/rsources.h index cd47859c0..5ffdd6df3 100644 --- a/common/rsources.h +++ b/common/rsources.h @@ -56,12 +56,13 @@ class SourcesList { unsigned short NumSections; string Comment; string SourceFile; + bool PreserveOriginalURI; // Flag to preserve original URI format bool SetType(string); string GetType(); bool SetURI(string); - SourceRecord():Type(0), Sections(0), NumSections(0) { + SourceRecord():Type(0), Sections(0), NumSections(0), PreserveOriginalURI(false) { } ~SourceRecord() { if (Sections) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 8f37c0dbc..a0e96623f 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include @@ -613,15 +615,26 @@ void RGRepositoryEditor::doEdit() rec->NumSections = 0; const char *Section = gtk_entry_get_text(GTK_ENTRY(_entrySect)); - if (Section != 0 && Section[0] != 0) - rec->NumSections++; - - rec->Sections = new string[rec->NumSections]; - rec->NumSections = 0; - Section = gtk_entry_get_text(GTK_ENTRY(_entrySect)); - - if (Section != 0 && Section[0] != 0) - rec->Sections[rec->NumSections++] = Section; + if (Section != 0 && Section[0] != 0) { + // Parse sections properly - split by spaces + string sectionsStr = Section; + vector sections; + stringstream ss(sectionsStr); + string section; + + while (ss >> section) { + sections.push_back(section); + } + + rec->NumSections = sections.size(); + rec->Sections = new string[rec->NumSections]; + for (unsigned int I = 0; I < rec->NumSections; I++) { + rec->Sections[I] = sections[I]; + } + } else { + rec->Sections = new string[0]; + rec->NumSections = 0; + } string Sect; for (unsigned int I = 0; I < rec->NumSections; I++) { @@ -836,7 +849,7 @@ bool RGRepositoryEditor::ConvertToDeb822() { if (result != GTK_RESPONSE_YES) { return false; - } +} // Convert each source record to Deb822 format for (SourcesListIter I = _lst.SourceRecords.begin(); I != _lst.SourceRecords.end(); I++) { From 8d568b5cb5435a6d5aececbb89feccd0ca35e25b Mon Sep 17 00:00:00 2001 From: Ajay Bandaru Date: Sun, 13 Jul 2025 21:14:44 +0530 Subject: [PATCH 33/43] preserve section order, trim extra lines, handle Deb822 enable/disable, preserve comments and fields, and show deb-src entries --- common/rsource_deb822.cc | 132 ++++++++++++++++++++++++++++++--------- common/rsources.cc | 34 ++++++---- gtk/rgrepositorywin.cc | 64 ++++++++----------- 3 files changed, 149 insertions(+), 81 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index f7c7a829d..600421a29 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -23,45 +23,109 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector fields; - while (ParseStanza(file, fields)) { - Deb822Entry entry; - - // Check required fields - if (fields.find("Types") == fields.end()) { - return _error->Error(_("Missing Types field in %s"), path.c_str()); - } - entry.Types = fields["Types"]; + std::string pending_comments; + std::string line; + while (std::getline(file, line)) { + if (line.empty()) { + if (!fields.empty()) { + Deb822Entry entry; + + // Check required fields + if (fields.find("Types") == fields.end()) { + fields.clear(); + pending_comments.clear(); + continue; + } + entry.Types = fields["Types"]; - if (fields.find("URIs") == fields.end()) { - return _error->Error(_("Missing URIs field in %s"), path.c_str()); - } - entry.URIs = fields["URIs"]; + if (fields.find("URIs") == fields.end()) { + fields.clear(); + pending_comments.clear(); + continue; + } + entry.URIs = fields["URIs"]; - if (fields.find("Suites") == fields.end()) { - return _error->Error(_("Missing Suites field in %s"), path.c_str()); - } - entry.Suites = fields["Suites"]; + if (fields.find("Suites") == fields.end()) { + fields.clear(); + pending_comments.clear(); + continue; + } + entry.Suites = fields["Suites"]; - // Optional fields - if (fields.find("Components") != fields.end()) { - entry.Components = fields["Components"]; - } - if (fields.find("Signed-By") != fields.end()) { - entry.SignedBy = fields["Signed-By"]; + // Optional fields + if (fields.find("Components") != fields.end()) { + entry.Components = fields["Components"]; + } + if (fields.find("Signed-By") != fields.end()) { + entry.SignedBy = fields["Signed-By"]; + } + if (fields.find("Architectures") != fields.end()) { + entry.Architectures = fields["Architectures"]; + } + if (fields.find("Languages") != fields.end()) { + entry.Languages = fields["Languages"]; + } + if (fields.find("Targets") != fields.end()) { + entry.Targets = fields["Targets"]; + } + + entry.Enabled = !pending_comments.empty() && pending_comments.find("# Disabled:") == 0 ? false : true; + entry.Comment = pending_comments; + entries.push_back(entry); + fields.clear(); + pending_comments.clear(); + } + continue; } - if (fields.find("Architectures") != fields.end()) { - entry.Architectures = fields["Architectures"]; + + // Detect disabled stanza + if (line.find("# Disabled:") == 0) { + pending_comments += line + "\n"; + // Mark as disabled for the next stanza + // We'll check this in the stanza handler above + continue; } - if (fields.find("Languages") != fields.end()) { - entry.Languages = fields["Languages"]; + + // Skip comments + if (line[0] == '#') { + pending_comments += line + "\n"; + continue; } - if (fields.find("Targets") != fields.end()) { - entry.Targets = fields["Targets"]; + + // Check for stanza start + if (line.find("Types:") != std::string::npos) { + size_t colonPos = line.find(':'); + if (colonPos != std::string::npos) { + std::string key = line.substr(0, colonPos); + std::string value = line.substr(colonPos + 1); + + // Trim whitespace + key.erase(0, key.find_first_not_of(" \t")); + key.erase(key.find_last_not_of(" \t") + 1); + value.erase(0, value.find_first_not_of(" \t")); + value.erase(value.find_last_not_of(" \t") + 1); + + fields[key] = value; + } } + } + // Handle last stanza + if (!fields.empty()) { + Deb822Entry entry; - entry.Enabled = true; // Default to enabled - entries.push_back(entry); - fields.clear(); + if (fields.find("Types") != fields.end() && fields.find("URIs") != fields.end() && fields.find("Suites") != fields.end()) { + entry.Types = fields["Types"]; + entry.URIs = fields["URIs"]; + entry.Suites = fields["Suites"]; + if (fields.find("Components") != fields.end()) entry.Components = fields["Components"]; + if (fields.find("Signed-By") != fields.end()) entry.SignedBy = fields["Signed-By"]; + if (fields.find("Architectures") != fields.end()) entry.Architectures = fields["Architectures"]; + if (fields.find("Languages") != fields.end()) entry.Languages = fields["Languages"]; + if (fields.find("Targets") != fields.end()) entry.Targets = fields["Targets"]; + entry.Enabled = true; + entry.Comment = pending_comments; + entries.push_back(entry); + } } return true; @@ -76,6 +140,12 @@ bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vectorError(_("Syntax error in line %s"), buf); } + AddSourceNode(rec); + continue; } #ifndef HAVE_RPM // check for absolute dist @@ -318,6 +314,18 @@ bool SourcesList::UpdateSources() continue; } + // Trim trailing blank/comment lines (empty or whitespace-only comments) + std::vector trimmed_records = records; + while (!trimmed_records.empty()) { + SourceRecord* rec = trimmed_records.back(); + bool is_blank_comment = (rec->Type == Comment) && (rec->Comment.find_first_not_of(" \t\r\n") == std::string::npos); + if (is_blank_comment) { + trimmed_records.pop_back(); + } else { + break; + } + } + // Check if this is a Deb822 format file (only .sources files) bool isDeb822 = false; if (sourcePath.size() > 8 && sourcePath.substr(sourcePath.size() - 8) == ".sources") { @@ -333,7 +341,7 @@ bool SourcesList::UpdateSources() if (isDeb822) { // Write Deb822 format vector entries; - for (const auto& record : records) { + for (const auto& record : trimmed_records) { RDeb822Source::Deb822Entry entry; if (!RDeb822Source::ConvertFromSourceRecord(*record, entry)) { return _error->Error(_("Failed to convert source record to Deb822 format")); @@ -345,7 +353,7 @@ bool SourcesList::UpdateSources() } } else { // Write classic format (deb lines) - for (const auto& record : records) { + for (const auto& record : trimmed_records) { if (record->Type == Comment) { out << record->Comment << endl; } else { diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index a0e96623f..92dfb2778 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -429,6 +429,21 @@ bool RGRepositoryEditor::Run() Sections += " "; } + // --- NEW: Show both deb and deb-src for Deb822 stanzas --- + std::string type_display; + bool is_deb = ((*it)->Type & SourcesList::Deb) != 0; + bool is_debsrc = ((*it)->Type & SourcesList::DebSrc) != 0; + if (is_deb && is_debsrc) { + type_display = "deb, deb-src"; + } else if (is_deb) { + type_display = "deb"; + } else if (is_debsrc) { + type_display = "deb-src"; + } else { + type_display = (*it)->GetType(); + } + // --- END NEW --- + // Add another debug print before appending to the list store g_print("DEBUG: Preparing to append to list store for URI: %s\n", (*it)->URI.c_str()); @@ -436,7 +451,7 @@ bool RGRepositoryEditor::Run() gtk_list_store_set(_sourcesListStore, &iter, STATUS_COLUMN, !((*it)->Type & SourcesList::Disabled), - TYPE_COLUMN, utf8((*it)->GetType().c_str()), + TYPE_COLUMN, utf8(type_display.c_str()), VENDOR_COLUMN, utf8((*it)->VendorID.c_str()), URI_COLUMN, utf8((*it)->URI.c_str()), DISTRIBUTION_COLUMN, utf8((*it)->Dist.c_str()), @@ -564,42 +579,17 @@ void RGRepositoryEditor::doEdit() if (!status) rec->Type |= SourcesList::Disabled; - GtkTreeIter item; - int type; - gtk_combo_box_get_active_iter(GTK_COMBO_BOX(_optType), &item); - gtk_tree_model_get(GTK_TREE_MODEL(_optTypeMenu), &item, - 1, &type, - -1); - - switch (type) { - case ITEM_TYPE_DEB: - rec->Type |= SourcesList::Deb; - break; - case ITEM_TYPE_DEBSRC: - rec->Type |= SourcesList::DebSrc; - break; - case ITEM_TYPE_RPM: - rec->Type |= SourcesList::Rpm; - break; - case ITEM_TYPE_RPMSRC: - rec->Type |= SourcesList::RpmSrc; - break; - case ITEM_TYPE_RPMDIR: - rec->Type |= SourcesList::RpmDir; - break; - case ITEM_TYPE_RPMSRCDIR: - rec->Type |= SourcesList::RpmSrcDir; - break; - case ITEM_TYPE_REPOMD: - rec->Type |= SourcesList::Repomd; - break; - case ITEM_TYPE_REPOMDSRC: - rec->Type |= SourcesList::RepomdSrc; - break; - default: - _userDialog->error(_("Unknown source type")); - return; - } + // --- NEW: For Deb822, allow both deb and deb-src to be set --- + // Parse the type_display string from the TYPE_COLUMN + gchar* type_str = NULL; + gtk_tree_model_get(GTK_TREE_MODEL(_sourcesListStore), _lastIter, TYPE_COLUMN, &type_str, -1); + std::string type_val = type_str ? type_str : ""; + g_free(type_str); + bool set_deb = (type_val.find("deb") != std::string::npos); + bool set_debsrc = (type_val.find("deb-src") != std::string::npos); + if (set_deb) rec->Type |= SourcesList::Deb; + if (set_debsrc) rec->Type |= SourcesList::DebSrc; + // --- END NEW --- #if 0 // PORTME, no vendor id support right now gtk_combo_box_get_active_iter(GTK_COMBO_BOX(_optVendor), &item); From 01f2ae9e748052427a5aadb556f6430f41b26a7d Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 08:17:51 +0530 Subject: [PATCH 34/43] fix: preserve Deb822 flag when editing sources in repository window to prevent corruption of .sources files --- gtk/rgrepositorywin.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gtk/rgrepositorywin.cc b/gtk/rgrepositorywin.cc index 92dfb2778..732faca4b 100644 --- a/gtk/rgrepositorywin.cc +++ b/gtk/rgrepositorywin.cc @@ -556,7 +556,6 @@ void RGRepositoryEditor::doEdit() { //cout << "RGRepositoryEditor::doEdit()"<Type & SourcesList::Deb822) != 0; + // --- END PATCH --- + rec->Type = 0; gboolean status; gtk_tree_model_get(GTK_TREE_MODEL(_sourcesListStore), _lastIter, @@ -591,6 +594,10 @@ void RGRepositoryEditor::doEdit() if (set_debsrc) rec->Type |= SourcesList::DebSrc; // --- END NEW --- + // --- PATCH: Restore Deb822 flag if it was set --- + if (was_deb822) rec->Type |= SourcesList::Deb822; + // --- END PATCH --- + #if 0 // PORTME, no vendor id support right now gtk_combo_box_get_active_iter(GTK_COMBO_BOX(_optVendor), &item); gtk_tree_model_get(GTK_TREE_MODEL(_optVendorMenu), &item, From 6f0643e6d79ba944e9d944fee2b1af65877dc1e2 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 09:56:17 +0530 Subject: [PATCH 35/43] fix: always scan /etc/apt/sources.list.d/ for Deb822 .sources files, matching APT --- common/rsources.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/rsources.cc b/common/rsources.cc index 990a03d12..a66201bac 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -217,9 +217,13 @@ bool SourcesList::ReadSources() { bool Res = true; - // First try to read Deb822 format sources from sourcelist.d + // Use config or fallback to /etc/apt/sources.list.d/ string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); + if (Deb822Parts.empty()) + Deb822Parts = "/etc/apt/sources.list.d/"; + if (FileExists(Deb822Parts) == true) { + g_print("DEBUG: Scanning Deb822 sources in directory: %s\n", Deb822Parts.c_str()); Res &= ReadDeb822SourceDir(Deb822Parts); } From b679b09c568b016e16c436183c1d023300f74b41 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 10:00:08 +0530 Subject: [PATCH 36/43] fix: use std::cout for debug output in ReadSources instead of g_print (portable, non-GTK) --- common/rsources.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/rsources.cc b/common/rsources.cc index a66201bac..66617bad1 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -37,6 +37,7 @@ #include "config.h" #include "i18n.h" #include "rsource_deb822.h" +#include SourcesList::~SourcesList() { @@ -223,7 +224,7 @@ bool SourcesList::ReadSources() Deb822Parts = "/etc/apt/sources.list.d/"; if (FileExists(Deb822Parts) == true) { - g_print("DEBUG: Scanning Deb822 sources in directory: %s\n", Deb822Parts.c_str()); + std::cout << "DEBUG: Scanning Deb822 sources in directory: " << Deb822Parts << std::endl; Res &= ReadDeb822SourceDir(Deb822Parts); } From d86e93db11960339b1b7376e54a96b00cc3c14bf Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 10:14:33 +0530 Subject: [PATCH 37/43] fix: ensure /etc/apt/sources.list.d/ is always scanned for Deb822 sources, add debug output for config path --- common/rsources.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/rsources.cc b/common/rsources.cc index 66617bad1..f8d32e834 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -220,7 +220,8 @@ bool SourcesList::ReadSources() // Use config or fallback to /etc/apt/sources.list.d/ string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); - if (Deb822Parts.empty()) + std::cout << "DEBUG: Config returned: '" << Deb822Parts << "'" << std::endl; + if (Deb822Parts.empty() || Deb822Parts == "/") Deb822Parts = "/etc/apt/sources.list.d/"; if (FileExists(Deb822Parts) == true) { From 2372c57e019a718ad78e610dbe9fb5d330ae350c Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 10:22:48 +0530 Subject: [PATCH 38/43] debug: add detailed debug prints for Deb822 .sources file discovery and parsing --- common/rsources.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/common/rsources.cc b/common/rsources.cc index f8d32e834..e240816c9 100644 --- a/common/rsources.cc +++ b/common/rsources.cc @@ -631,16 +631,20 @@ ostream &operator<<(ostream &os, const SourcesList::VendorRecord &rec) } bool SourcesList::ReadDeb822SourcePart(string listpath) { + std::cout << "DEBUG: Entering ReadDeb822SourcePart for file: " << listpath << std::endl; vector entries; if (!RDeb822Source::ParseDeb822File(listpath, entries)) { + std::cout << "DEBUG: Failed to parse Deb822 file: " << listpath << std::endl; return false; } + std::cout << "DEBUG: Parsed " << entries.size() << " Deb822 entries from file: " << listpath << std::endl; for (const auto& entry : entries) { SourceRecord rec; rec.SourceFile = listpath; if (!RDeb822Source::ConvertToSourceRecord(entry, rec)) { + std::cout << "DEBUG: Failed to convert Deb822 entry to SourceRecord in file: " << listpath << std::endl; return _error->Error(_("Failed to convert Deb822 entry in %s"), listpath.c_str()); } @@ -652,9 +656,10 @@ bool SourcesList::ReadDeb822SourcePart(string listpath) { } bool SourcesList::ReadDeb822SourceDir(string Dir) { + std::cout << "DEBUG: Entering ReadDeb822SourceDir for directory: " << Dir << std::endl; DIR *D = opendir(Dir.c_str()); if (D == 0) - return _error->Errno("opendir", _("Unable to read %s"), Dir.c_str()); + return _error->Errno("opendir", _( "Unable to read %s"), Dir.c_str()); vector List; for (struct dirent * Ent = readdir(D); Ent != 0; Ent = readdir(D)) { @@ -670,6 +675,7 @@ bool SourcesList::ReadDeb822SourceDir(string Dir) { struct stat St; if (stat(File.c_str(), &St) != 0 || S_ISREG(St.st_mode) == 0) continue; + std::cout << "DEBUG: Found .sources file: " << File << std::endl; List.push_back(File); } closedir(D); @@ -677,9 +683,11 @@ bool SourcesList::ReadDeb822SourceDir(string Dir) { sort(List.begin(), List.end()); // Read the files - for (vector::const_iterator I = List.begin(); I != List.end(); I++) + for (vector::const_iterator I = List.begin(); I != List.end(); I++) { + std::cout << "DEBUG: Parsing .sources file: " << *I << std::endl; if (ReadDeb822SourcePart(*I) == false) return false; + } return true; } From 0eb0093475c91abc8a0a3ac2f11872bd94383b09 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 10:37:35 +0530 Subject: [PATCH 39/43] debug: add detailed debug prints to Deb822 parser to trace stanza and field parsing --- common/rdeb822source.cc | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/common/rdeb822source.cc b/common/rdeb822source.cc index 8e82c4692..1f5696c72 100644 --- a/common/rdeb822source.cc +++ b/common/rdeb822source.cc @@ -3,6 +3,8 @@ #include #include #include +#include // Added for debug prints +#include // Added for debug prints RDeb822Source::RDeb822Source() : enabled(true) @@ -89,4 +91,84 @@ std::string RDeb822Source::trim(const std::string& str) { } size_t end = str.find_last_not_of(whitespace); return str.substr(start, end - start + 1); +} + +bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { + std::cout << "DEBUG: [Deb822Parser] Opening file: " << path << std::endl; + std::ifstream file(path); + if (!file.is_open()) { + std::cout << "DEBUG: [Deb822Parser] Failed to open file: " << path << std::endl; + return false; + } + std::string line; + std::map fields; + int stanza_count = 0; + while (std::getline(file, line)) { + std::cout << "DEBUG: [Deb822Parser] Read line: '" << line << "'" << std::endl; + if (line.empty()) { + if (!fields.empty()) { + std::cout << "DEBUG: [Deb822Parser] End of stanza, fields found:" << std::endl; + for (const auto& kv : fields) { + std::cout << " '" << kv.first << "': '" << kv.second << "'" << std::endl; + } + Deb822Entry entry; + // Check required fields + if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) { + std::cout << "DEBUG: [Deb822Parser] Missing required field in stanza, skipping." << std::endl; + fields.clear(); + continue; + } + entry.Types = fields["Types"]; + entry.URIs = fields["URIs"]; + entry.Suites = fields["Suites"]; + entry.Components = fields.count("Components") ? fields["Components"] : ""; + entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : ""; + entry.Enabled = true; // Default to enabled + entries.push_back(entry); + stanza_count++; + fields.clear(); + } + continue; + } + if (line[0] == '#') { + std::cout << "DEBUG: [Deb822Parser] Skipping comment line." << std::endl; + continue; + } + size_t colon = line.find(':'); + if (colon == std::string::npos) { + std::cout << "DEBUG: [Deb822Parser] No colon found in line, skipping." << std::endl; + continue; + } + std::string key = line.substr(0, colon); + std::string value = line.substr(colon + 1); + // Trim whitespace + key.erase(0, key.find_first_not_of(" \t")); + key.erase(key.find_last_not_of(" \t") + 1); + value.erase(0, value.find_first_not_of(" \t")); + value.erase(value.find_last_not_of(" \t") + 1); + std::cout << "DEBUG: [Deb822Parser] Parsed field: '" << key << "' = '" << value << "'" << std::endl; + fields[key] = value; + } + // Handle last stanza if file does not end with blank line + if (!fields.empty()) { + std::cout << "DEBUG: [Deb822Parser] End of file, last stanza fields:" << std::endl; + for (const auto& kv : fields) { + std::cout << " '" << kv.first << "': '" << kv.second << "'" << std::endl; + } + Deb822Entry entry; + if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) { + std::cout << "DEBUG: [Deb822Parser] Missing required field in last stanza, skipping." << std::endl; + } else { + entry.Types = fields["Types"]; + entry.URIs = fields["URIs"]; + entry.Suites = fields["Suites"]; + entry.Components = fields.count("Components") ? fields["Components"] : ""; + entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : ""; + entry.Enabled = true; + entries.push_back(entry); + stanza_count++; + } + } + std::cout << "DEBUG: [Deb822Parser] Parsed " << stanza_count << " stanzas from file: " << path << std::endl; + return true; } \ No newline at end of file From 0683195925a7ed8acc97996e4d73c51eaac955d8 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 10:49:33 +0530 Subject: [PATCH 40/43] debug: add detailed debug prints to Deb822 parser in rsource_deb822.cc to trace stanza and field parsing --- common/rsource_deb822.cc | 121 ++++++++++++++------------------------- 1 file changed, 43 insertions(+), 78 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 600421a29..b47cdd73f 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -17,117 +17,82 @@ #include bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { + std::cout << "DEBUG: [Deb822Parser] Opening file: " << path << std::endl; std::ifstream file(path); - if (!file) { - return _error->Error(_("Cannot open %s"), path.c_str()); + if (!file.is_open()) { + std::cout << "DEBUG: [Deb822Parser] Failed to open file: " << path << std::endl; + return false; } - - std::map fields; - std::string pending_comments; std::string line; + std::map fields; + int stanza_count = 0; while (std::getline(file, line)) { + std::cout << "DEBUG: [Deb822Parser] Read line: '" << line << "'" << std::endl; if (line.empty()) { if (!fields.empty()) { + std::cout << "DEBUG: [Deb822Parser] End of stanza, fields found:" << std::endl; + for (const auto& kv : fields) { + std::cout << " '" << kv.first << "': '" << kv.second << "'" << std::endl; + } Deb822Entry entry; - // Check required fields - if (fields.find("Types") == fields.end()) { + if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) { + std::cout << "DEBUG: [Deb822Parser] Missing required field in stanza, skipping." << std::endl; fields.clear(); - pending_comments.clear(); continue; } entry.Types = fields["Types"]; - - if (fields.find("URIs") == fields.end()) { - fields.clear(); - pending_comments.clear(); - continue; - } entry.URIs = fields["URIs"]; - - if (fields.find("Suites") == fields.end()) { - fields.clear(); - pending_comments.clear(); - continue; - } entry.Suites = fields["Suites"]; - - // Optional fields - if (fields.find("Components") != fields.end()) { - entry.Components = fields["Components"]; - } - if (fields.find("Signed-By") != fields.end()) { - entry.SignedBy = fields["Signed-By"]; - } - if (fields.find("Architectures") != fields.end()) { - entry.Architectures = fields["Architectures"]; - } - if (fields.find("Languages") != fields.end()) { - entry.Languages = fields["Languages"]; - } - if (fields.find("Targets") != fields.end()) { - entry.Targets = fields["Targets"]; - } - - entry.Enabled = !pending_comments.empty() && pending_comments.find("# Disabled:") == 0 ? false : true; - entry.Comment = pending_comments; + entry.Components = fields.count("Components") ? fields["Components"] : ""; + entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : ""; + entry.Enabled = true; // Default to enabled entries.push_back(entry); + stanza_count++; fields.clear(); - pending_comments.clear(); } continue; } - - // Detect disabled stanza - if (line.find("# Disabled:") == 0) { - pending_comments += line + "\n"; - // Mark as disabled for the next stanza - // We'll check this in the stanza handler above - continue; - } - - // Skip comments if (line[0] == '#') { - pending_comments += line + "\n"; + std::cout << "DEBUG: [Deb822Parser] Skipping comment line." << std::endl; continue; } - - // Check for stanza start - if (line.find("Types:") != std::string::npos) { - size_t colonPos = line.find(':'); - if (colonPos != std::string::npos) { - std::string key = line.substr(0, colonPos); - std::string value = line.substr(colonPos + 1); - - // Trim whitespace - key.erase(0, key.find_first_not_of(" \t")); - key.erase(key.find_last_not_of(" \t") + 1); - value.erase(0, value.find_first_not_of(" \t")); - value.erase(value.find_last_not_of(" \t") + 1); - - fields[key] = value; - } + size_t colon = line.find(':'); + if (colon == std::string::npos) { + std::cout << "DEBUG: [Deb822Parser] No colon found in line, skipping." << std::endl; + continue; } + std::string key = line.substr(0, colon); + std::string value = line.substr(colon + 1); + // Trim whitespace + key.erase(0, key.find_first_not_of(" \t")); + key.erase(key.find_last_not_of(" \t") + 1); + value.erase(0, value.find_first_not_of(" \t")); + value.erase(value.find_last_not_of(" \t") + 1); + std::cout << "DEBUG: [Deb822Parser] Parsed field: '" << key << "' = '" << value << "'" << std::endl; + fields[key] = value; } - // Handle last stanza + // Handle last stanza if file does not end with blank line if (!fields.empty()) { + std::cout << "DEBUG: [Deb822Parser] End of file, last stanza fields:" << std::endl; + for (const auto& kv : fields) { + std::cout << " '" << kv.first << "': '" << kv.second << "'" << std::endl; + } Deb822Entry entry; - - if (fields.find("Types") != fields.end() && fields.find("URIs") != fields.end() && fields.find("Suites") != fields.end()) { + if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) { + std::cout << "DEBUG: [Deb822Parser] Missing required field in last stanza, skipping." << std::endl; + } else { entry.Types = fields["Types"]; entry.URIs = fields["URIs"]; entry.Suites = fields["Suites"]; - if (fields.find("Components") != fields.end()) entry.Components = fields["Components"]; - if (fields.find("Signed-By") != fields.end()) entry.SignedBy = fields["Signed-By"]; - if (fields.find("Architectures") != fields.end()) entry.Architectures = fields["Architectures"]; - if (fields.find("Languages") != fields.end()) entry.Languages = fields["Languages"]; - if (fields.find("Targets") != fields.end()) entry.Targets = fields["Targets"]; + entry.Components = fields.count("Components") ? fields["Components"] : ""; + entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : ""; entry.Enabled = true; - entry.Comment = pending_comments; entries.push_back(entry); + stanza_count++; } } - + std::cout << "DEBUG: [Deb822Parser] Parsed " << stanza_count << " stanzas from file: " << path << std::endl; return true; } From bc23446660d0c8fe2eb9bae6697ef9dace5c0c26 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 11:21:41 +0530 Subject: [PATCH 41/43] test: add global print to confirm rsource_deb822.cc is being used at runtime --- common/rsource_deb822.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index b47cdd73f..111c193f4 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -16,6 +16,10 @@ #include #include +struct Deb822ParserInit { + Deb822ParserInit() { std::cout << "TEST: rsource_deb822.cc is being used" << std::endl; } +} _deb822ParserInit; + bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { std::cout << "DEBUG: [Deb822Parser] Opening file: " << path << std::endl; std::ifstream file(path); From c439acca0c5b54c75e909a6fd9ea33b694ff485e Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 12:19:59 +0530 Subject: [PATCH 42/43] cleanup: remove debug prints and temporary debug code from Deb822 parser and source management --- common/rsource_deb822.cc | 23 +---------------------- common/rsources.cc | 12 ------------ 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 111c193f4..2dc342e4a 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -13,35 +13,22 @@ #include #include "i18n.h" #include -#include #include -struct Deb822ParserInit { - Deb822ParserInit() { std::cout << "TEST: rsource_deb822.cc is being used" << std::endl; } -} _deb822ParserInit; - bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector& entries) { - std::cout << "DEBUG: [Deb822Parser] Opening file: " << path << std::endl; std::ifstream file(path); if (!file.is_open()) { - std::cout << "DEBUG: [Deb822Parser] Failed to open file: " << path << std::endl; return false; } std::string line; std::map fields; int stanza_count = 0; while (std::getline(file, line)) { - std::cout << "DEBUG: [Deb822Parser] Read line: '" << line << "'" << std::endl; if (line.empty()) { if (!fields.empty()) { - std::cout << "DEBUG: [Deb822Parser] End of stanza, fields found:" << std::endl; - for (const auto& kv : fields) { - std::cout << " '" << kv.first << "': '" << kv.second << "'" << std::endl; - } Deb822Entry entry; // Check required fields if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) { - std::cout << "DEBUG: [Deb822Parser] Missing required field in stanza, skipping." << std::endl; fields.clear(); continue; } @@ -58,12 +45,10 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vectorErrno("opendir", _("Unable to read %s"), Dir.c_str()); @@ -220,12 +217,10 @@ bool SourcesList::ReadSources() // Use config or fallback to /etc/apt/sources.list.d/ string Deb822Parts = _config->FindDir("Dir::Etc::sourcelist.d"); - std::cout << "DEBUG: Config returned: '" << Deb822Parts << "'" << std::endl; if (Deb822Parts.empty() || Deb822Parts == "/") Deb822Parts = "/etc/apt/sources.list.d/"; if (FileExists(Deb822Parts) == true) { - std::cout << "DEBUG: Scanning Deb822 sources in directory: " << Deb822Parts << std::endl; Res &= ReadDeb822SourceDir(Deb822Parts); } @@ -631,20 +626,16 @@ ostream &operator<<(ostream &os, const SourcesList::VendorRecord &rec) } bool SourcesList::ReadDeb822SourcePart(string listpath) { - std::cout << "DEBUG: Entering ReadDeb822SourcePart for file: " << listpath << std::endl; vector entries; if (!RDeb822Source::ParseDeb822File(listpath, entries)) { - std::cout << "DEBUG: Failed to parse Deb822 file: " << listpath << std::endl; return false; } - std::cout << "DEBUG: Parsed " << entries.size() << " Deb822 entries from file: " << listpath << std::endl; for (const auto& entry : entries) { SourceRecord rec; rec.SourceFile = listpath; if (!RDeb822Source::ConvertToSourceRecord(entry, rec)) { - std::cout << "DEBUG: Failed to convert Deb822 entry to SourceRecord in file: " << listpath << std::endl; return _error->Error(_("Failed to convert Deb822 entry in %s"), listpath.c_str()); } @@ -656,7 +647,6 @@ bool SourcesList::ReadDeb822SourcePart(string listpath) { } bool SourcesList::ReadDeb822SourceDir(string Dir) { - std::cout << "DEBUG: Entering ReadDeb822SourceDir for directory: " << Dir << std::endl; DIR *D = opendir(Dir.c_str()); if (D == 0) return _error->Errno("opendir", _( "Unable to read %s"), Dir.c_str()); @@ -675,7 +665,6 @@ bool SourcesList::ReadDeb822SourceDir(string Dir) { struct stat St; if (stat(File.c_str(), &St) != 0 || S_ISREG(St.st_mode) == 0) continue; - std::cout << "DEBUG: Found .sources file: " << File << std::endl; List.push_back(File); } closedir(D); @@ -684,7 +673,6 @@ bool SourcesList::ReadDeb822SourceDir(string Dir) { // Read the files for (vector::const_iterator I = List.begin(); I != List.end(); I++) { - std::cout << "DEBUG: Parsing .sources file: " << *I << std::endl; if (ReadDeb822SourcePart(*I) == false) return false; } From cbd4b0113cdcb14c4f946abd4130ed71254a6b11 Mon Sep 17 00:00:00 2001 From: aybanda Date: Sat, 19 Jul 2025 12:43:05 +0530 Subject: [PATCH 43/43] fix: Deb822 round-trip, enabled/disabled state, and field preservation; preserve Deb822 flag on edit --- common/rsource_deb822.cc | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/common/rsource_deb822.cc b/common/rsource_deb822.cc index 2dc342e4a..00dc524b1 100644 --- a/common/rsource_deb822.cc +++ b/common/rsource_deb822.cc @@ -37,7 +37,18 @@ bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector