Skip to content
Snippets Groups Projects
database.vala 71.5 KiB
Newer Older
/*
 *  pamac-vala
 *
guinux's avatar
guinux committed
 *  Copyright (C) 2019 Guillaume Benoit <guillaume@manjaro.org>
 *
 *  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 <http://www.gnu.org/licenses/>.
 */

namespace Pamac {
	public class Database: Object {

		class AURUpdates {
			public List<AURPackage> updates;
			public List<AURPackage> outofdate;
			public AURUpdates (owned List<AURPackage> updates, owned List<AURPackage> outofdate) {
				this.updates = (owned) updates;
				this.outofdate = (owned) outofdate;
			}
		}

guinux's avatar
guinux committed
		AlpmConfig alpm_config;
		Alpm.Handle? alpm_handle;
		Alpm.Handle? files_handle;
		MainLoop loop;
		AUR aur;
		As.Store app_store;
		string locale;
guinux's avatar
guinux committed
		GenericSet<string?> already_vcs_checked;
guinux's avatar
guinux committed
		#if ENABLE_SNAP
		SnapPlugin snap_plugin;
		#endif

		public Config config { get; construct set; }

guinux's avatar
guinux committed
		public signal void get_updates_progress (uint percent);

		public Database (Config config) {
			Object (config: config);
		}

		construct {
guinux's avatar
guinux committed
			loop = new MainLoop ();
guinux's avatar
guinux committed
			already_vcs_checked = new GenericSet<string?>  (str_hash, str_equal);
guinux's avatar
guinux committed
			refresh ();
guinux's avatar
guinux committed
			aur = new AUR ();
guinux's avatar
guinux committed
			// init appstream
			app_store = new As.Store ();
			app_store.set_add_flags (As.StoreAddFlags.USE_UNIQUE_ID
									| As.StoreAddFlags.ONLY_NATIVE_LANGS
									| As.StoreAddFlags.USE_MERGE_HEURISTIC);
			locale = Environ.get_variable (Environ.get (), "LANG");
			if (locale != null) {
				// remove .UTF-8 from locale
				locale = locale.split (".")[0];
			} else {
				locale = "C";
			}
			// load alpm databases in memory
			unowned Alpm.List<unowned Alpm.Package> pkgcache = alpm_handle.localdb.pkgcache;
			unowned Alpm.List<unowned Alpm.Group> groupcache = alpm_handle.localdb.groupcache;
			unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
			while (syncdbs != null) {
				unowned Alpm.DB db = syncdbs.data;
				pkgcache = db.pkgcache;
				groupcache = db.groupcache;
				syncdbs.next ();
			}
guinux's avatar
guinux committed
			#if ENABLE_SNAP
			// load snap plugin
			if (config.support_snap) {
				snap_plugin = config.get_snap_plugin ();
guinux's avatar
guinux committed
			}
guinux's avatar
guinux committed
			#endif
guinux's avatar
guinux committed
		}

		public void enable_appstream () {
guinux's avatar
guinux committed
			try {
				app_store.load (As.StoreLoadFlags.APP_INFO_SYSTEM);
				app_store.set_search_match (As.AppSearchMatch.PKGNAME
											| As.AppSearchMatch.DESCRIPTION
											| As.AppSearchMatch.NAME
											| As.AppSearchMatch.KEYWORD);
			} catch (Error e) {
guinux's avatar
guinux committed
				critical ("%s\n", e.message);
guinux's avatar
guinux committed
			}
guinux's avatar
guinux committed
		}

		public void refresh () {
guinux's avatar
guinux committed
			alpm_config = new AlpmConfig ("/etc/pacman.conf");
			alpm_handle = alpm_config.get_handle ();
			if (alpm_handle == null) {
				critical (dgettext (null, "Failed to initialize alpm library"));
				return;
			} else {
				files_handle = alpm_config.get_handle (true);
			}
guinux's avatar
guinux committed
			already_vcs_checked.remove_all ();
guinux's avatar
guinux committed
		public List<string> get_mirrors_countries () {
			var countries = new List<string> ();
guinux's avatar
guinux committed
				string countries_str;
				int status;
				Process.spawn_command_line_sync ("pacman-mirrors -l",
											out countries_str,
											null,
											out status);
				if (status == 0) {
					foreach (unowned string country in countries_str.split ("\n")) {
						if (country != "") {
							countries.append (country);
						}
					}
				}
			} catch (SpawnError e) {
guinux's avatar
guinux committed
				critical ("%s\n", e.message);
			}
			return countries;
		}

		public string get_mirrors_choosen_country () {
			string country = "";
			try {
guinux's avatar
guinux committed
				string countries_str;
				int status;
				Process.spawn_command_line_sync ("pacman-mirrors -lc",
											out countries_str,
											null,
											out status);
				if (status == 0) {
					// only take first country
					country = countries_str.split ("\n", 2)[0];
				}
			} catch (SpawnError e) {
guinux's avatar
guinux committed
				critical ("%s\n", e.message);
		public string get_alpm_dep_name (string dep_string) {
			return Alpm.Depend.from_string (dep_string).name;
		}

		public CompareFunc<string> vercmp = Alpm.pkg_vercmp;

guinux's avatar
guinux committed
		public HashTable<string, int64?> get_clean_cache_details () {
guinux's avatar
guinux committed
			var filenames_size = new HashTable<string, int64?> (str_hash, str_equal);
			var pkg_version_filenames = new HashTable<string, SList<string>> (str_hash, str_equal);
			var pkg_versions = new HashTable<string, SList<string>> (str_hash, str_equal);
			// compute all infos
			unowned Alpm.List<unowned string> cachedirs_names = alpm_handle.cachedirs;
			while (cachedirs_names != null) {
				unowned string cachedir_name = cachedirs_names.data;
				var cachedir = File.new_for_path (cachedir_name);
				try {
					FileEnumerator enumerator = cachedir.enumerate_children ("standard::*", FileQueryInfoFlags.NONE);
					FileInfo info;
					while ((info = enumerator.next_file (null)) != null) {
						unowned string filename = info.get_name ();
						string absolute_filename = "%s%s".printf (cachedir_name, filename);
						string name_version_release = filename.slice (0, filename.last_index_of_char ('-'));
						int release_index = name_version_release.last_index_of_char ('-');
						string name_version = name_version_release.slice (0, release_index);
						int version_index = name_version.last_index_of_char ('-');
						string name = name_version.slice (0, version_index);
guinux's avatar
guinux committed
						if (config.clean_rm_only_uninstalled && is_installed_pkg (name)) {
guinux's avatar
guinux committed
							continue;
						}
						filenames_size.insert (absolute_filename, info.get_size ());
						if (pkg_versions.contains (name)) {
							if (pkg_version_filenames.contains (name_version_release)) {
								// case of .sig file
								unowned SList<string> filenames = pkg_version_filenames.lookup (name_version_release);
								filenames.append (absolute_filename);
							} else {
								unowned SList<string> versions = pkg_versions.lookup (name);
								string version = name_version.slice (version_index + 1, name_version.length);
								string release = name_version_release.slice (release_index + 1, name_version_release.length);
								string version_release = "%s-%s".printf (version, release);
								versions.append ((owned) version_release);
								var filenames = new SList<string> ();
								filenames.append (absolute_filename);
								pkg_version_filenames.insert (name_version_release, (owned) filenames);
							}
						} else {
							var versions = new SList<string> ();
							string version = name_version.slice (version_index + 1, name_version.length);
							string release = name_version_release.slice (release_index + 1, name_version_release.length);
							string version_release = "%s-%s".printf (version, release);
							versions.append ((owned) version_release);
							pkg_versions.insert (name, (owned) versions);
							var filenames = new SList<string> ();
							filenames.append (absolute_filename);
							pkg_version_filenames.insert (name_version_release, (owned) filenames);
						}
					}
				} catch (GLib.Error e) {
guinux's avatar
guinux committed
					critical ("%s\n", e.message);
guinux's avatar
guinux committed
				}
				cachedirs_names.next ();
			}
guinux's avatar
guinux committed
			if (config.clean_keep_num_pkgs == 0) {
guinux's avatar
guinux committed
				return filenames_size;
			}
			// filter candidates
			var iter = HashTableIter<string, SList<string>> (pkg_versions);
			unowned string name;
			unowned SList<string> versions;
			while (iter.next (out name, out versions)) {
				// sort versions
				uint length = versions.length ();
guinux's avatar
guinux committed
				if (length > config.clean_keep_num_pkgs) {
guinux's avatar
guinux committed
					versions.sort ((version1, version2) => {
						// reverse version 1 and version2 to have higher versions first
						return Alpm.pkg_vercmp (version2, version1);
					});
				}
				uint i = 1;
				foreach (unowned string version in versions) {
					unowned SList<string>? filenames = pkg_version_filenames.lookup ("%s-%s".printf (name, version));
					if (filenames != null) {
						foreach (unowned string filename in filenames) {
							filenames_size.remove (filename);
						}
					}
					i++;
guinux's avatar
guinux committed
					if (i > config.clean_keep_num_pkgs) {
guinux's avatar
guinux committed
						break;
					}
				}
			}
			return filenames_size;
		}

guinux's avatar
guinux committed
		async void enumerate_directory (string directory_path, HashTable<string, int64?> filenames_size) {
guinux's avatar
guinux committed
			var directory = GLib.File.new_for_path (directory_path);
guinux's avatar
guinux committed
			if (!directory.query_exists ()) {
				return;
			}
guinux's avatar
guinux committed
			try {
guinux's avatar
guinux committed
				FileEnumerator enumerator = yield directory.enumerate_children_async ("standard::*", FileQueryInfoFlags.NONE);
guinux's avatar
guinux committed
				FileInfo info;
				while ((info = enumerator.next_file (null)) != null) {
					string absolute_filename = Path.build_path ("/", directory.get_path (), info.get_name ());
					if (info.get_file_type () == FileType.DIRECTORY) {
guinux's avatar
guinux committed
						yield enumerate_directory (absolute_filename, filenames_size);
guinux's avatar
guinux committed
					} else {
						filenames_size.insert (absolute_filename, info.get_size ());
					}
				}
			} catch (GLib.Error e) {
guinux's avatar
guinux committed
				critical ("%s\n", e.message);
guinux's avatar
guinux committed
		public HashTable<string, int64?> get_build_files_details () {
guinux's avatar
guinux committed
			var filenames_size = new HashTable<string, int64?> (str_hash, str_equal);
guinux's avatar
guinux committed
			enumerate_directory.begin (config.aur_build_dir, filenames_size, (obj, res) => {
				loop.quit ();
			});
			loop.run ();
guinux's avatar
guinux committed
			return filenames_size;
		}

guinux's avatar
guinux committed
		public bool is_installed_pkg (string pkgname) {
			return alpm_handle.localdb.get_pkg (pkgname) != null;
		}

guinux's avatar
guinux committed
		public AlpmPackage? get_installed_pkg (string pkgname) {
guinux's avatar
guinux committed
			return initialise_pkg (alpm_handle.localdb.get_pkg (pkgname));
guinux's avatar
guinux committed
		public bool has_installed_satisfier (string depstring) {
			return Alpm.find_satisfier (alpm_handle.localdb.pkgcache, depstring) != null;
		}

		public AlpmPackage? get_installed_satisfier (string depstring) {
guinux's avatar
guinux committed
			return initialise_pkg (Alpm.find_satisfier (alpm_handle.localdb.pkgcache, depstring));
		}

		public bool should_hold (string pkgname) {
guinux's avatar
guinux committed
			if (alpm_config.get_holdpkgs ().find_custom (pkgname, strcmp) != null) {
				return true;
			}
			return false;
		}

		public uint get_pkg_reason (string pkgname) {
guinux's avatar
guinux committed
			unowned Alpm.Package? pkg = alpm_handle.localdb.get_pkg (pkgname);
			if (pkg != null) {
				return pkg.reason;
			}
			return 0;
		}

		public List<string> get_uninstalled_optdeps (string pkgname) {
			var optdeps = new List<string> ();
guinux's avatar
guinux committed
			new Thread<int> ("get_uninstalled_optdeps", () => {
				unowned Alpm.Package? pkg = get_syncpkg (pkgname);
				if (pkg != null) {
					unowned Alpm.List<unowned Alpm.Depend> optdepends = pkg.optdepends;
					while (optdepends != null) {
						string optdep = optdepends.data.compute_string ();
						unowned Alpm.Package? satisfier = Alpm.find_satisfier (alpm_handle.localdb.pkgcache, optdep);
						if (satisfier == null) {
							optdeps.append ((owned) optdep);
						}
						optdepends.next ();
guinux's avatar
guinux committed
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) optdeps;
guinux's avatar
guinux committed
		string get_localized_string (HashTable<string,string> hashtable) {
			unowned string val;
			if (!hashtable.lookup_extended (locale, null, out val)) {
				// try with just the language
				if (!hashtable.lookup_extended (locale.split ("_")[0], null, out val)) {
					// try C locale
					if (!hashtable.lookup_extended ("C", null, out val)) {
						return "";
					}
				}
			}
			return val;
		}

		string get_app_name (As.App app) {
			return get_localized_string (app.get_names ());
		}

guinux's avatar
guinux committed
		string get_app_launchable (As.App app) {
			As.Launchable? launchable = app.get_launchable_by_kind (As.LaunchableKind.DESKTOP_ID);
			if (launchable != null) {
				return launchable.get_value ();
			}
			return "";
		}

guinux's avatar
guinux committed
		string get_app_summary (As.App app) {
			return get_localized_string (app.get_comments ());
		}

		string get_app_description (As.App app) {
			return get_localized_string (app.get_descriptions ());
		}

		string get_app_icon (As.App app, string dbname) {
			string icon = "";
			app.get_icons ().foreach ((as_icon) => {
				if (as_icon.get_kind () == As.IconKind.CACHED) {
					if (as_icon.get_height () == 64) {
						icon = "/usr/share/app-info/icons/archlinux-arch-%s/64x64/%s".printf (dbname, as_icon.get_name ());
					}
				}
			});
			return icon;
		}

guinux's avatar
guinux committed
		List<string> get_app_screenshots (As.App app) {
			var screenshots = new List<string> ();
guinux's avatar
guinux committed
			app.get_screenshots ().foreach ((as_screenshot) => {
guinux's avatar
guinux committed
				As.Image? as_image = as_screenshot.get_source ();
				if (as_image != null) {
					string? url = as_image.get_url ();
					if (url != null) {
						screenshots.append ((owned) url);
guinux's avatar
guinux committed
			return (owned) screenshots;
guinux's avatar
guinux committed
		SList<As.App> get_pkgname_matching_apps (string pkgname) {
			var matching_apps = new SList<As.App> ();
guinux's avatar
guinux committed
			app_store.get_apps ().foreach ((app) => {
guinux's avatar
guinux committed
				if (app.get_kind () == As.AppKind.DESKTOP) {
					if (app.get_pkgname_default () == pkgname) {
						matching_apps.append (app);
					}
guinux's avatar
guinux committed
				}
			});
guinux's avatar
guinux committed
			return (owned) matching_apps;
guinux's avatar
guinux committed
		AlpmPackage? initialise_pkg (Alpm.Package? alpm_pkg) {
			if (alpm_pkg == null) {
				return null;
			}
			var pkg = new AlpmPackage ();
			initialise_pkg_common (alpm_pkg, ref pkg);
			if (alpm_pkg.origin == Alpm.Package.From.SYNCDB) {
				pkg.repo = alpm_pkg.db.name;
			} else if (alpm_pkg.origin == Alpm.Package.From.LOCALDB) {
				unowned Alpm.Package? sync_pkg = get_syncpkg (alpm_pkg.name);
				if (sync_pkg != null) {
					pkg.repo = sync_pkg.db.name;
				} else if (config.enable_aur) {
					if (aur.get_infos (alpm_pkg.name) != null) {
						pkg.repo = dgettext (null, "AUR");
guinux's avatar
guinux committed
			// find if pkgname provides only one app
			var matching_apps = get_pkgname_matching_apps (alpm_pkg.name);
			if (matching_apps.length () == 1) {
				initialize_app_data (matching_apps.nth_data (0), ref pkg);
			}
guinux's avatar
guinux committed
			return pkg;
guinux's avatar
guinux committed
		void initialise_pkg_common (Alpm.Package alpm_pkg, ref AlpmPackage pkg) {
			// name
			pkg.name = alpm_pkg.name;
			// version
			pkg.version = alpm_pkg.version;
			// desc can be null
			if (alpm_pkg.desc != null) {
				pkg.desc = alpm_pkg.desc;
			}
			// url can be null
			if (alpm_pkg.url != null) {
				pkg.url = alpm_pkg.url;
			}
			// packager can be null
			pkg.packager = alpm_pkg.packager ?? "";
			// groups
			unowned Alpm.List<unowned string> list = alpm_pkg.groups;
			while (list != null) {
				pkg.groups_priv.append (list.data);
				list.next ();
			}
			// licenses
			list = alpm_pkg.licenses;
			while (list != null) {
				pkg.licenses_priv.append (list.data);
				list.next ();
			}
			// build_date
			pkg.builddate = alpm_pkg.builddate;
			// installed_size
			pkg.installed_size = alpm_pkg.isize;
			// installed_size
			pkg.download_size = alpm_pkg.download_size;
			// local pkg
			if (alpm_pkg.origin == Alpm.Package.From.LOCALDB) {
				// installed_version
				pkg.installed_version = alpm_pkg.version;
				// reason
				if (alpm_pkg.reason == Alpm.Package.Reason.EXPLICIT) {
					pkg.reason = dgettext (null, "Explicitly installed");
				} else if (alpm_pkg.reason == Alpm.Package.Reason.DEPEND) {
					pkg.reason = dgettext (null, "Installed as a dependency for another package");
				} else {
					pkg.reason = dgettext (null, "Unknown");
				}
				// install_date
				pkg.installdate = alpm_pkg.installdate;
				// backups
				unowned Alpm.List<unowned Alpm.Backup> backups_list = alpm_pkg.backups;
				while (backups_list != null) {
					pkg.backups_priv.append ("/" + backups_list.data.name);
					backups_list.next ();
				}
				// requiredby
				Alpm.List<string> pkg_requiredby = alpm_pkg.compute_requiredby ();
				unowned Alpm.List<string> string_list = pkg_requiredby;
				while (string_list != null) {
					pkg.requiredby_priv.append ((owned) string_list.data);
					string_list.next ();
				}
				// optionalfor
				Alpm.List<string> pkg_optionalfor = alpm_pkg.compute_optionalfor ();
				string_list = pkg_optionalfor;
				while (string_list != null) {
					pkg.optionalfor_priv.append ((owned) string_list.data);
					string_list.next ();
				}
			// sync pkg
			} else if (alpm_pkg.origin == Alpm.Package.From.SYNCDB) {
				// installed_version
				unowned Alpm.Package? local_pkg = alpm_handle.localdb.get_pkg (alpm_pkg.name);
				if (local_pkg != null) {
					pkg.installed_version = local_pkg.version;
				}
				// signature
				if (alpm_pkg.base64_sig != null) {
					pkg.has_signature = dgettext (null, "Yes");
				} else {
					pkg.has_signature = dgettext (null, "No");
				}
			}
			// depends
			unowned Alpm.List<unowned Alpm.Depend> depends_list = alpm_pkg.depends;
			while (depends_list != null) {
				pkg.depends_priv.append (depends_list.data.compute_string ());
				depends_list.next ();
			}
			// optdepends
			depends_list = alpm_pkg.optdepends;
			while (depends_list != null) {
				pkg.optdepends_priv.append (depends_list.data.compute_string ());
				depends_list.next ();
			}
			// provides
			depends_list = alpm_pkg.provides;
			while (depends_list != null) {
				pkg.provides_priv.append (depends_list.data.compute_string ());
				depends_list.next ();
			}
			// replaces
			depends_list = alpm_pkg.replaces;
			while (depends_list != null) {
				pkg.replaces_priv.append (depends_list.data.compute_string ());
				depends_list.next ();
			}
			// conflicts
			depends_list = alpm_pkg.conflicts;
			while (depends_list != null) {
				pkg.conflicts_priv.append (depends_list.data.compute_string ());
				depends_list.next ();
			}
		}

		void initialize_app_data (As.App app, ref AlpmPackage pkg) {
			pkg.app_name = get_app_name (app);
			pkg.launchable = get_app_launchable (app);
			pkg.desc = get_app_summary (app);
			try {
				pkg.long_desc = As.markup_convert_simple (get_app_description (app));
			} catch (Error e) {
				critical ("%s\n", e.message);
			}
			pkg.icon = get_app_icon (app, pkg.repo);
			pkg.screenshots_priv = get_app_screenshots (app);
		}

		List<AlpmPackage> initialise_pkgs (Alpm.List<unowned Alpm.Package>? alpm_pkgs) {
			var pkgs = new List<AlpmPackage> ();
			var data = new HashTable<string, AlpmPackage> (str_hash, str_equal);
guinux's avatar
guinux committed
			string[] foreign_pkgnames = {};
			while (alpm_pkgs != null) {
				unowned Alpm.Package alpm_pkg = alpm_pkgs.data;
guinux's avatar
guinux committed
				var pkg = new AlpmPackage ();
				initialise_pkg_common (alpm_pkg, ref pkg);
guinux's avatar
guinux committed
				if (alpm_pkg.origin == Alpm.Package.From.LOCALDB) {
					unowned Alpm.Package? sync_pkg = get_syncpkg (alpm_pkg.name);
					if (sync_pkg != null) {
guinux's avatar
guinux committed
						pkg.repo = sync_pkg.db.name;
					} else if (config.enable_aur) {
guinux's avatar
guinux committed
						foreign_pkgnames += alpm_pkg.name;
guinux's avatar
guinux committed
					}
				} else if (alpm_pkg.origin == Alpm.Package.From.SYNCDB) {
guinux's avatar
guinux committed
					pkg.repo = alpm_pkg.db.name;
guinux's avatar
guinux committed
				}
guinux's avatar
guinux committed
				if (pkg.repo == "" ) {
guinux's avatar
guinux committed
					if (config.enable_aur) {
guinux's avatar
guinux committed
						data.insert (alpm_pkg.name, pkg);
guinux's avatar
guinux committed
					} else {
guinux's avatar
guinux committed
						pkgs.append (pkg);
guinux's avatar
guinux committed
					}
				} else {
guinux's avatar
guinux committed
					var apps = get_pkgname_matching_apps (alpm_pkg.name);
					if (apps.length () > 0) {
guinux's avatar
guinux committed
						// alpm_pkg provide some apps
guinux's avatar
guinux committed
						unowned SList<As.App> apps_list = apps;
						unowned As.App app = apps_list.data;
guinux's avatar
guinux committed
						initialize_app_data (app, ref pkg);
guinux's avatar
guinux committed
						pkgs.append (pkg);
						apps_list = apps_list.next;
						while (apps_list != null) {
							app = apps_list.data;
							var pkg_dup = pkg.dup ();
guinux's avatar
guinux committed
							initialize_app_data (app, ref pkg_dup);
guinux's avatar
guinux committed
							pkgs.append (pkg_dup);
							apps_list = apps_list.next;
guinux's avatar
guinux committed
						}
					} else {
guinux's avatar
guinux committed
						pkgs.append (pkg);
guinux's avatar
guinux committed
					}
guinux's avatar
guinux committed
				}
				alpm_pkgs.next ();
			}
			// get aur infos
			if (foreign_pkgnames.length > 0) {
guinux's avatar
guinux committed
				foreach (unowned Json.Object json_object in aur.get_multi_infos (foreign_pkgnames)) {
guinux's avatar
guinux committed
					unowned AlpmPackage? pkg = data.lookup (json_object.get_string_member ("Name"));
guinux's avatar
guinux committed
					if (pkg != null) {
						pkg.repo = dgettext (null, "AUR");
guinux's avatar
guinux committed
					}
guinux's avatar
guinux committed
				}
				var iter = HashTableIter<string, AlpmPackage> (data);
				unowned AlpmPackage pkg;
guinux's avatar
guinux committed
				while (iter.next (null, out pkg)) {
					pkgs.append (pkg);
guinux's avatar
guinux committed
				}
			}
			return pkgs;
guinux's avatar
guinux committed
		public List<AlpmPackage> get_installed_pkgs () {
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("get_installed_pkgs", () => {
				pkgs = initialise_pkgs (alpm_handle.localdb.pkgcache);
				pkgs.sort (pkg_compare_name);
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) pkgs;
guinux's avatar
guinux committed
		public List<AlpmPackage> get_installed_apps () {
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("get_installed_apps", () => {
				app_store.get_apps ().foreach ((app) => {
					if (app.get_kind () == As.AppKind.DESKTOP) {
						unowned string pkgname = app.get_pkgname_default ();
						unowned Alpm.Package? local_pkg = alpm_handle.localdb.get_pkg (pkgname);
						if (local_pkg != null) {
							unowned Alpm.Package? sync_pkg = get_syncpkg (pkgname);
							if (sync_pkg != null) {
								var pkg = new AlpmPackage ();
								initialise_pkg_common (local_pkg, ref pkg);
								pkg.repo = sync_pkg.db.name;
								initialize_app_data (app, ref pkg);
								pkgs.append (pkg);
							}
guinux's avatar
guinux committed
						}
guinux's avatar
guinux committed
					}
guinux's avatar
guinux committed
				});
				loop.quit ();
				return 0;
guinux's avatar
guinux committed
			});
guinux's avatar
guinux committed
			loop.run ();
			return (owned) pkgs;
guinux's avatar
guinux committed
		public List<AlpmPackage> get_explicitly_installed_pkgs () {
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("get_explicitly_installed_pkgs", () => {
				Alpm.List<unowned Alpm.Package> alpm_pkgs = null;
				unowned Alpm.List<unowned Alpm.Package> pkgcache = alpm_handle.localdb.pkgcache;
				while (pkgcache != null) {
					unowned Alpm.Package alpm_pkg = pkgcache.data;
					if (alpm_pkg.reason == Alpm.Package.Reason.EXPLICIT) {
						alpm_pkgs.add (alpm_pkg);
					}
					pkgcache.next ();
guinux's avatar
guinux committed
				}
guinux's avatar
guinux committed
				pkgs = initialise_pkgs (alpm_pkgs);
				pkgs.sort (pkg_compare_name);
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) pkgs;
guinux's avatar
guinux committed
		public List<AlpmPackage> get_foreign_pkgs () {
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("get_foreign_pkgs", () => {
				Alpm.List<unowned Alpm.Package> alpm_pkgs = null;
				unowned Alpm.List<unowned Alpm.Package> pkgcache = alpm_handle.localdb.pkgcache;
				while (pkgcache != null) {
					unowned Alpm.Package alpm_pkg = pkgcache.data;
					bool sync_found = false;
					unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
					while (syncdbs != null) {
						unowned Alpm.DB db = syncdbs.data;
						unowned Alpm.Package? sync_pkg = db.get_pkg (alpm_pkg.name);
						if (sync_pkg != null) {
							sync_found = true;
							break;
						}
						syncdbs.next ();
guinux's avatar
guinux committed
					}
guinux's avatar
guinux committed
					if (sync_found == false) {
						alpm_pkgs.add (alpm_pkg);
					}
					pkgcache.next ();
guinux's avatar
guinux committed
				}
guinux's avatar
guinux committed
				pkgs = initialise_pkgs (alpm_pkgs);
				pkgs.sort (pkg_compare_name);
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) pkgs;
guinux's avatar
guinux committed
		public List<AlpmPackage> get_orphans () {
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("get_orphans", () => {
				Alpm.List<unowned Alpm.Package> alpm_pkgs = null;
				unowned Alpm.List<unowned Alpm.Package> pkgcache = alpm_handle.localdb.pkgcache;
				while (pkgcache != null) {
					unowned Alpm.Package alpm_pkg = pkgcache.data;
					if (alpm_pkg.reason == Alpm.Package.Reason.DEPEND) {
						Alpm.List<string> requiredby = alpm_pkg.compute_requiredby ();
						if (requiredby.length == 0) {
							Alpm.List<string> optionalfor = alpm_pkg.compute_optionalfor ();
							if (optionalfor.length == 0) {
								alpm_pkgs.add (alpm_pkg);
							} else {
								optionalfor.free_inner (GLib.free);
							}
guinux's avatar
guinux committed
						} else {
guinux's avatar
guinux committed
							requiredby.free_inner (GLib.free);
guinux's avatar
guinux committed
						}
					}
guinux's avatar
guinux committed
					pkgcache.next ();
guinux's avatar
guinux committed
				}
guinux's avatar
guinux committed
				pkgs = initialise_pkgs (alpm_pkgs);
				pkgs.sort (pkg_compare_name);
				loop.quit ();
				return 0;
			});
			loop.run ();
guinux's avatar
guinux committed
			return (owned) pkgs;
guinux's avatar
guinux committed
		}

		unowned Alpm.Package? get_syncpkg (string name) {
			unowned Alpm.Package? pkg = null;
			unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
			while (syncdbs != null) {
				unowned Alpm.DB db = syncdbs.data;
				pkg = db.get_pkg (name);
				if (pkg != null) {
					break;
				}
				syncdbs.next ();
			}
			return pkg;
guinux's avatar
guinux committed
		public bool is_sync_pkg (string pkgname) {
			return get_syncpkg (pkgname) != null;
		}

guinux's avatar
guinux committed
		public AlpmPackage? get_sync_pkg (string pkgname) {
guinux's avatar
guinux committed
			return initialise_pkg (get_syncpkg (pkgname));
guinux's avatar
guinux committed
		}

		unowned Alpm.Package? find_dbs_satisfier (string depstring) {
			unowned Alpm.Package? pkg = null;
			unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
			while (syncdbs != null) {
				unowned Alpm.DB db = syncdbs.data;
				pkg = Alpm.find_satisfier (db.pkgcache, depstring);
				if (pkg != null) {
					break;
				}
				syncdbs.next ();
			}
			return pkg;
guinux's avatar
guinux committed
		public bool has_sync_satisfier (string depstring) {
			return find_dbs_satisfier (depstring) != null;
		}

		public AlpmPackage? get_sync_satisfier (string depstring) {
guinux's avatar
guinux committed
			return initialise_pkg (find_dbs_satisfier (depstring));
		Alpm.List<unowned Alpm.Package> search_local_db (string search_string) {
			Alpm.List<unowned string> needles = null;
			string[] splitted = search_string.split (" ");
			foreach (unowned string part in splitted) {
				needles.add (part);
			}
			Alpm.List<unowned Alpm.Package> result = alpm_handle.localdb.search (needles);
			// search in appstream
			string[]? search_terms = As.utils_search_tokenize (search_string);
			if (search_terms != null) {
				Alpm.List<unowned Alpm.Package> appstream_result = null;
				app_store.get_apps ().foreach ((app) => {
guinux's avatar
guinux committed
					if (app.get_kind () == As.AppKind.DESKTOP) {
						uint match_score = app.search_matches_all (search_terms);
						if (match_score > 0) {
							unowned string pkgname = app.get_pkgname_default ();
							unowned Alpm.Package? alpm_pkg = alpm_handle.localdb.get_pkg (pkgname);
							if (alpm_pkg != null) {
								if (appstream_result.find (alpm_pkg, (Alpm.List.CompareFunc) alpm_pkg_compare_name) == null) {
									appstream_result.add (alpm_pkg);
								}
							}
						}
					}
				});
				result.join (appstream_result.diff (result, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
			}
			return result;
		}

		Alpm.List<unowned Alpm.Package> search_sync_dbs (string search_string) {
			Alpm.List<unowned string> needles = null;
			string[] splitted = search_string.split (" ");
			foreach (unowned string part in splitted) {
				needles.add (part);
			}
			Alpm.List<unowned Alpm.Package> syncpkgs = null;
			unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
			while (syncdbs != null) {
				unowned Alpm.DB db = syncdbs.data;
				if (syncpkgs.length == 0) {
					syncpkgs = db.search (needles);
				} else {
					syncpkgs.join (db.search (needles).diff (syncpkgs, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
				}
				syncdbs.next ();
			}
guinux's avatar
guinux committed
			// remove foreign pkgs
			Alpm.List<unowned Alpm.Package> localpkgs = alpm_handle.localdb.search (needles);
			Alpm.List<unowned Alpm.Package> result = syncpkgs.diff (localpkgs.diff (syncpkgs, (Alpm.List.CompareFunc) alpm_pkg_compare_name), (Alpm.List.CompareFunc) alpm_pkg_compare_name);
			// search in appstream
			string[]? search_terms = As.utils_search_tokenize (search_string);
			if (search_terms != null) {
				Alpm.List<unowned Alpm.Package> appstream_result = null;
				app_store.get_apps ().foreach ((app) => {
guinux's avatar
guinux committed
					if (app.get_kind () == As.AppKind.DESKTOP) {
						uint match_score = app.search_matches_all (search_terms);
						if (match_score > 0) {
							unowned string pkgname = app.get_pkgname_default ();
							unowned Alpm.Package? alpm_pkg = alpm_handle.localdb.get_pkg (pkgname);
							if (alpm_pkg == null) {
								alpm_pkg = get_syncpkg (pkgname);
								if (alpm_pkg != null) {
									if (appstream_result.find (alpm_pkg, (Alpm.List.CompareFunc) alpm_pkg_compare_name) == null) {
										appstream_result.add (alpm_pkg);
									}
								}
							}
						}
					}
				});
				result.join (appstream_result.diff (result, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
			}
			return result;
		}

guinux's avatar
guinux committed
		public List<AlpmPackage> search_installed_pkgs (string search_string) {
			string search_string_down = search_string.down ();
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("search_installed_pkgs", () => {
				pkgs = initialise_pkgs (search_local_db (search_string_down));
				// use custom sort function
				global_search_string = (owned) search_string_down;
				pkgs.sort (pkg_sort_search_by_relevance);
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) pkgs;
		}

guinux's avatar
guinux committed
		public List<AlpmPackage> search_repos_pkgs (string search_string) {
			string search_string_down = search_string.down ();
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("search_repos_pkgs", () => {
				pkgs = initialise_pkgs (search_sync_dbs (search_string_down));
				// use custom sort function
				global_search_string = (owned) search_string_down;
				pkgs.sort (pkg_sort_search_by_relevance);
				loop.quit ();
				return 0;
			});
			loop.run ();
			return (owned) pkgs;
		}

guinux's avatar
guinux committed
		Alpm.List<unowned Alpm.Package> search_all_dbs (string search_string) {
			Alpm.List<unowned string> needles = null;
			string[] splitted = search_string.split (" ");
			foreach (unowned string part in splitted) {
				needles.add (part);
			}
			Alpm.List<unowned Alpm.Package> result = alpm_handle.localdb.search (needles);
			Alpm.List<unowned Alpm.Package> syncpkgs = null;
			unowned Alpm.List<unowned Alpm.DB> syncdbs = alpm_handle.syncdbs;
			while (syncdbs != null) {
				unowned Alpm.DB db = syncdbs.data;
				if (syncpkgs.length == 0) {
					syncpkgs = db.search (needles);
				} else {
					syncpkgs.join (db.search (needles).diff (syncpkgs, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
				}
				syncdbs.next ();
			}
			result.join (syncpkgs.diff (result, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
			// search in appstream
guinux's avatar
guinux committed
			string[]? search_terms = As.utils_search_tokenize (search_string);
			if (search_terms != null) {
guinux's avatar
guinux committed
				Alpm.List<unowned Alpm.Package> appstream_result = null;
				app_store.get_apps ().foreach ((app) => {
guinux's avatar
guinux committed
					if (app.get_kind () == As.AppKind.DESKTOP) {
						uint match_score = app.search_matches_all (search_terms);
						if (match_score > 0) {
							unowned string pkgname = app.get_pkgname_default ();
							unowned Alpm.Package? alpm_pkg = alpm_handle.localdb.get_pkg (pkgname);
							if (alpm_pkg == null) {
								alpm_pkg = get_syncpkg (pkgname);
							}
							if (alpm_pkg != null) {
								if (appstream_result.find (alpm_pkg, (Alpm.List.CompareFunc) alpm_pkg_compare_name) == null) {
									appstream_result.add (alpm_pkg);
								}
guinux's avatar
guinux committed
							}
						}
					}
				});
				result.join (appstream_result.diff (result, (Alpm.List.CompareFunc) alpm_pkg_compare_name));
			}
			return result;
guinux's avatar
guinux committed
		public List<AlpmPackage> search_pkgs (string search_string) {
			string search_string_down = search_string.down ();
			var pkgs = new List<AlpmPackage> ();
			new Thread<int> ("search_pkgs", () => {
				pkgs = initialise_pkgs (search_all_dbs (search_string_down));
				// use custom sort function
				global_search_string = (owned) search_string_down;
				pkgs.sort (pkg_sort_search_by_relevance);
				loop.quit ();
				return 0;
			});
			loop.run ();
guinux's avatar
guinux committed
			return (owned) pkgs;
guinux's avatar
guinux committed
		public List<AURPackage> search_aur_pkgs (string search_string) {
			string search_string_down = search_string.down ();
			var pkgs = new List<AURPackage> ();
			if (config.enable_aur) {
				new Thread<int> ("search_aur_pkgs", () => {
					foreach (unowned Json.Object json_object in aur.search_aur (search_string_down)) {
						unowned Alpm.Package? local_pkg = alpm_handle.localdb.get_pkg (json_object.get_string_member ("Name"));
						pkgs.append (initialise_aur_pkg (json_object, local_pkg));
					}
					loop.quit ();
					return 0;
				});
				loop.run ();
guinux's avatar
guinux committed
			return (owned) pkgs;
		}

		public HashTable<string, Variant> search_files (string[] files) {
guinux's avatar
guinux committed
			var result = new HashTable<string, Variant> (str_hash, str_equal);
			foreach (unowned string file in files) {
				// search in localdb
				unowned Alpm.List<unowned Alpm.Package> pkgcache = alpm_handle.localdb.pkgcache;
				while (pkgcache != null) {
					unowned Alpm.Package alpm_pkg = pkgcache.data;
					string[] found_files = {};
					unowned Alpm.FileList filelist = alpm_pkg.files;
					Alpm.File* file_ptr = filelist.files;
					for (size_t i = 0; i < filelist.count; i++, file_ptr++) {
						// exclude directory name
						if (!file_ptr->name.has_suffix ("/")) {
							// adding / to compare
guinux's avatar
guinux committed
							var real_file_name = new StringBuilder (alpm_handle.root);
guinux's avatar
guinux committed
							real_file_name.append (file_ptr->name);
							if (file in real_file_name.str) {
guinux's avatar
guinux committed
								found_files += (owned) real_file_name.str;
guinux's avatar
guinux committed
							}
						}
					}
					if (found_files.length > 0) {
						result.insert (alpm_pkg.name, new Variant.strv (found_files));
					}
					pkgcache.next ();
				}
				// search in syncdbs
				unowned Alpm.List<unowned Alpm.DB> syncdbs = files_handle.syncdbs;
				while (syncdbs != null) {
					unowned Alpm.DB db = syncdbs.data;
					pkgcache = db.pkgcache;
					while (pkgcache != null) {
						unowned Alpm.Package alpm_pkg = pkgcache.data;
						string[] found_files = {};
						unowned Alpm.FileList filelist = alpm_pkg.files;