Last active
August 29, 2015 13:56
-
-
Save rschroll/8952015 to your computer and use it in GitHub Desktop.
A script to browse the Ubuntu Touch apps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# A basic Python script to let you browse the apps available in the | |
# Ubuntu Touch App store. Run it and your web browser should open. | |
# | |
# Copyright 2014 Robert Schroll | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | |
import urlparse | |
import shutil | |
import urllib2 | |
class AppListServer(HTTPServer): | |
def __init__(self, *args): | |
HTTPServer.__init__(self, *args) | |
self.keep_running = True | |
def run(self): | |
while self.keep_running: | |
self.handle_request() | |
class AppListHandler(BaseHTTPRequestHandler): | |
def do_GET(self): | |
parsed_path = urlparse.urlparse(self.path) | |
path = urlparse.unquote(parsed_path.path[1:]) # strip leading / | |
if path == 'halt': | |
return self.halt() | |
if path.startswith('search') or path.startswith('package'): | |
return self.proxy(self.path) # include query string | |
if path == '': | |
return self.serve_string(INDEX, "text/html") | |
if path == "style.css": | |
return self.serve_string(STYLE, "text/css") | |
return self.send_error(404, "You can't always get what you want.") | |
def halt(self): | |
self.server.keep_running = False | |
self.serve_string(HALT, "text/html") | |
def proxy(self, path): | |
try: | |
remote = urllib2.urlopen("https://search.apps.ubuntu.com/api/v1" + path) | |
except urllib2.URLError as error: | |
self.send_error(500, str(error)) | |
else: | |
code = remote.getcode() | |
if code >= 400: | |
self.send_error(code, remote.read()) | |
return | |
self.send_response(code) | |
self.send_header("Content-type", remote.info().gettype()) | |
self.end_headers() | |
shutil.copyfileobj(remote, self.wfile) | |
def serve_string(self, s, mimetype): | |
self.send_response(200) | |
self.send_header("Content-type", mimetype) | |
self.send_header("Content-Length", str(len(s))) | |
self.end_headers() | |
self.wfile.write(s) | |
def log_message(self, *args): | |
pass | |
INDEX = """<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Ubuntu Touch Apps</title> | |
<script> | |
var screenshots = []; | |
var screenshotnum = 0; | |
function byID(id) { | |
return document.getElementById(id); | |
} | |
function do_get(s, callback) { | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", s, true); | |
xhr.onload = function () { callback(xhr.response); }; | |
xhr.responseType = "json"; | |
xhr.send(); | |
} | |
function do_search() { | |
var query = byID("query").value; | |
do_get("search?q=" + query + ",architecture:armhf", list_apps); | |
clear_children("results"); | |
clear_children("numresults"); | |
return false; | |
} | |
function do_lookup() { | |
var name = byID("lookupname").value; | |
do_get("package/" + name, set_details); | |
clear_children("details"); | |
return false; | |
} | |
function clear_children(id) { | |
var elem = byID(id); | |
while (elem.firstChild) | |
elem.removeChild(elem.firstChild); | |
} | |
function alpha(a, b) { | |
var A = a.toLocaleUpperCase(), | |
B = b.toLocaleUpperCase(); | |
if (A < B) | |
return -1; | |
if (A > B) | |
return 1; | |
return 0; | |
} | |
function list_apps(apps) { | |
var results = byID("results"); | |
var names = {}; | |
byID("numresults").innerHTML = apps.length + " results"; | |
for (var i=0; i<apps.length; i++) { | |
var app = apps[i]; | |
names[app.title] = app; | |
} | |
var sorted = Object.keys(names).sort(alpha); | |
for (var i=0; i<sorted.length; i++) { | |
var elem = document.createElement("li"); | |
var app = names[sorted[i]]; | |
elem.innerHTML = "<img src='" + app.icon_url + "' /><span class='title'>" + | |
app.title + "</span>"; | |
elem.onclick = (function (name) { | |
return function () { | |
byID("lookupname").value = name; | |
do_lookup(); | |
}; | |
})(app.name); | |
results.appendChild(elem); | |
} | |
} | |
function set_details(app) { | |
screenshots = app.screenshot_urls || []; | |
screenshotn = 0; | |
var details = byID("details"); | |
var desc = "<h2>" + app.title + "</h2>"; | |
if (screenshots.length > 1) | |
desc += "<img src='" + screenshots[0] + | |
"' onclick='rotate_ss(event)' class='multiple' />"; | |
else if (screenshots.length == 1) | |
desc += "<img src='" + screenshots[0] + "' />"; | |
if (app.description) | |
desc += "<p class='description'>" + app.description + "</p>"; | |
if (app.publisher) | |
desc += "<p class='publisher'><b>Publisher:</b> " + | |
"<a href='#' onclick='return publisher_click(\\"" + app.publisher + "\\")'>" + | |
app.publisher + "</a></p>"; | |
if (app.website) | |
desc += "<p class='website'><b>Website:</b> <a href='" + app.website + "'>" + | |
app.website + "</a></p>"; | |
if (app.support_url) | |
desc += "<p class='support'><b>Support:</b> <a href='" + app.support_url + "'>" + | |
app.support_url + "</a></p>"; | |
desc += "<div class='Version'>Version " + app.version + " for " + app.architecture; | |
if (app.changelog) | |
desc += "<p class='changelog'>" + app.changelog + "</p>"; | |
desc += "</div><table>"; | |
for (var key in app) | |
desc += "<tr><th>" + key + "</th><td>" + app[key] + "</td></tr>"; | |
desc += "</table>"; | |
details.innerHTML = desc; | |
} | |
function rotate_ss(event) { | |
screenshotn = (screenshotn + 1) % screenshots.length; | |
event.target.src = screenshots[screenshotn]; | |
} | |
function publisher_click(name) { | |
byID("query").value = "publisher:" + name; | |
do_search(); | |
return false; | |
} | |
window.onload = do_search; | |
</script> | |
<link rel="stylesheet" href="style.css" /> | |
</head> | |
<body> | |
<div id="search"> | |
<form onsubmit="return do_search()"> | |
<input id="query" placeholder="keywords" /> | |
<input type="submit" value="Search" /> | |
<span id="numresults"></span> | |
</form> | |
<ul id="results"></ul> | |
</div> | |
<div id="lookup"> | |
<form id="quit" action="/halt"> | |
<input type="submit" value="Quit" /> | |
</form> | |
<form onsubmit="return do_lookup()"> | |
<input id="lookupname" placeholder="Full name of app" /> | |
<input type="submit" value="Look up" /> | |
</form> | |
<div id="details"></div> | |
</div> | |
</body> | |
</html> | |
""" | |
STYLE = """body{ | |
font-family: Ubuntu, sans-serif; | |
background: #eee; | |
color: #222; | |
} | |
#search { | |
position: absolute; | |
margin: 0; | |
left: 0; | |
right: 67%; | |
top: 0; | |
bottom: 0; | |
padding: 0.5em; | |
} | |
#lookup { | |
position: absolute; | |
left: 33%; | |
right: 0; | |
top: 0; | |
bottom: 0; | |
padding: 0.5em; | |
overflow: auto; | |
} | |
#query { | |
width: 10em; | |
} | |
#lookupname { | |
width: 50%; | |
} | |
#quit { | |
float: right; | |
} | |
#results { | |
position: absolute; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
top: 3em; | |
overflow: auto; | |
margin: 0; | |
padding: 0.5em; | |
} | |
#results li { | |
display: block; | |
clear: both; | |
border-top: thin solid #222; | |
padding: 2px; | |
cursor: pointer; | |
} | |
#results li:first-child { | |
border-top:none; | |
} | |
#results img { | |
height: 48px; | |
padding-right: 0.25em; | |
vertical-align: middle; | |
} | |
#details { | |
position: absolute; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
top: 3em; | |
overflow: auto; | |
padding: 1em; | |
} | |
#details h2 { | |
text-align: center; | |
font-size: 2em; | |
} | |
#details img { | |
max-width: 50%; | |
float: left; | |
padding-right: 1em; | |
padding-bottom: 1em; | |
} | |
#details img.multiple { | |
cursor: pointer; | |
} | |
#details .description::first-line { | |
font-weight: bold; | |
} | |
#details .description, #details .changelog { | |
white-space: pre-line; | |
} | |
#details .changelog { | |
display: table; | |
margin-top: 0; | |
font-size: smaller; | |
border-left: 3px solid #aaa; | |
padding: 0 6px; | |
background: #ddd; | |
} | |
#details table { | |
font-size: smaller; | |
margin: 1em 0; | |
} | |
#details table, #details table td, #details table th { | |
border-collapse: collapse; | |
border: thin solid black; | |
white-space: pre-line; | |
} | |
""" | |
HALT = """<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Goodbye!</title> | |
</head> | |
<body> | |
<h1>Goodbye!</h1> | |
<p>You may close this window now.</p> | |
</body> | |
</html> | |
""" | |
if __name__ == '__main__': | |
import webbrowser | |
server = AppListServer(('localhost', 0), AppListHandler) | |
url = 'http://localhost:%i/' % server.server_port | |
webbrowser.open(url) | |
print "Open " + url | |
server.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment