Skip to content

Instantly share code, notes, and snippets.

@silvasur
Last active October 11, 2015 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save silvasur/3867249 to your computer and use it in GitHub Desktop.
Save silvasur/3867249 to your computer and use it in GitHub Desktop.
Yet another AUR package downloader...
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# aurfetch.py - My small helper script for fetching AUR packages and automatically update them
# It doesn't do the fancier stuff like automagically fetch dependencies.
# Have fun :-)
#
# License: [Do What The Fuck You Want To Public License](http://en.wikipedia.org/wiki/WTFPL)
###### START EDITING HERE
AUR_BASE = "https://aur.archlinux.org"
DBPATH = "~/.aurfetch.db"
BUILDPATH = "~/build"
MAKEPKG_OPTIONS = ["-s", "-c"]
###### END EDITING HERE
# (At least if you only want to configure :-) )
import urllib2
import json
import os, sys
import subprocess
from functools import partial
from uuid import uuid4
from shutil import rmtree
try:
import cPickle as pickle
except ImportError:
import pickle
class AURQueryError(Exception): pass
class NoAURDataAvail(Exception): pass
def query(cmd, arg):
urlh = urllib2.urlopen("{base}/rpc.php?type={cmd}&arg={arg}".format(
base=AUR_BASE,
cmd=urllib2.quote(cmd),
arg=urllib2.quote(str(arg))
))
raw = urlh.read()
urlh.close()
data = json.loads(raw)
if data["type"] == "error":
raise AURQueryError(data["results"])
if (data["type"] == "info") and (data["resultcount"] == 0):
raise NoAURDataAvail("No results.")
return data["results"]
search = partial(query, "search")
info = partial(query, "info")
multiinfo = partial(query, "multiinfo")
msearch = partial(query, "msearch")
def load_db():
try:
with open(os.path.expanduser(DBPATH), "rb") as fh:
return pickle.load(fh)
except IOError:
return {}
def save_db(db):
with open(os.path.expanduser(DBPATH), "wb") as fh:
pickle.dump(db, fh)
db = load_db()
def indent(s, by):
return u"\n".join(by + l for l in s.splitlines())
def format_info(inf):
global db
flags = ""
if inf["OutOfDate"] == "1":
flags += "!"
if inf["ID"] in db:
if int(inf["LastModified"]) > db[inf["ID"]]["timestamp"]:
flags += "*"
else:
flags += "~"
if len(flags) > 0:
flags += " "
return u"{flags}{name} {version} (ID:{id}, Votes:{votes})\n{desc}".format(
flags=flags,
name=inf["Name"],
version=inf["Version"],
id=inf["ID"],
votes=inf["NumVotes"],
desc=indent(inf["Description"], u"| ")
)
def format_search(search_result):
global db
search_result.sort(key=lambda x: int(x["NumVotes"]), reverse=True)
return "\n\n".join(map(format_info, search_result))
def build(path):
global MAKEPKG_OPTIONS
olddir = os.getcwd()
os.chdir(path)
makepkg = subprocess.Popen(["makepkg"] + MAKEPKG_OPTIONS)
rv = makepkg.wait()
if rv != 0:
print "\033[41m\033[37m\033[1m" + "Automatic build failed. I will open a shell in the packagepath for you. Exit it to continue." + "\033[0m"
shell = subprocess.Popen([os.environ.get("SHELL", "/bin/sh")])
shell.wait()
print "\033[43m\033[37m\033[1m" + "Leaving build shell..." + "\033[0m"
os.chdir(olddir)
def fetch(inf):
global db
tmpname = os.path.join("/tmp", "aurfetch_py_pkg_" + str(uuid4()))
url = AUR_BASE + inf["URLPath"]
curl = subprocess.Popen(["curl", "-o", tmpname, url])
if curl.wait() != 0:
print >>sys.stderr, "Could not fetch"
return 1
tar = subprocess.Popen(["tar", "-C", os.path.expanduser(BUILDPATH), "-x", "-v", "-z", "--overwrite", "-f", tmpname])
if tar.wait() != 0:
print >>sys.stderr, "Could not extract"
os.unlink(tmpname)
return 1
path = os.path.join(os.path.expanduser(BUILDPATH), inf["Name"])
db[int(inf["ID"])] = {
"name": inf["Name"],
"timestamp": int(inf["LastModified"]),
"path": path
}
save_db(db)
print "Package is now at " + path
if askyesno("Build '{}' now? ".format(inf["Name"])):
build(path)
return 0
def unfetch(ID):
global db
data = db[ID]
rmtree(data["path"])
del db[ID]
save_db(db)
return 0
def askyesno(prompt):
while True:
try:
usrin = raw_input(prompt)[0]
except IndexError:
continue
if usrin in "yYjJ":
return True
elif usrin in "nN":
return False
if __name__ == '__main__':
if len(sys.argv) < 2:
print >>sys.stderr, "Commands: search fetch unfetch update list"
sys.exit(1)
if sys.argv[1] == "search":
try:
print format_search(search(sys.argv[2])).encode("utf-8")
except IndexError:
print >>sys.stderr, "search needs a keyword"
sys.exit(1)
elif sys.argv[1] == "list":
pkglist = list(db.items())
pkglist.sort(key=lambda x: x[1]["name"])
for ID, data in pkglist:
print data["name"]
elif sys.argv[1] == "fetch":
if len(sys.argv) < 3:
print >>sys.stderr, "fetch needs a keyword"
sys.exit(1)
inf = info(sys.argv[2])
print format_info(inf)
print ""
if askyesno("Fetch? "):
sys.exit(fetch(inf))
elif sys.argv[1] == "unfetch":
if len(sys.argv) < 3:
print >>sys.stderr, "unfetch needs a keyword"
sys.exit(1)
try:
ID, data = [(ID, data) for ID, data in db.items() if data["name"] == sys.argv[2]][0]
except KeyError:
print >>sys.stderr, "{} was not fetched (at least not with aurfetch.py)".format(sys.argv[2])
if askyesno("Really unfetch? "):
sys.exit(unfetch(ID))
elif sys.argv[1] == "update":
print "This might take a while..."
toupdate = []
for ID, data in db.items():
try:
inf = info(ID)
except NoAURDataAvail:
print >>sys.stderr, "\033[41m\033[37m\033[1m"+"Package {p} not found. Was probably removed.".format(p=data["name"])+"\033[0m"
if askyesno("Do you want to unfetch it? "):
unfetch(ID)
continue
if int(inf["LastModified"]) > data["timestamp"]:
print "{} will be updated".format(inf["Name"])
toupdate.append(inf)
else:
print "{} is up to date".format(inf["Name"])
failed = []
yay = []
for tu in toupdate:
if fetch(tu) != 0:
failed.append(tu["Name"])
else:
yay.append(tu["Name"])
if len(failed) > 0:
print ""
print "These packages could not be updated:"
for f in failed:
print "*", f
sys.exit(1)
if len(yay) > 0:
print ""
print "These packages were updated:"
for y in yay:
print "*", y
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment