/* * pamac-vala * * Copyright (C) 2014 Guillaume Benoit * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a get of the GNU General Public License * along with this program. If not, see . */ using Alpm; using Pamac; using Polkit; // i18n const string GETTEXT_PACKAGE = "pacman"; PamacServer server; MainLoop loop; [DBus (name = "org.manjaro.pamac")] public class PamacServer : Object { Alpm.Config alpm_config; Pamac.Config pamac_config; unowned Alpm.DB localdb; unowned Alpm.List syncdbs; public uint64 previous_percent; unowned Alpm.List local_packages; int force_refresh; bool emit_refreshed_signal; public Cond provider_cond; public Mutex provider_mutex; public int? choosen_provider; public signal void emit_event (uint event, string msg); public signal void emit_providers (string depend, string[] providers); public signal void emit_progress (uint progress, string action, string pkgname, int percent, uint n_targets, uint current_target); public signal void emit_download (string filename, uint64 xfered, uint64 total); public signal void emit_totaldownload (uint64 total); public signal void emit_log (uint level, string msg); public signal void emit_updates (Updates updates); public signal void emit_refreshed (ErrorInfos error); public signal void emit_trans_prepared (ErrorInfos error); public signal void emit_trans_committed (ErrorInfos error); public PamacServer () { previous_percent = 0; local_packages = null; get_handle (); force_refresh = 0; emit_refreshed_signal = true; } private void get_handle () { alpm_config = new Alpm.Config ("/etc/pacman.conf"); pamac_config = new Pamac.Config ("/etc/pamac.conf"); alpm_config.handle.eventcb = (EventCallBack) cb_event; alpm_config.handle.progresscb = (ProgressCallBack) cb_progress; alpm_config.handle.questioncb = (QuestionCallBack) cb_question; alpm_config.handle.dlcb = (DownloadCallBack) cb_download; alpm_config.handle.totaldlcb = (TotalDownloadCallBack) cb_totaldownload; alpm_config.handle.logcb = (LogCallBack) cb_log; localdb = alpm_config.handle.localdb; syncdbs = alpm_config.handle.syncdbs; } public void write_config (HashTable new_conf, GLib.BusName sender) { try { Polkit.Authority authority = Polkit.Authority.get_sync (null); Polkit.Subject subject = Polkit.SystemBusName.new (sender); Polkit.AuthorizationResult result = authority.check_authorization_sync (subject, "org.manjaro.pamac.commit", null, Polkit.CheckAuthorizationFlags.ALLOW_USER_INTERACTION, null); if (result.get_is_authorized ()) { pamac_config.write (new_conf); } } catch (GLib.Error e) { stderr.printf ("%s\n", e.message); } } private void refresh_real () { ErrorInfos err = ErrorInfos (); string[] details = {}; uint success = 0; int ret = alpm_config.handle.trans_init (0); if (ret == 0) { foreach (unowned DB db in syncdbs) { ret = db.update (force_refresh); if (ret >= 0) { success++; } } // We should always succeed if at least one DB was upgraded - we may possibly // fail later with unresolved deps, but that should be rare, and would be expected if (success == 0) { err.str = _("failed to synchronize any databases\n"); details += Alpm.strerror (alpm_config.handle.errno ()) + "\n"; err.details = details; } trans_release (); get_handle (); if (emit_refreshed_signal) emit_refreshed (err); else emit_refreshed_signal = true; } } public async void refresh (int force, bool emit_signal) { force_refresh = force; emit_refreshed_signal = emit_signal; try { new Thread.try ("refresh thread", (ThreadFunc) refresh_real); } catch (GLib.Error e) { stderr.printf ("%s\n", e.message); } } public UpdatesInfos[] get_updates () { UpdatesInfos[] updates = {}; updates = get_syncfirst_updates (alpm_config); if (updates.length != 0) { return updates; } else { string[] ignore_pkgs = get_ignore_pkgs (alpm_config); updates = get_repos_updates (alpm_config, ignore_pkgs); if (pamac_config.enable_aur) { UpdatesInfos[] aur_updates = get_aur_updates (alpm_config, ignore_pkgs); foreach (UpdatesInfos infos in aur_updates) updates += infos; } return updates; } } public ErrorInfos trans_init (TransFlag transflags) { ErrorInfos err = ErrorInfos (); int ret = alpm_config.handle.trans_init (transflags); if (ret == -1) { //err = _("failed to init transaction (%s)\n").printf (Alpm.strerror (errno)); err.str = Alpm.strerror (alpm_config.handle.errno ()) + "\n"; } return err; } public ErrorInfos trans_sysupgrade (int enable_downgrade) { ErrorInfos err = ErrorInfos (); int ret = alpm_config.handle.trans_sysupgrade (enable_downgrade); if (ret == -1) {; err.str = Alpm.strerror (alpm_config.handle.errno ()) + "\n"; } return err; } public ErrorInfos trans_add_pkg (string pkgname) { ErrorInfos err = ErrorInfos (); unowned Package? pkg = null; //pkg = alpm_config.handle.find_dbs_satisfier (syncdbs, pkgname); foreach (var db in syncdbs) { pkg = find_satisfier (db.pkgcache, pkgname); if (pkg != null) break; } if (pkg == null) { err.str = _("target not found: %s\n").printf (pkgname); return err; } int ret = alpm_config.handle.trans_add_pkg (pkg); if (ret == -1) { Alpm.Errno errno = alpm_config.handle.errno (); if (errno == Errno.TRANS_DUP_TARGET || errno == Errno.PKG_IGNORED) // just skip duplicate or ignored targets return err; else { err.str = "'%s': %s\n".printf (pkg.name, Alpm.strerror (errno)); return err; } } return err; } public ErrorInfos trans_load_pkg (string pkgpath) { ErrorInfos err = ErrorInfos (); unowned Package? pkg = alpm_config.handle.load_file (pkgpath, 1, alpm_config.handle.localfilesiglevel); if (pkg == null) { err.str = "'%s': %s\n".printf (pkgpath, Alpm.strerror (alpm_config.handle.errno ())); return err; } else { int ret = alpm_config.handle.trans_add_pkg (pkg); if (ret == -1) { Alpm.Errno errno = alpm_config.handle.errno (); if (errno == Errno.TRANS_DUP_TARGET || errno == Errno.PKG_IGNORED) { // just skip duplicate or ignored targets return err; } else { err.str = "'%s': %s\n".printf (pkg.name, Alpm.strerror (errno)); return err; } } } return err; } public ErrorInfos trans_remove_pkg (string pkgname) { ErrorInfos err = ErrorInfos (); unowned Package? pkg = localdb.get_pkg (pkgname); if (pkg == null) { err.str = _("target not found: %s\n").printf (pkgname); return err; } int ret = alpm_config.handle.trans_remove_pkg (pkg); if (ret == -1) { err.str = "'%s': %s\n".printf (pkg.name, Alpm.strerror (alpm_config.handle.errno ())); } return err; } public void trans_prepare_real () { ErrorInfos err = ErrorInfos (); string[] details = {}; Alpm.List err_data = null; int ret = alpm_config.handle.trans_prepare (out err_data); if (ret == -1) { Alpm.Errno errno = alpm_config.handle.errno (); //err = _("failed to prepare transaction (%s)\n").printf (); err.str = Alpm.strerror (errno) + "\n"; switch (errno) { case Errno.PKG_INVALID_ARCH: foreach (void *i in err_data) { char *pkgname = i; details += _("package %s does not have a valid architecture\n").printf (pkgname); } break; case Errno.UNSATISFIED_DEPS: foreach (void *i in err_data) { DepMissing *miss = i; string depstring = miss->depend.compute_string (); details += _("%s: requires %s\n").printf (miss->target, depstring); } break; case Errno.CONFLICTING_DEPS: foreach (void *i in err_data) { Conflict *conflict = i; // only print reason if it contains new information if (conflict->reason.mod == DepMod.ANY) details += _("%s and %s are in conflict\n").printf (conflict->package1, conflict->package2); else { string reason = conflict->reason.compute_string (); details += _("%s and %s are in conflict (%s)\n").printf (conflict->package1, conflict->package2, reason); } } break; default: break; } err.details = details; trans_release (); } emit_trans_prepared (err); } public async void trans_prepare () { try { new Thread.try ("prepare thread", (ThreadFunc) trans_prepare_real); } catch (GLib.Error e) { stderr.printf ("%s\n", e.message); } } public void choose_provider (int provider) { provider_mutex.lock (); choosen_provider = provider; provider_cond.signal (); provider_mutex.unlock (); } public UpdatesInfos[] trans_to_add () { UpdatesInfos info = UpdatesInfos (); UpdatesInfos[] infos = {}; foreach (unowned Package pkg in alpm_config.handle.trans_to_add ()) { info.name = pkg.name; info.version = pkg.version; // if pkg was load from a file, pkg.db is null if (pkg.db != null) info.db_name = pkg.db.name; else info.db_name = ""; info.tarpath = ""; info.download_size = pkg.download_size; infos += info; } return infos; } public UpdatesInfos[] trans_to_remove () { UpdatesInfos info = UpdatesInfos (); UpdatesInfos[] infos = {}; foreach (unowned Package pkg in alpm_config.handle.trans_to_remove ()) { info.name = pkg.name; info.version = pkg.version; info.db_name = pkg.db.name; info.tarpath = ""; info.download_size = pkg.download_size; infos += info; } return infos; } private void trans_commit_real () { ErrorInfos err = ErrorInfos (); string[] details = {}; Alpm.List err_data = null; int ret = alpm_config.handle.trans_commit (out err_data); if (ret == -1) { Alpm.Errno errno = alpm_config.handle.errno (); //err = _("failed to commit transaction (%s)\n").printf (Alpm.strerror (errno)); err.str = Alpm.strerror (errno) + "\n"; switch (errno) { case Alpm.Errno.FILE_CONFLICTS: TransFlag flags = alpm_config.handle.trans_get_flags (); if ((flags & TransFlag.FORCE) != 0) { details += _("unable to %s directory-file conflicts\n").printf ("--force"); } foreach (void *i in err_data) { FileConflict *conflict = i; switch (conflict->type) { case FileConflictType.TARGET: details += _("%s exists in both '%s' and '%s'\n").printf (conflict->file, conflict->target, conflict->ctarget); break; case FileConflictType.FILESYSTEM: details += _("%s: %s exists in filesystem\n").printf (conflict->target, conflict->file); break; } } break; case Alpm.Errno.PKG_INVALID: case Alpm.Errno.PKG_INVALID_CHECKSUM: case Alpm.Errno.PKG_INVALID_SIG: case Alpm.Errno.DLT_INVALID: foreach (void *i in err_data) { char *filename = i; details += _("%s is invalid or corrupted\n").printf (filename); } break; default: break; } err.details = details; } trans_release (); get_handle (); emit_trans_committed (err); } public async void trans_commit (GLib.BusName sender) { try { Polkit.Authority authority = Polkit.Authority.get_sync (null); Polkit.Subject subject = Polkit.SystemBusName.new (sender); Polkit.AuthorizationResult result = authority.check_authorization_sync (subject, "org.manjaro.pamac.commit", null, Polkit.CheckAuthorizationFlags.ALLOW_USER_INTERACTION, null); if (result.get_is_authorized ()) { new Thread.try ("commit thread", (ThreadFunc) trans_commit_real); } else { ErrorInfos err = ErrorInfos (); err.str = "Not Authorized\n"; emit_trans_committed (err); trans_release (); } } catch (GLib.Error e) { stderr.printf ("%s\n", e.message); } } public int trans_release () { return alpm_config.handle.trans_release (); } public void trans_cancel () { alpm_config.handle.trans_interrupt (); alpm_config.handle.trans_release (); get_handle (); } public void quit () { GLib.File lockfile = GLib.File.new_for_path ("/var/lib/pacman/db.lck"); if (lockfile.query_exists () == false) loop.quit (); } // End of Server Object } private void write_log_file (string event) { var now = new DateTime.now_local (); string log = "%s %s".printf (now.format ("[%Y-%m-%d %H:%M]"), event); var file = GLib.File.new_for_path ("/var/log/pamac.log"); try { // creating a DataOutputStream to the file var dos = new DataOutputStream (file.append_to (FileCreateFlags.NONE)); // writing a short string to the stream dos.put_string (log); } catch (GLib.Error e) { GLib.stderr.printf("%s\n", e.message); } } private void cb_event (Event event, void *data1, void *data2) { string event_str = null; switch (event) { case Event.CHECKDEPS_START: event_str = _("checking dependencies...\n"); break; case Event.FILECONFLICTS_START: event_str = _("checking for file conflicts...\n"); break; case Event.RESOLVEDEPS_START: event_str = _("resolving dependencies...\n"); break; case Event.INTERCONFLICTS_START: event_str = _("looking for inter-conflicts...\n"); break; case Event.ADD_START: unowned Package pkg = (Package) data1; event_str = _("installing %s...\n").printf ("%s (%s)".printf (pkg.name, pkg.version)); break; case Event.ADD_DONE: unowned Package pkg = (Package) data1; string log = "installed %s (%s)\n".printf (pkg.name, pkg.version); write_log_file (log); break; case Event.REMOVE_START: unowned Package pkg = (Package) data1; event_str = _("removing %s...\n").printf ("%s (%s)".printf (pkg.name, pkg.version)); break; case Event.REMOVE_DONE: unowned Package pkg = (Package) data1; string log = "removed %s (%s)\n".printf (pkg.name, pkg.version); write_log_file (log); break; case Event.UPGRADE_START: unowned Package new_pkg = (Package) data1; unowned Package old_pkg = (Package) data2; event_str = _("upgrading %s...\n").printf ("%s (%s -> %s)".printf (old_pkg.name, old_pkg.version, new_pkg.version)); break; case Event.UPGRADE_DONE: unowned Package new_pkg = (Package) data1; unowned Package old_pkg = (Package) data2; string log = _("upgraded %s (%s -> %s)\n").printf (old_pkg.name, old_pkg.version, new_pkg.version); write_log_file (log); break; case Event.DOWNGRADE_START: unowned Package new_pkg = (Package) data1; unowned Package old_pkg = (Package) data2; event_str = _("downgrading %s...\n").printf ("%s (%s -> %s)".printf (old_pkg.name, old_pkg.version, new_pkg.version)); break; case Event.DOWNGRADE_DONE: unowned Package new_pkg = (Package) data1; unowned Package old_pkg = (Package) data2; string log = _("downgraded %s (%s -> %s)\n").printf (old_pkg.name, old_pkg.version, new_pkg.version); write_log_file (log); break; case Event.REINSTALL_START: unowned Package pkg = (Package) data1; event_str = _("reinstalling %s...\n").printf ("%s (%s)".printf (pkg.name, pkg.version)); break; case Event.REINSTALL_DONE: unowned Package pkg = (Package) data1; string log = "reinstalled %s (%s)\n".printf (pkg.name, pkg.version); write_log_file (log); break; case Event.INTEGRITY_START: event_str = _("checking package integrity...\n"); break; case Event.KEYRING_START: event_str = _("checking keyring...\n"); break; case Event.KEY_DOWNLOAD_START: event_str = _("downloading required keys...\n"); break; case Event.LOAD_START: event_str = _("loading package files...\n"); break; case Event.DELTA_INTEGRITY_START: event_str = _("checking delta integrity...\n"); break; case Event.DELTA_PATCHES_START: event_str = _("applying deltas...\n"); break; case Event.DELTA_PATCH_START: unowned string string1 = (string) data1; unowned string string2 = (string) data2; event_str = _("generating %s with %s... ").printf (string1, string2); break; case Event.DELTA_PATCH_DONE: event_str = _("success!\n"); break; case Event.DELTA_PATCH_FAILED: event_str = _("failed.\n"); break; case Event.SCRIPTLET_INFO: unowned string info = (string) data1; event_str = info; write_log_file (event_str); break; case Event.RETRIEVE_START: event_str = _("Retrieving packages ...\n"); break; case Event.DISKSPACE_START: event_str = _("checking available disk space...\n"); break; case Event.OPTDEP_REQUIRED: unowned Package pkg = (Package) data1; Depend *depend = data2; event_str = _("%s optionally requires %s\n").printf (pkg.name, depend->compute_string ()); break; case Event.DATABASE_MISSING: event_str = _("database file for '%s' does not exist\n").printf ((char *) data1); break; /* all the simple done events, with fallthrough for each */ case Event.FILECONFLICTS_DONE: case Event.CHECKDEPS_DONE: case Event.RESOLVEDEPS_DONE: case Event.INTERCONFLICTS_DONE: case Event.INTEGRITY_DONE: case Event.KEYRING_DONE: case Event.KEY_DOWNLOAD_DONE: case Event.LOAD_DONE: case Event.DELTA_INTEGRITY_DONE: case Event.DELTA_PATCHES_DONE: case Event.DISKSPACE_DONE: /* nothing */ break; default: event_str = "unknown event"; break; } if (event_str != null) server.emit_event ((uint) event, event_str); } private void cb_question (Question question, void *data1, void *data2, void *data3, out int response) { switch (question) { case Question.INSTALL_IGNOREPKG: // Do not install package in IgnorePkg/IgnoreGroup response = 0; break; case Question.REPLACE_PKG: // Auto-remove conflicts in case of replaces response = 1; break; case Question.CONFLICT_PKG: // Auto-remove conflicts response = 1; break; case Question.REMOVE_PKGS: // Do not upgrade packages which have unresolvable dependencies response = 1; break; case Question.SELECT_PROVIDER: unowned Alpm.List providers = (Alpm.List) data1; Depend *depend = data2; string depend_str = depend->compute_string (); string[] providers_str = {}; foreach (unowned Package pkg in providers) { providers_str += pkg.name; } server.provider_cond = Cond (); server.provider_mutex = Mutex (); server.choosen_provider = null; server.emit_providers (depend_str, providers_str); server.provider_mutex.lock (); while (server.choosen_provider == null) { server.provider_cond.wait (server.provider_mutex); } response = server.choosen_provider; server.provider_mutex.unlock (); break; case Question.CORRUPTED_PKG: // Auto-remove corrupted pkgs in cache response = 1; break; case Question.IMPORT_KEY: PGPKey *key = data1; // Do not get revoked key if (key->revoked == 1) response = 0; // Auto get not revoked key else response = 1; break; default: response = 0; break; } } private void cb_progress (Progress progress, string pkgname, int percent, uint n_targets, uint current_target) { string action = ""; switch (progress) { case Progress.ADD_START: action = _("installing"); break; case Progress.UPGRADE_START: action = _("upgrading"); break; case Progress.DOWNGRADE_START: action = _("downgrading"); break; case Progress.REINSTALL_START: action = _("reinstalling"); break; case Progress.REMOVE_START: action = _("removing"); break; case Progress.CONFLICTS_START: action = _("checking for file conflicts"); break; case Progress.DISKSPACE_START: action = _("checking available disk space"); break; case Progress.INTEGRITY_START: action = _("checking package integrity"); break; case Progress.KEYRING_START: action = _("checking keys in keyring"); break; case Progress.LOAD_START: action = _("loading package files"); break; default: break; } if ((uint64) percent != server.previous_percent) { server.previous_percent = (uint64) percent; server.emit_progress ((uint) progress, action, pkgname, percent, n_targets, current_target); } } private void cb_download (string filename, uint64 xfered, uint64 total) { if (xfered != server.previous_percent) { server.previous_percent = xfered; server.emit_download (filename, xfered, total); } } private void cb_totaldownload (uint64 total) { server.emit_totaldownload (total); } private void cb_log (LogLevel level, string fmt, va_list args) { LogLevel logmask = LogLevel.ERROR | LogLevel.WARNING; if ((level & logmask) == 0) return; string? log = null; log = fmt.vprintf (args); if (log != null) server.emit_log ((uint) level, log); } void on_bus_acquired (DBusConnection conn) { server = new PamacServer (); try { conn.register_object ("/org/manjaro/pamac", server); } catch (IOError e) { stderr.printf ("Could not register service\n"); } } void main () { // i18n Intl.setlocale(LocaleCategory.ALL, ""); Intl.textdomain(GETTEXT_PACKAGE); Bus.own_name(BusType.SYSTEM, "org.manjaro.pamac", BusNameOwnerFlags.NONE, on_bus_acquired, () => {}, () => stderr.printf("Could not acquire name\n")); loop = new MainLoop (); loop.run (); }