Skip to content

Instantly share code, notes, and snippets.

@jamestait
Forked from rschroll/applist.py
Last active January 30, 2017 13:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamestait/539788077e86c89bff7c to your computer and use it in GitHub Desktop.
Save jamestait/539788077e86c89bff7c to your computer and use it in GitHub Desktop.
A script to browse the Ubuntu Touch apps
#!/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
FRAMEWORKS = [
'ubuntu-sdk-13.10',
'ubuntu-sdk-14.04-dev1',
'ubuntu-sdk-14.04-html-dev1',
'ubuntu-sdk-14.04-html',
'ubuntu-sdk-14.04-papi-dev1',
'ubuntu-sdk-14.04-papi',
'ubuntu-sdk-14.04-qml-dev1',
'ubuntu-sdk-14.04-qml',
'ubuntu-sdk-14.04',
'ubuntu-sdk-14.10-dev1',
'ubuntu-sdk-14.10-dev2',
'ubuntu-sdk-14.10-html-dev1',
'ubuntu-sdk-14.10-html-dev2',
'ubuntu-sdk-14.10-papi-dev1',
'ubuntu-sdk-14.10-papi-dev2',
'ubuntu-sdk-14.10-qml-dev1',
'ubuntu-sdk-14.10-qml-dev2',
'ubuntu-sdk-14.10-qml-dev3',
]
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') or
path.startswith('departments')):
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:
request = urllib2.Request(
"https://search.apps.ubuntu.com/api/v1" + path,
headers={'X-Ubuntu-Architecture': 'armhf',
'X-Ubuntu-Frameworks': ','.join(FRAMEWORKS)})
remote = urllib2.urlopen(request)
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.setRequestHeader('Accept', 'application/hal+json');
xhr.responseType = "json";
xhr.send();
}
function do_search() {
var path = "search";
var query = byID("query").value;
if (query) {
path += "?q=" + query;
}
do_get(path, 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 do_dept(name) {
do_get("departments/" + name, list_apps);
clear_children("results");
clear_children("numresults");
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(response) {
var results = byID("results");
var names = {};
apps = response._embedded["clickindex:package"];
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.department)
desc += "<p class='department'><b>Department:</b> ";
app.department.forEach(function (dept) {
desc += "<a href='#' onclick='return department_click(\\"" + dept + "\\")'>" +
dept + "</a><br>";
});
desc += "</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) {
if (key != "_links") {
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;
}
function department_click(name) {
do_dept(name);
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