#!/usr/bin/env python # Original code by synorgy (Bryan Bennett, bbenne10 at g mail dot com), # Code contributions by Edwin Marshall (aspidites / EnvoyRising) from os import makedirs, remove, chdir, environ, getuid, getcwd, setuid import requests from sys import exit from subprocess import getoutput, getstatusoutput, call from shutil import rmtree from argparse import ArgumentParser from json import loads BASE_URL = 'http://aur.archlinux.org/rpc.php' CATEGORIES = ['padding', 'none', 'daemons', 'devel', 'editors', 'emulators', 'games', 'gnome', 'i18n', 'kde', 'lib', 'modules', 'multimedia', 'network', 'office', 'science', 'system', 'x11', 'xfce', 'kernels'] class Color: black = "\x1b[30m" red = "\x1b[31m" green = "\x1b[32m" yellow = "\x1b[33m" blue = "\x1b[34m" magenta = "\x1b[35m" cyan = "\x1b[36m" white = "\x1b[37m" bright_black = black + ";1m" bright_red = red + ";1m" bright_green = green + ";1m" bright_yellow = yellow + ";1m" bright_blue = blue + ";1m" bright_magenta = magenta + ";1m" bright_cyan = cyan + ";1m" bright_white = white + ";1m" reset = "\x1b[0m" class Options: def __init__(self, download_dir=None, upgrade=None, search_string=None, grab_string=None, as_root=None, auto_install=None, auto_edit=None, auto_clean=None): self.download_dir = download_dir self.upgrade = upgrade self.search_string = search_string self.grab_string = grab_string self.auto_edit = auto_edit self.auto_install = auto_install self.as_root = as_root self.auto_clean = auto_clean class Package: def __init__(self, results, options): self.name = results['Name'] self.url = "http://aur.archlinux.org/{0}".format(results['URLPath']) self.version = results['Version'] try: self.category = CATEGORIES[int(results['CategoryID'])] except: self.category = "UNKNOWN" self.options = options def __repr__(self): return repr(self.category, ' ', self.name, ' ', self.version, ' ', self.version) def is_out_of_date(self, target_ver): verCmp = int(getoutput("vercmp {0} {1}".format(self.version, target_ver))) return (verCmp > 0 or self.is_devel) @property def is_devel(self): suffixes = ['-git', '-hg', '-bzr', '-svn', '-cvs'] for suffix in suffixes: if self.name.endswith(suffix): return True return False def download(self): print("Downloading {0} to {1}".format(self.name, self.options.download_dir)) try: chdir(self.options.download_dir) with open("{}/{}.tar.gz".format(self.options.download_dir, self.name), "wb") as f: resp = requests.get(self.url) f.write(resp.content) except OSError: overwrite = "y" overwrite = input("::{0}ERROR{1}{2}{3}/{3}.tar.gz exists\n Overwrite? (Y/n)".format( Color.red, Color.reset, self.options.download_dir, self.name)) if overwrite.lower() == "y": remove("{0}{1}/{1}.tar.gz".format( self.options.download_dir, self.name)) self.download() else: pass del overwrite except IOError: print("::{}ERROR{}: Your network seems to be down." "Please check your internet connection.".format(Color.red, Color.reset)) clean_up(self.options.download_dir, self.options.auto_clean) exit(2) def install(self): self.download() chdir(self.options.download_dir) untar = getstatusoutput('tar xvf {0}.tar.gz'.format(self.name)) if untar[0] == 0: remove('{0}.tar.gz'.format(self.name)) else: print("Something has gone wrong with unpacking the tarball, dying now.") exit(2) chdir(self.name) print("=" * 80) if self.options.auto_edit: if 'EDITOR' in list(environ.keys()): call([environ['EDITOR'], "PKGBUILD"]) else: print("Please use your editor of choice to check and edit the PKGBUILD") print("If you'd like auto-edit, set $EDITOR.") pause = input(':: Press Enter to continue') del pause else: print("Please use your editor of choice to check and edit the PKGBUILD") print("If you'd like auto-edit, set $EDITOR.") pause = input(':: Press Enter to continue') del pause if not self.options.auto_install: call(['makepkg']) print(":: Ran makepkg on {0} and placed the output in {1}".format(self.name, dir)) else: #Optimize here so that we can maybe handle recursive deps #and deps on packages in the AUR? call(['makepkg', '-si']) def grab(self): #override the default download location here, but only changes #it if the default value of OPTIONS.downloadDir is set if self.options.download_dir == "/tmp/aurora/": self.options.download_dir = str(getcwd()) + "/" self.download() def clean_up(download_dir, auto_clean): if auto_clean: rmtree(dir) def upgrade_single(args): (entry, url, queue, options) = args raw = requests.get(url, params={'type': 'search', 'arg': entry[0]}).text try: results = loads(raw)['results'] except ValueError: print(":: {}ERROR{}: Couldn't grab the package list, exiting.".format( COLORS.red, COLORS.reset)) clean_up(options.download_dir, options.auto_clean) exit(2) for result in results: if result['Name'] == entry[0]: pkg = Package(results[0], options) if pkg.is_out_of_date(entry[1]): queue.put(pkg) break def upgrade(options): """Get all locally installed packages and attempt to update them from AUR""" print(":: Searching AUR for out of date packages. This may take a few minutes") import multiprocessing manager = multiprocessing.Manager() queue = manager.Queue() local = map(lambda x: x.split(), getoutput('pacman -Qm').split('\n')) local = [(x, BASE_URL, queue, options) for x in local] p = multiprocessing.Pool(5) p.map(upgrade_single, local) out_of_date = [] while not queue.empty(): out_of_date.append(queue.get()) if out_of_date: print(":: Packages to be upgraded:") for entry in out_of_date: print(entry.name) print("=" * 80) go_on = "y" go_on = input("Continue? (Y/n)") if go_on.lower() == "y" or go_on == "": for entry in out_of_date: entry.install() else: print(":: No AUR updates available") exit() def search(term, options): """Search the interface and present the results""" raw = requests.get(BASE_URL, params={'type': 'search', 'arg': term}).text try: results = loads(raw, 'utf-8')['results'] except ValueError: print(":: {}ERROR{}: Couldn't grab the package list, exiting.".format( Color.red, Color.reset)) clean_up(options.download_dir, options.auto_clean) exit(2) results_list = [] if isinstance(results, str): print(":: {}ERROR{}: {}".format(Color.red, Color.reset, results)) exit() else: #add an instance of package for each entry in results; add #each instance to results_list and sort based on category. results_list = [Package(i, options) for i in results] results_list.sort(key=lambda x: x.category) return results_list def present(results_list): '''get all installed packages and their installed versions''' installedVersion = dict(item.split() for item in getoutput('pacman -Q').split('\n')) if len(results_list) > 1: longest_name = 0 longest_category = 0 longest_index = 0 for entry in results_list: if len(entry.name) > longest_name: longest_name = len(entry.name) if len(entry.category) > longest_category: longest_category = len(entry.category) # this is dumb, but I cba to find a better solution if len(str(results_list.index(entry))) > longest_index: longest_index = len(str(results_list.index(entry))) longest_category += 1 longest_name += 1 for entry in results_list: try: installed = installedVersion[entry.name] verCmp = int(getoutput("vercmp {0} {1}".format(entry.version, installed))) except KeyError: verCmp = None pass to_print = "{0:<{l_index}}. {1}{2:<{l_cat}}{3}{4}{5:<{l_name}}{6}" if verCmp is None: to_print += "[{7}]" elif verCmp <= 0: to_print += "[installed,{7}]" elif verCmp > 0: to_print += "[outdated,{7}]" to_print = to_print.format( str(int(results_list.index(entry)) + 1), Color.green, entry.category, Color.reset, Color.red, entry.name, Color.reset, entry.version, l_index=longest_index, l_cat=longest_category, l_name=longest_name) print(to_print) install_index = input("==> ") safe_list = [] for entry in install_index.split(): try: #pkg = results_list[int(entry) - 1] safe_list.append(results_list[int(entry) - 1]) except ValueError: print(":: {}ERROR{}: Entered value is not a number, exiting.".format(Color.red, Color.reset)) exit(2) return safe_list def main(): # TODO: rewrite this with click parser = ArgumentParser() parser.add_argument('-n', '--noauto', action='store_false', dest='auto_install', default=True, help="Disable auto-install of built packages") parser.add_argument('-a', '--as-root', action='store_true', dest='as_root', default=False, help='Allow building as root. Aurora will drop root' 'priveledges and run as uid 99') parser.add_argument('-c', '--autoclean', action='store_true', dest='auto_clean', default=False, help="Enables cleaning of the download directory when" " building is completed. Note that currently" " this script simply deletes the download" " directory") parser.add_argument('-d', '--directory', action='store', dest='download_dir', default='/tmp/aurora/', help="Change the download directory. Default is" " /tmp/aurora for -S and current working" " directory for -G") parser.add_argument('-S', '--search', action='store', dest='search_string', help='Search for, select, and install packages') parser.add_argument('-U', '--upgrade', action='store_true', dest='upgrade', default=False, help='Upgrade all AUR packages.') parser.add_argument('-G', '--grab', action='store', dest='grab_string', help='Grab the PKGBUILD and suporting files, then exit') parser.add_argument('-e', '--edit', action='store_true', dest='auto_edit', default=False, help="Enables auto-editing of the the PKGBUILD based " "on the user's $EDITOR variable") options = parser.parse_args() options = Options(download_dir=options.download_dir, upgrade=options.upgrade, as_root=options.as_root, auto_edit=options.auto_edit, auto_clean=options.auto_clean, search_string=options.search_string, grab_string=options.grab_string, auto_install=options.auto_install) try: #handle root without asroot. if getuid() == 0: if not options.as_root: print("Aurora should not be run as root. " "(Makepkg doesn't like it much either).\n" "Please run as an unprivileged user or specify -a.") exit() else: setuid(99) #Nothing usable specified if not options.search_string and not options.upgrade and \ not options.grab_string: print("No action specified, or no search string present") exit() #Upgrade only elif not options.search_string and not options.grab_string \ and options.upgrade: upgrade(options) #Grab only elif options.grab_string and not options.upgrade \ and not options.search_string: output = search(options.grab_string, options) if len(output) == 1: output[0].grab() else: x = present(output) if x: for entry in x: entry.grab() #search only elif options.search_string and not options.upgrade \ and not options.grab_string: try: makedirs(options.download_dir) except: pass output = search(options.search_string, options) if len(output) == 1: output[0].install() elif len(output) > 1: x = present(output) for entry in x: entry.install() #Multiple exclusive options present, die gracefully else: print("Multiple OPTIONS provided. This is currently not supported.") exit() except KeyboardInterrupt: clean_up(options.download_dir, options.auto_clean) print("\n:: Keyboard Interrupt received, exiting.") exit(2) if __name__ == "__main__": main()