summaryrefslogtreecommitdiff
path: root/webapp.py
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2014-07-22 08:56:42 +0200
committerHelmut Grohne <helmut@subdivi.de>2014-07-22 08:56:42 +0200
commit04597f25729740406775a3dff528c9774c84efd5 (patch)
treefe905fc94afbdcfad60d5aaf88886a1f10f92a8c /webapp.py
parentba9ae116e0bbb25e2df327ba48c82472ccfa2690 (diff)
parentd48c3c208ee6ba54225b3eb68ce5c9f3c894bfa4 (diff)
downloaddebian-dedup-04597f25729740406775a3dff528c9774c84efd5.tar.gz
Merge branch master into multiarch
Resolve accumulated conflicts. In particular webapp.py gained a few non-trivial ones, such as changes in InternalRedirect or usage of contextlib.closing. Conflicts: schema.sql webapp.py
Diffstat (limited to 'webapp.py')
-rwxr-xr-xwebapp.py125
1 files changed, 70 insertions, 55 deletions
diff --git a/webapp.py b/webapp.py
index 2819a83..e173d60 100755
--- a/webapp.py
+++ b/webapp.py
@@ -1,5 +1,6 @@
#!/usr/bin/python
+import contextlib
import datetime
import optparse
import sqlite3
@@ -7,7 +8,8 @@ from wsgiref.simple_server import make_server
import jinja2
from werkzeug.exceptions import HTTPException, NotFound
-from werkzeug.routing import Map, Rule, RequestRedirect
+from werkzeug.routing import Map, Rule
+from werkzeug.utils import redirect
from werkzeug.wrappers import Request, Response
from werkzeug.wsgi import SharedDataMiddleware
@@ -61,9 +63,10 @@ def html_response(unicode_iterator, max_age=24 * 60 * 60):
return resp
class InternalRedirect(Exception):
- def __init__(self, target):
+ def __init__(self, target, code=301):
Exception.__init__(self)
self.target = target
+ self.code = code
class Application(object):
def __init__(self, db):
@@ -93,33 +96,33 @@ class Application(object):
return self.show_hash(args["function"], args["hashvalue"])
elif endpoint == "index":
if not request.environ["PATH_INFO"]:
- raise RequestRedirect(request.environ["SCRIPT_NAME"] + "/")
+ raise InternalRedirect("/")
return html_response(index_template.render(dict(urlroot="")))
elif endpoint == "source":
return self.show_source(args["package"])
raise NotFound()
except InternalRedirect as r:
- return RequestRedirect(request.environ["SCRIPT_NAME"] + r.target)
+ return redirect(request.environ["SCRIPT_NAME"] + r.target, r.code)
except HTTPException as e:
return e
def guess_package(self, package):
- cur = self.db.cursor()
- cur.execute("SELECT architecture, id FROM package WHERE name = ?;",
- (package,))
- ret = dict(cur.fetchall())
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT architecture, id FROM package WHERE name = ?;",
+ (package,))
+ ret = dict(cur.fetchall())
if not ret:
raise NotFound()
return ret
def get_details(self, package, architecture):
- cur = self.db.cursor()
- cur.execute("SELECT package.id, package.version, package.architecture, count(content.filename), sum(content.size) FROM package JOIN content ON package.id = content.pid WHERE name = ? AND architecture = ? GROUP BY package.id;",
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT package.id, package.version, count(content.filename), sum(content.size) FROM package JOIN content ON package.id = content.pid WHERE name = ? AND architecture = ? GROUP BY package.id;",
(package, architecture))
- row = cur.fetchone()
+ row = cur.fetchone()
if not row:
raise NotFound()
- pid, version, architecture, num_files, total_size = row
+ pid, version, num_files, total_size = row
if total_size is None:
total_size = 0
return dict(pid=pid, package=package, version=version,
@@ -127,23 +130,25 @@ class Application(object):
total_size=total_size)
def get_dependencies(self, pid):
- cur = self.db.cursor()
- cur.execute("SELECT required FROM dependency WHERE pid = ?;",
- (pid,))
- return set(row[0] for row in fetchiter(cur))
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT required FROM dependency WHERE pid = ?;",
+ (pid,))
+ return set(row[0] for row in fetchiter(cur))
def cached_sharedstats(self, pid):
- cur = self.db.cursor()
sharedstats = {}
- cur.execute("SELECT pid2, package.name, package.architecture, f1.name, f2.name, files, size FROM sharing JOIN package ON sharing.pid2 = package.id JOIN function AS f1 ON sharing.fid1 = f1.id JOIN function AS f2 ON sharing.fid2 = f2.id WHERE pid1 = ? AND f1.eqclass = f2.eqclass;",
- (pid,))
- for pid2, package2, architecture2, func1, func2, files, size in fetchiter(cur):
- curstats = sharedstats.setdefault(
- function_combination(func1, func2), list())
- if pid2 == pid:
- package2 = None
- architecture2 = None
- curstats.append(dict(package=package2, architecture=architecture2, duplicate=files, savable=size))
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT pid2, package.name, package.architecture, f1.name, f2.name, files, size FROM sharing JOIN package ON sharing.pid2 = package.id JOIN function AS f1 ON sharing.fid1 = f1.id JOIN function AS f2 ON sharing.fid2 = f2.id WHERE pid1 = ? AND f1.eqclass = f2.eqclass;",
+ (pid,))
+ for pid2, package2, architecture2, func1, func2, files, size in fetchiter(cur):
+ curstats = sharedstats.setdefault(
+ function_combination(func1, func2), list())
+ if pid2 == pid:
+ package2 = None
+ architecture2 = None
+ curstats.append(dict(package=package2,
+ architecture=architecture2,
+ duplicate=files, savable=size))
return sharedstats
def show_package(self, package):
@@ -151,7 +156,8 @@ class Application(object):
package, architecture = package.split(':', 1)
else:
architecture = min(self.guess_package(package))
- raise InternalRedirect("/binary/%s:%s" % (package, architecture))
+ raise InternalRedirect("/binary/%s:%s" % (package, architecture),
+ code=302)
params = self.get_details(package, architecture)
params["dependencies"] = self.get_dependencies(params["pid"])
params["shared"] = self.cached_sharedstats(params["pid"])
@@ -164,7 +170,7 @@ class Application(object):
return html_response(package_template.render(params))
def compute_comparison(self, pid1, pid2):
- """Compute a sequence of comparison objects ordery by the size of the
+ """Compute a sequence of comparison objects ordered by the size of the
object in the first package. Each element of the sequence is a dict
defining the following keys:
* filenames: A set of filenames in package 1 (pid1) all referring to
@@ -179,6 +185,7 @@ class Application(object):
cursize = -1
files = dict()
minmatch = 2 if pid1 == pid2 else 1
+ cur2 = self.db.cursor()
for cid, filename, size, hashvalue in fetchiter(cur):
if cursize != size:
for entry in files.values():
@@ -194,13 +201,12 @@ class Application(object):
entry = dict(filenames=set((filename,)), size=size, matches={})
files[hashvalue] = entry
- cur2 = self.db.cursor()
- cur2.execute("SELECT fa.name, ha.hash, fb.name, filename FROM hash AS ha JOIN hash AS hb ON ha.hash = hb.hash JOIN content ON hb.cid = content.id JOIN function AS fa ON ha.fid = fa.id JOIN function AS fb ON hb.fid = fb.id WHERE ha.cid = ? AND pid = ?;",
+ cur2.execute("SELECT fa.name, ha.hash, fb.name, filename FROM hash AS ha JOIN hash AS hb ON ha.hash = hb.hash JOIN content ON hb.cid = content.id JOIN function AS fa ON ha.fid = fa.id JOIN function AS fb ON hb.fid = fb.id WHERE ha.cid = ? AND pid = ? AND fa.eqclass = fb.eqclass;",
(cid, pid2))
for func1, hashvalue, func2, filename in fetchiter(cur2):
entry["matches"].setdefault(filename, {})[func1, func2] = \
hashvalue
- cur2.close()
+ cur2.close()
cur.close()
for entry in files.values():
@@ -222,7 +228,7 @@ class Application(object):
if guessed:
raise InternalRedirect("/compare/%s:%s/%s:%s" %
(package1, architecture1, package2,
- architecture2))
+ architecture2), code=302)
details1 = details2 = self.get_details(package1, architecture1)
if package1 != package2 or architecture1 != architecture2:
details2 = self.get_details(package2, architecture2)
@@ -236,34 +242,43 @@ class Application(object):
return html_response(detail_template.stream(params))
def show_hash(self, function, hashvalue):
- cur = self.db.cursor()
- cur.execute("SELECT package.name, package.architecture, content.filename, content.size, f2.name FROM hash JOIN content ON hash.cid = content.id JOIN package ON content.pid = package.id JOIN function AS f2 ON hash.fid = f2.id JOIN function AS f1 ON f2.eqclass = f1.eqclass WHERE f1.name = ? AND hash = ?;",
- (function, hashvalue,))
- entries = [dict(package=package, architecture=architecture,
- filename=filename, size=size, function=otherfunc)
- for package, architecture, filename, size, otherfunc in fetchiter(cur)]
- if not entries:
- raise NotFound()
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT package.name, package.architecture, content.filename, content.size, f2.name FROM hash JOIN content ON hash.cid = content.id JOIN package ON content.pid = package.id JOIN function AS f2 ON hash.fid = f2.id JOIN function AS f1 ON f2.eqclass = f1.eqclass WHERE f1.name = ? AND hash = ?;",
+ (function, hashvalue,))
+ entries = [dict(package=package, architecture=architecture,
+ filename=filename, size=size, function=otherfunc)
+ for package, architecture, filename, size, otherfunc
+ in fetchiter(cur)]
+ if not entries:
+ # Assumption: '~' serves as an infinite character larger than
+ # any other character in the hash column.
+ cur.execute("SELECT DISTINCT hash.hash FROM hash JOIN function ON hash.fid = function.id WHERE function.name = ? AND hash.hash >= ? AND hash.hash <= ? LIMIT 2;",
+ (function, hashvalue, hashvalue + '~'))
+ values = cur.fetchall()
+ if len(values) == 1:
+ raise InternalRedirect("/hash/%s/%s" %
+ (function, values[0][0]), 302)
+ raise NotFound()
params = dict(function=function, hashvalue=hashvalue, entries=entries,
urlroot="../..")
return html_response(hash_template.render(params))
def show_source(self, package):
- cur = self.db.cursor()
- cur.execute("SELECT name FROM package WHERE source = ?;",
- (package,))
- binpkgs = dict.fromkeys(pkg for pkg, in fetchiter(cur))
- if not binpkgs:
- raise NotFound
- cur.execute("SELECT p1.name, p2.name, f1.name, f2.name, sharing.files, sharing.size FROM sharing JOIN package AS p1 ON sharing.pid1 = p1.id JOIN package AS p2 ON sharing.pid2 = p2.id JOIN function AS f1 ON sharing.fid1 = f1.id JOIN function AS f2 ON sharing.fid2 = f2.id WHERE p1.source = ?;",
- (package,))
- for binary, otherbin, func1, func2, files, size in fetchiter(cur):
- entry = dict(package=otherbin,
- funccomb=function_combination(func1, func2),
- duplicate=files, savable=size)
- oldentry = binpkgs.get(binary)
- if not (oldentry and oldentry["savable"] >= size):
- binpkgs[binary] = entry
+ with contextlib.closing(self.db.cursor()) as cur:
+ cur.execute("SELECT name FROM package WHERE source = ?;",
+ (package,))
+ binpkgs = dict.fromkeys(pkg for pkg, in fetchiter(cur))
+ if not binpkgs:
+ raise NotFound
+ cur.execute("SELECT p1.name, p2.name, f1.name, f2.name, sharing.files, sharing.size FROM sharing JOIN package AS p1 ON sharing.pid1 = p1.id JOIN package AS p2 ON sharing.pid2 = p2.id JOIN function AS f1 ON sharing.fid1 = f1.id JOIN function AS f2 ON sharing.fid2 = f2.id WHERE p1.source = ?;",
+ (package,))
+ for binary, otherbin, func1, func2, files, size in fetchiter(cur):
+ entry = dict(package=otherbin,
+ funccomb=function_combination(func1, func2),
+ duplicate=files, savable=size)
+ oldentry = binpkgs.get(binary)
+ if not (oldentry and oldentry["savable"] >= size):
+ binpkgs[binary] = entry
params = dict(source=package, packages=binpkgs, urlroot="..")
return html_response(source_template.render(params))