Xo-get

From OLPC
Revision as of 23:57, 6 December 2007 by Crazy-chris (talk | contribs) (v0.4)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

XO-Get is a very simple package-installer for the olpc xo-laptop.

./xo-get.py update
./xo-get.py list     [categories/category_name]
./xo-get.py search   tag/activity_name
./xo-get.py install  activity_name
./xo-get.py remove   activity_name

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

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 = ""
	
	def __init__(self, db_path):
		self.db_path = db_path
		
		# 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

		print "- [%s/%s]:" % (self.db_path, self.db_filename),
		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 "created"
		else:
			print "ok"
			
#		q = "SELECT * FROM activities WHERE 1"
#		print self.query(q)

	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"
	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))

		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\n       - remove   activity_name"
			print
			sys.exit(0)
		
		# Loading DB
		self.db = Database(self.localpath)
		print

		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

				# 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 res[0][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

				# Security Ask
				print
				i = self.force_input("- Remove activity '%s'?" % app_name, ["y", "n"])
				if i == "n": 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:]
				
				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 sugar_loaded == False: 
					print "- Couldn't load sugar environment; removing not possible."
					print "  http://wiki.laptop.org/go/Sugar"
				if dbus_loaded == False: print "- Couldn't load dbus.mainloop.glib"
			
		else:
			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
		if s == None:
			if len(sys.argv) > 2:
				s = sys.argv[2].replace(" ", "_")
			else:
				print "- Plase supply an activity_name (eg: '%s install imagequiz')" %  sys.argv[0]
				return False
		
		# Start Installation
		print "- Installing Activity '%s'" % s
		q = "SELECT xo_url FROM activities WHERE name='%s'" % s
		res = self.db.query(q)
		
		# No xo_url means no found activity
		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": 
					self.install(res[0][0])
					return True
				else:
					return False
		
		# Installation process ready to download
		i = self.force_input("   - Do you want to proceed?", ["y", "n"])									
		if i == "n": 
			return False

		print 
		
		# start Download
		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:]

		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()

		# Start Installation - Try The Sugar Way
		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 "   - 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 c
				os.system(c)

		print
		print "- Finished"
		
	def update_xoget(self, to_version):
		# Set local script filename (/.../xo-get.py)
		fn = sys.argv[0][sys.argv[0].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)
 		
 		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('')]

		# Clear DB 
		print "- Clearing database:",
		self.db.commit("DELETE FROM activities WHERE 1")
		print "ok"

		# Add Activities
		print "- Adding activities:",

content_arr = content.split("") act_count = 0 for act_html in content_arr: # Extract Each Activity if act_html.count("") > 0:

				# Add Activity

act_html = act_html.replace("", "") act_html = act_html.replace("", "") # Put all Info in class a = Activity() a = Activity() act_html_arr = act_html.split("")

				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()