Xo-get: Difference between revisions
Jump to navigation
Jump to search
Crazy-chris (talk | contribs) (0.4.1: install and remove any local .xo file) |
Crazy-chris (talk | contribs) (remove source from wiki) |
||
Line 1: | Line 1: | ||
XO-Get is a very simple package-installer for |
XO-Get is a very simple package-installer / manager, used for installing, testing and removing activities for / on the xo-laptop. |
||
# [http://www.linuxuser.at/xo-get.txt Source] |
# [http://www.linuxuser.at/xo-get.txt Source] (GPL) |
||
# [http://www.olpcaustria.org/mediawiki/index.php/Xo-get/Screenshots Screenshots] |
|||
# License: GPL |
|||
# |
# [http://www.olpcaustria.org/mediawiki/index.php/Xo-get/Repository Public Repository]: 25 Activities (add your .xo now!) |
||
# [http://www.olpcaustria.org/mediawiki/index.php/Xo-get Project Page] @ Olpc-Austria |
|||
# Installing & Removing of local .xo files, or download over web |
|||
# Installing |
# Installing & removing via local .xo files or downloaded bundles |
||
# |
# ActivityBundle.install(), Unzipping, ActivityBundle.uninstall() |
||
# Infos stored in a local sqlite3 db (in ~/.xo_get/activities.db) |
# Infos stored in a local sqlite3 db (in ~/.xo_get/activities.db) |
||
# Successfully tested on the xo (build_648) |
# Successfully tested on the xo (build_648) |
||
# Supported commands: |
# Supported commands: |
||
./xo-get.py update |
./xo-get.py update |
||
Line 16: | Line 16: | ||
'''[http://www.linuxuser.at/xo-get.txt xo-get.py]''' |
'''[http://www.linuxuser.at/xo-get.txt xo-get.py]''' |
||
#! /usr/bin/env python |
|||
# script started by crazy-chris, olpc-austria |
|||
# project page: http://www.olpcaustria.org/mediawiki/index.php/Xo-get |
|||
# license: gpl |
|||
# version: 0.4.1 |
|||
VERSION_NEWS = " 1. Install and remove any .xo file (like 'test.xo'): \n\ |
|||
./xo-get.py install test.xo \n\ |
|||
./xo-get.py remove test.xo" |
|||
import os |
|||
import sys |
|||
import urllib2 |
|||
import sqlite3 |
|||
class Activity: |
|||
name = u"" |
|||
desc = u"" |
|||
xo_url = u"" |
|||
category = u"" |
|||
tags = u"" |
|||
class Database: |
|||
db_path = "" |
|||
db_filename = "activities.db" |
|||
cur = "" |
|||
con = "" |
|||
version = "" |
|||
def __init__(self, db_path, version): |
|||
self.db_path = db_path |
|||
self.version = version |
|||
# Change Directory |
|||
try: os.mkdir(self.db_path) |
|||
except: pass |
|||
os.chdir(self.db_path) |
|||
# Init DB |
|||
create_db = True |
|||
if os.path.isfile(self.db_filename): create_db = False |
|||
self.con = sqlite3.connect(self.db_filename) |
|||
self.cur = self.con.cursor() |
|||
self.cur.execute("-- types unicode") |
|||
if create_db: |
|||
# Setup New Database |
|||
self.cur.execute("CREATE TABLE 'activities' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' VARCHAR( 100 ) NOT NULL, 'desc' VARCHAR( 355 ) NOT NULL, 'xo_url' VARCHAR( 255 ) NOT NULL, 'category' VARCHAR( 255 ) NOT NULL, 'tags' VARCHAR( 255 ) NOT NULL);") |
|||
self.con.commit() |
|||
print "- [%s/%s]: created\n" % (self.db_path, self.db_filename), |
|||
# print "created" |
|||
else: |
|||
# print "ok" |
|||
pass |
|||
# Check for version & display version news on update |
|||
self.cur.execute("CREATE TABLE IF NOT EXISTS 'versions' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'version' VARCHAR( 10 ) NOT NULL, 'notes' VARCHAR( 355 ) NOT NULL);") |
|||
self.con.commit() |
|||
q = "SELECT count(*) FROM versions WHERE version='%s'" % version |
|||
res = self.query(q) |
|||
if res[0][0] == 0: |
|||
# New Version Update |
|||
q = "INSERT INTO versions (version, notes) VALUES ('%s', '%s')" % (version, VERSION_NEWS.replace("'", '"')) |
|||
self.commit(q) |
|||
print |
|||
print " News to version %s:\n -----------------%s\n%s" % (version, "-"*len(version), VERSION_NEWS) |
|||
def query(self, q): |
|||
dataList = [] |
|||
self.cur.execute(q) |
|||
data = self.cur.fetchall() |
|||
if data: dataList = [list(row) for row in data] |
|||
return dataList |
|||
def commit(self, q): |
|||
self.cur.execute(q) |
|||
self.con.commit() |
|||
class XOGet: |
|||
version = "0.4.1" |
|||
update_url = "http://www.linuxuser.at/xo-get.txt" |
|||
# domain = "http://wiki.laptop.org" |
|||
# path = "/go/Xo-get/Repository" |
|||
domain = "http://www.olpcaustria.org" |
|||
path = "/mediawiki/index.php?title=Xo-get/Repository&printable=yes" |
|||
localpath = "%s/%s" % (os.path.expanduser( '~' ), ".xo_get") |
|||
def __init__(self): |
|||
print |
|||
print " xo-get %s" % self.version |
|||
print " %s" % ("~" * (len(self.version)+7)) |
|||
# Loading DB |
|||
self.db = Database(self.localpath, self.version) |
|||
print |
|||
if len(sys.argv) == 1: |
|||
print " http://www.olpcaustria.org/mediawiki/index.php/Xo-get\n\n use: - update\n - list ['categories' / category_name] \n - search activity_name / tag\n - install activity_name / activity_file.xo \n - remove activity_name / activity_file.xo" |
|||
print |
|||
sys.exit(0) |
|||
if sys.argv[1] == "update": self.update() |
|||
if sys.argv[1] == "list": self.list() |
|||
if sys.argv[1] == "search": self.search() |
|||
if sys.argv[1] == "install": self.install() |
|||
if sys.argv[1] == "remove": self.remove() |
|||
print |
|||
def force_input(self, question, possibilities): |
|||
i = "" |
|||
while i not in possibilities: |
|||
print "%s [%s]" % (question, "/".join(possibilities)), |
|||
i = raw_input() |
|||
return i |
|||
def remove(self): |
|||
# To remove, we need sugar, and the .xo file |
|||
if len(sys.argv) > 2: |
|||
# Load Sugar |
|||
try: |
|||
sugar_loaded = True |
|||
from sugar.bundle.activitybundle import ActivityBundle |
|||
except: sugar_loaded = False |
|||
try: |
|||
dbus_loaded = True |
|||
from dbus.mainloop.glib import DBusGMainLoop |
|||
DBusGMainLoop(set_as_default=True) |
|||
except: dbus_loaded = False |
|||
if sugar_loaded and dbus_loaded: |
|||
# Try to get correct app_name, then xo file |
|||
app_name = sys.argv[2] |
|||
print "- Starting to remove '%s'" % app_name |
|||
# NEW: Install any .xo-file (outside repo) |
|||
if os.path.isfile(app_name): |
|||
print " - (Local file)" |
|||
fn = app_name |
|||
else: |
|||
# Normal install via activity_name |
|||
# -> Get .xo Filename |
|||
q = "SELECT xo_url FROM activities WHERE name = '%s'" % app_name |
|||
res = self.db.query(q) |
|||
if len(res) == 0: |
|||
print " - No exact matching name found." |
|||
q = "SELECT name FROM activities WHERE name LIKE '%s'" % app_name |
|||
res = self.db.query(q) |
|||
if len(res) > 0: |
|||
app_name = res[0][0] |
|||
q = "SELECT xo_url FROM activities WHERE name = '%s'" % app_name |
|||
res = self.db.query(q) |
|||
else: |
|||
return False |
|||
# Extract Filename from URL => Write function for that (used 2x) |
|||
fn = "%s/%s" % (self.localpath, res[0][0][res[0][0].rindex('/')+1:]) |
|||
if fn.count(";f=") > 0: |
|||
fn = fn[fn.rindex(";f=")+3:] |
|||
# Security Question |
|||
print |
|||
i = self.force_input("- Remove activity '%s'?" % app_name, ["y", "n"]) |
|||
if i == "n": return False |
|||
if os.path.isfile(fn): |
|||
print "- Source file found (%s)" % fn |
|||
else: |
|||
print "- No source file found (%s)" % fn |
|||
return False |
|||
bundle = ActivityBundle(fn) |
|||
if bundle.is_installed(): |
|||
print "- Removing..." |
|||
bundle.uninstall() |
|||
print "- Finished!" |
|||
return True |
|||
else: |
|||
print "- Activity is not yet installed" |
|||
return False |
|||
else: |
|||
# DBUS or Sugar Failure |
|||
if dbus_loaded == False: print "- Couldn't load dbus.mainloop.glib" |
|||
if sugar_loaded == False: |
|||
print "- Couldn't load sugar environment; removing not possible." |
|||
print " http://wiki.laptop.org/go/Sugar" |
|||
else: |
|||
# len(sys.argv) != 2 |
|||
print "- Plase supply an activity_name (eg: '%s remove imagequiz')" % sys.argv[0] |
|||
return False |
|||
def list(self): |
|||
# Lists available Activities |
|||
q = "SELECT name, desc, category, tags FROM activities WHERE 1" |
|||
print "- Listing", |
|||
if len(sys.argv) > 2: |
|||
if sys.argv[2] == "categories": |
|||
print "Categories:" |
|||
q = "SELECT DISTINCT category FROM activities WHERE 1 ORDER BY category ASC" |
|||
res = self.db.query(q) |
|||
for r in res: |
|||
print " - [%s]" % r[0] |
|||
return True |
|||
else: |
|||
print "Category '%s'" % sys.argv[2] |
|||
q = "%s AND category LIKE '%s'" % (q, sys.argv[2]) |
|||
else: |
|||
print "All Activities" |
|||
q = "%s%s" % (q, " ORDER BY category ASC, name ASC") |
|||
res = self.db.query(q) |
|||
spaces = 18 |
|||
spaces2 = 14 |
|||
for r in res: |
|||
print " -", " " * (spaces2 - len(r[2])), |
|||
print "[ %s ] " % r[2], r[0], "." * (spaces - len(r[0])), r[1] |
|||
def search(self): |
|||
# Search for tags and name |
|||
if len(sys.argv) > 2: |
|||
s = sys.argv[2].replace(" ", "_") |
|||
print "- Results for '%s':" % s |
|||
q = "SELECT name, desc, category FROM activities WHERE tags LIKE '%s%s%s' OR name LIKE '%s%s%s' ORDER BY category ASC, name ASC" % ('%', s, '%', '%', s, '%') |
|||
res = self.db.query(q) |
|||
spaces = 18 |
|||
spaces2 = 8 |
|||
for r in res: |
|||
print " - [%s]" % r[2], " " * (spaces2 - len(r[2])), r[0], "." * (spaces - len(r[0])), r[1] |
|||
else: |
|||
print "- Please supply query-string (eg: '%s search quiz')" % sys.argv[0] |
|||
def install(self, s = None): |
|||
# s = Activity_Name, If not supplied to function, take from sys.argv |
|||
# local .xo filename |
|||
fn = None |
|||
# Check Parameters |
|||
if s == None: |
|||
if len(sys.argv) > 2: |
|||
s = sys.argv[2] |
|||
if os.path.isfile(s): |
|||
# NEW: Supplied .xo app-name will search for .xo file, and if found installs from that |
|||
fn = s |
|||
print "- Installing Activity '%s'" % s |
|||
i = self.force_input(" - Do you want to proceed?", ["y", "n"]) |
|||
if i == "n": return False |
|||
print |
|||
else: |
|||
# Else: Format Name (" " > "_") |
|||
s = s.replace(" ", "_") |
|||
print "- Installing Activity '%s'" % s |
|||
else: |
|||
print "- Plase supply an activity_name (eg: '%s install imagequiz')" % sys.argv[0] |
|||
return False |
|||
# If there is no direct .xo-name supplied, search DB for .xo url |
|||
if fn == None: |
|||
q = "SELECT xo_url FROM activities WHERE name='%s'" % s |
|||
res = self.db.query(q) |
|||
# If no xo_url, then Search for similar Name! |
|||
if len(res) == 0: |
|||
print " - No matching name found." |
|||
# Search DB with LIKE |
|||
q = "SELECT name FROM activities WHERE name LIKE '%s'" % s |
|||
res = self.db.query(q) |
|||
if len(res) == 0: |
|||
print " - Nothing found. Please try 'xo-get search'" |
|||
return False |
|||
else: |
|||
print " - Found quite similar name: '%s'" % res[0][0] |
|||
# Only Upper/Lowercase Problem |
|||
if s.lower() == res[0][0].lower(): |
|||
print |
|||
self.install(res[0][0]) |
|||
return True |
|||
# Else Ask if to take it |
|||
i = self.force_input(" - Use this name?", ["y", "n"]) |
|||
print |
|||
if i == "y": |
|||
print |
|||
self.install(res[0][0]) |
|||
return True |
|||
else: |
|||
return False |
|||
url = res[0][0] |
|||
fn = "%s/%s" % (self.localpath, res[0][0][res[0][0].rindex('/')+1:]) |
|||
if fn.count(";f=") > 0: |
|||
fn = fn[fn.index(";f=")+3:] |
|||
# Installation process ready to download |
|||
i = self.force_input(" - Do you want to proceed?", ["y", "n"]) |
|||
if i == "n": return False |
|||
print |
|||
# start Download |
|||
print "- Download => %s" % (fn) |
|||
# print "- %s => %s" % (url, fn) |
|||
# 1. Check if file exists |
|||
download = True |
|||
if os.path.isfile(fn): |
|||
# File Exists: Ask if dl or use |
|||
i = self.force_input(" - File already exists. Download again?", ["y", "n"]) |
|||
if i == "n": |
|||
download = False |
|||
print |
|||
# 2. If wanted, download .xo |
|||
if download: |
|||
try: xo = urllib2.urlopen(url).read() |
|||
except: |
|||
print "- Could not contact server." |
|||
return False |
|||
f = open(fn, "w") |
|||
f.write(xo) |
|||
f.close() |
|||
else: # (if fn != None) |
|||
# If supplied .xo-filename, it's already checked and ready to go |
|||
pass |
|||
# Start Installation - Try The Sugar Way |
|||
# app_name and fn, all ok, proceed to installation |
|||
try: |
|||
sugar_loaded = True |
|||
from sugar.bundle.activitybundle import ActivityBundle |
|||
except: sugar_loaded = False |
|||
try: |
|||
dbus_loaded = True |
|||
from dbus.mainloop.glib import DBusGMainLoop |
|||
DBusGMainLoop(set_as_default=True) |
|||
except: dbus_loaded = False |
|||
print "- Starting Installation" |
|||
if sugar_loaded and dbus_loaded: |
|||
# install the sugar way |
|||
# like os.system("sugar-install-bundle %s" % fn), but in here: |
|||
bundle = ActivityBundle(fn) |
|||
try: bundle.install() |
|||
except: print " - I think it's already installed :-)" |
|||
else: |
|||
if sugar_loaded == False: print " - Couldn't load sugar.bundle.activitybundle" |
|||
if dbus_loaded == False: print " - Couldn't load dbus.mainloop.glib" |
|||
i = self.force_input(" - Extract the Activity Bundle to %s/?" % self.localpath, ["y", "n"]) |
|||
if i == "y": |
|||
c = "unzip %s -d %s" % (fn, self.localpath) |
|||
print "\n- %s" % c |
|||
os.system(c) |
|||
print |
|||
print "- Finished" |
|||
def update_xoget(self, to_version): |
|||
# Set local script filename (/.../xo-get.py) |
|||
fn = sys.argv[0] |
|||
if fn.count('/') > 0: fn = fn[fn.rindex('/'):] |
|||
fn = "%s%s" % (sys.path[0], fn) |
|||
print "- Updating", fn |
|||
# Download new Script |
|||
content = urllib2.urlopen(self.update_url).read() |
|||
f = open(fn, "w") |
|||
f.write(content) |
|||
f.close() |
|||
# os.system("python %s init" % sys.argv[0]) |
|||
# print "rm %s/%s" % (self.localpath, self.db.db_filename) |
|||
print "- Update to version %s finished" % to_version |
|||
def update(self): |
|||
# Download |
|||
# print "- Contacting server: [ %s%s ]" % (self.domain, self.path) |
|||
print "- Contacting server: [ %s ]" % (self.domain) |
|||
try: content = urllib2.urlopen("%s%s" % (self.domain, self.path)).read() |
|||
except: |
|||
"- Server Down :(" |
|||
return False |
|||
# Extract Version => version |
|||
try: |
|||
version = content[content.index('cur_ver=')+8:] |
|||
version = version[:version.index('<')] |
|||
except: |
|||
version = None |
|||
print "- Local version: %s (current: %s)" % (self.version, version) |
|||
# Check Version |
|||
if self.version != version and version != None: |
|||
# Update Possible |
|||
i = self.force_input(" - Update xo-get to version: %s?" % version, ["y", "n"]) |
|||
if i == "y": |
|||
# Update Now |
|||
print |
|||
self.update_xoget(version) |
|||
return True |
|||
# Extract Repository => content |
|||
print |
|||
content = content[content.index('<table border="0"'):] |
|||
content = content[:content.index('</table>')] |
|||
# Clear DB |
|||
print "- Clearing database:", |
|||
self.db.commit("DELETE FROM activities WHERE 1") |
|||
print "ok" |
|||
# Add Activities |
|||
print "- Adding activities:", |
|||
content_arr = content.split("<tr>") |
|||
act_count = 0 |
|||
for act_html in content_arr: |
|||
# Extract Each Activity |
|||
if act_html.count("<td>") > 0: |
|||
# Add Activity |
|||
act_html = act_html.replace("</td>", "") |
|||
act_html = act_html.replace("</tr>", "") |
|||
# Put all Info in class a = Activity() |
|||
a = Activity() |
|||
act_html_arr = act_html.split("<td>") |
|||
a.name = act_html_arr[1].strip().replace(" ", "_") |
|||
if a.name.count('">') > 0: |
|||
a.name = a.name[a.name.index('">')+2:] |
|||
a.name = a.name[:a.name.index('<')] |
|||
# Existing Activity? |
|||
if a.name != "": |
|||
a.xo_url = act_html_arr[2].strip().replace("%s%s" % ("&a", "mp"), "&") |
|||
a.xo_url = a.xo_url[a.xo_url.index("http"):] |
|||
if a.xo_url.count('"') > 0: a.xo_url = a.xo_url[:a.xo_url.index('"')] |
|||
a.desc = act_html_arr[3].strip() |
|||
a.category = act_html_arr[4].strip() |
|||
a.tags = act_html_arr[5].strip() |
|||
# Submit to DB |
|||
q = "INSERT INTO activities (name, xo_url, desc, category, tags) VALUES ('%s', '%s', '%s', '%s', '%s')" % (a.name, a.xo_url, a.desc, a.category, a.tags) |
|||
act_count += 1 |
|||
self.db.commit(q) |
|||
print act_count |
|||
print "- Database is up-to-date" |
|||
if __name__ == "__main__": |
|||
xoget = XOGet() |
Revision as of 16:24, 7 December 2007
XO-Get is a very simple package-installer / manager, used for installing, testing and removing activities for / on the xo-laptop.
- Source (GPL)
- Screenshots
- Public Repository: 25 Activities (add your .xo now!)
- Project Page @ Olpc-Austria
- Installing & removing via local .xo files or downloaded bundles
- ActivityBundle.install(), Unzipping, ActivityBundle.uninstall()
- Infos stored in a local sqlite3 db (in ~/.xo_get/activities.db)
- Successfully tested on the xo (build_648)
- Supported commands:
./xo-get.py update ./xo-get.py list ['categories' / category_name] ./xo-get.py search activity_name / tag ./xo-get.py install activity_name / activity_file.xo ./xo-get.py remove activity_name / activity_file.xo