# -*- coding: utf-8 -*- # h-client, a client for an h-source server (such as http://www.h-node.com) # Copyright (C) 2011 Antonio Gallo # # # h-client is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # h-client is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with h-client. If not, see . import os import string import re import sys import pycurl import urllib import htmlentitydefs from xml.dom import minidom class Device(object): #from codename to h-source distro code _distrosTable = { 'deltah' : 'gnewsense_2_3', 'metad' : 'gnewsense_3_0', 'awen' : 'trisquel_3_5', 'taranis' : 'trisquel_4_0', 'slaine' : 'trisquel_4_5' } #list of options for the howItWorks entry _howItWorksOptions = ['yes','no'] #_allowedDistros = { #'blag_90001' : 'BLAG 90001', #'blag_120000' : 'BLAG 120000', #'dragora_1_1' : 'Dragora 1.1', #'dragora_2_0' : 'Dragora 2.0 Ardi', #'dynebolic_2_5_2' : 'Dynebolic 2.5.2 DHORUBA', #'gnewsense_2_3' : 'gNewSense 2.3 Deltah', #'gnewsense_3_0' : 'gNewSense 3.0 Metad', #'musix_2_0' : 'Musix GNU+Linux 2.0 R0', #'trisquel_3_5' : 'Trisquel 3.5 Awen', #'trisquel_4_0' : 'Trisquel 4.0 Taranis', #'trisquel_4_5' : 'Trisquel 4.5 Slaine', #'ututo_xs_2009' : 'UTUTO XS 2009', #'ututo_xs_2010' : 'UTUTO XS 2010', #'venenux_0_8' : 'VENENUX 0.8', #'venenux_0_8_2' : 'VENENUX-EC 0.8.2' #} #allowed years of commercialization _years = [ 'not-specified', '2011', '2010', '2009', '2008', '2007', '2006', '2005', '2004', '2003', '2002', '2001', '2000', '1999', '1998', '1997', '1996', '1995', '1994', '1993', '1992' ] #list of interfaces _interfaces = [] _status = True errors = [] def __init__(self): self._post = {} self._type = '' self._vendor = '' self._model = '' self._kernel = '' self._distributions = [self.userDistribution()] self._interface = 'not-specified' self._year = 'not-specified' self._vendorId = '' self._productId = '' self._howItWorks = '' self._driver = '' self._description = '' def setPost(self): self._post['model'] = self._model; self._post['kernel'] = self._kernel; self._post['distribution'] = self.createDistroEntry(); self._post['comm_year'] = self._year; self._post['pci_id'] = self._vendorId + ':' + self._productId; self._post['interface'] = self._interface; self._post['description'] = self._description.replace("\n","\r\n"); self._post['driver'] = self._driver; #replace the HTML entitites with utf-8 characters def htmlentitiesDecode(self,string): for entity,code in htmlentitydefs.name2codepoint.iteritems(): string = string.replace("&"+entity+";",unichr(code)) return string.encode('utf-8') #get the h-source distro code from the codename def getDistroCode(self,codenameString): codenames = self._distrosTable.keys() for codename in codenames: if codenameString.find(codename) != -1: return self._distrosTable[codename] return '' #create the distribution entry def createDistroEntry(self): cleanDistros = [] for distro in self._distributions: cleanDistros.append(distro.lstrip().rstrip()) #remove duplicates cleanDistros = list(set(cleanDistros)) #sort the elements cleanDistros = sorted(cleanDistros) dis = ' , '.join(cleanDistros) #correct a strange python behaviour if dis != '': if dis[0] == ' , ': dis = dis[3:] return dis #add a distribution def addDistribution(self,distroName): self._distributions.append(distroName) #add many distributions #distroList: comma separated list of distros (type: string) def addDistributions(self,distroList): distros = distroList.split(',') for distro in distros: self.addDistribution(distro) #get the h-source distro code of the user def userDistribution(self): if not os.system('cat /etc/*-release | grep CODENAME > tmp/temp'): f = open('tmp/temp','r') row = f.readline().replace("\n","").lower() return self.getDistroCode(row) f.close(); else: self._status = False self.errors.append('tmp folder not writable') def getHowItWorksOptions(self): return self._howItWorksOptions def getInterfaces(self): return self._interfaces def getYears(self): return self._years def getType(self): return self._type def getVendor(self): return self._vendor def getModel(self): return self._model def getKernel(self): return self._kernel def getDistributions(self): return self._distributions def getInterface(self): return self._interface def getYear(self): return self._year def getVendorId(self): return self._vendorId def getProductId(self): return self._productId def getHowItWorks(self): return self._howItWorks def getDriver(self): return self._driver def getDescription(self): return self._description def setType(self,ttype): self._type = ttype def setVendor(self,vendor): self._vendor = vendor def setModel(self,model): self._model = model def setKernel(self,kernel): self._kernel = kernel def setDistributions(self,distributions): self._distributions = distributions def setInterface(self,interface): self._interface = interface def setYear(self,year): self._year = year def setVendorId(self,vendorId): self._vendorId = vendorId def setProductId(self,productId): self._productId = productId def setHowItWorks(self,howItWorks): self._howItWorks = howItWorks def setDriver(self,driver): self._driver = driver def setDescription(self,description): self._description = description def getStatus(self): return self._status def getPost(self): return self._post class Videocard(Device): def __init__(self): super(Videocard, self).__init__() self._type = 'videocard' self._howItWorks = 'does_not_work' self._interfaces = ['not-specified','PCI','AGP','PCI-E','ISA','MCA','VLB'] self._howItWorksOptions = ['works_with_3D','works_without_3D','does_not_work'] def setPost(self): super(Videocard, self).setPost() self._post['video_card_works'] = self._howItWorks class Wifi(Device): def __init__(self): super(Wifi, self).__init__() self._type = 'wifi' self._howItWorks = 'no' self._interfaces = ['not-specified','USB','PCI','PCI-E','mini-PCI','mini-PCI-E','ExpressCard','PC-Card'] def setPost(self): super(Wifi, self).setPost() self._post['wifi_works'] = self._howItWorks class Soundcard(Device): def __init__(self): super(Soundcard, self).__init__() self._type = 'soundcard' self._howItWorks = 'no' self._interfaces = ['not-specified','PCI','ISA','USB','Firewire','Parallel','PCI-E','PCMCIA'] def setPost(self): super(Soundcard, self).setPost() self._post['sound_card_works'] = self._howItWorks #class to carry out http requests by means of pycurl class Mycurl: _post = None #set the domain def __init__(self,domain): self.contents = '' self.setDomain(domain) def getStatus(self): return self._status def setDomain(self,domain): self.domain = domain #check if the trailing slash is present if len(self.domain) > 1: if self.domain[len(self.domain)-1] != '/': self.domain += '/' def getDomain(self): return self.domain def setPost(self,post): self._post = post def body_callback(self, buf): self.contents = self.contents + buf #perform the HTTP request def perform(self,requestUri = ''): self.url = self.domain + requestUri; #print self.url self.contents = '' c = pycurl.Curl() c.setopt(c.URL, self.url) c.setopt(pycurl.COOKIEFILE, 'tmp/cookies.txt') c.setopt(pycurl.COOKIEJAR, 'tmp/cookies.txt') if self._post != None: c.setopt(c.POSTFIELDS, urllib.urlencode(self._post)) c.setopt(c.WRITEFUNCTION, self.body_callback) try: c.perform() result = True except: result = False #print c.getinfo(c.HTTP_CODE) #result = False #if c.getinfo(c.HTTP_CODE) == 200: #result = True c.close() return result class Client: devices = {} _status = True errors = [] _types = { '0403' : { 'type' : 'soundcard', 'controller': 'soundcards' }, '0280' : { 'type' : 'wifi', 'controller': 'wifi' }, '0300' : { 'type' : 'videocard', 'controller': 'videocards' } } def __init__(self,url = ''): self.request = Mycurl(url) def getNode(self): return self.request.getDomain() def setNode(self,domain): self.request.setDomain(domain) #get the type from the Class id def getType(self, Class): Classes = self._types.keys() if Class in Classes: return self._types[Class]['type'] return None #get the controller from the Class id def getController(self, Class): Classes = self._types.keys() if Class in Classes: return self._types[Class]['controller'] return None #return a device object def getObject(self,Class): if Class == '0403': return Soundcard() elif Class == '0280': return Wifi() elif Class == '0300': return Videocard() else: return None #get the system kernel def getKernel(self): if not os.system('uname -r > tmp/temp'): f = open('tmp/temp','r') row = f.readline().replace("\n","") f.close(); return row else: self._status = False self.errors.append('tmp folder not writable') #log in def login(self, username, password): self.request.setPost({'username' : username, 'password' : password}) result = self.request.perform('users/login/en') self.request.setPost(None) if result: result_login = self.isLogged() if result_login != -2: if result_login: return True else: self.errors.append("wrong username or password") else: self.errors.append("unable to connect to server") return False #log out def logout(self): result = self.request.perform('users/logout/en') if result: return True else: self.errors.append("unable to connect to server") return False #get info about the user logged def getUserInfo(self): result = self.request.perform('client/userinfo/en') if result: try: xmldoc = minidom.parseString(self.request.contents) status = xmldoc.getElementsByTagName("status")[0].childNodes[0].data username = '' token = '' groups = '' if status == 'logged': username = xmldoc.getElementsByTagName("username")[0].childNodes[0].data token = xmldoc.getElementsByTagName("token")[0].childNodes[0].data groups = xmldoc.getElementsByTagName("groups")[0].childNodes[0].data return {'status':status,'username':username,'token':token,'groups':groups} except: self.errors.append("the server is not up-to-date: unable to parse the xml database") return -2 else: self.errors.append("unable to connect to server") return False #return True if the user is logged, else return False def isLogged(self): info = self.getUserInfo() if info != False: if info != -2: if info['status'] == 'logged': return True else: return -2 return False #return the license info def getLicenseNotice(self): result = self.request.perform("client/licenseinfo/en"); if result: xmldoc = minidom.parseString(self.request.contents) notice = xmldoc.getElementsByTagName("license_info")[0].childNodes[0].data.encode('utf-8') return notice else: self.errors.append("unable to connect to server") return False def createDevices(self): if not os.system('lspci -vmmnn > tmp/temp'): f = open('tmp/temp','r') while 1: row = f.readline() if not row: break #get the slot if row.find('Slot') != -1: #get the class row = f.readline().replace("\n","") if row: cl = re.match('Class\:(.*)\[(.*)\]',row,re.I) if cl: #get the object dev = self.getObject(cl.group(2)) if dev: #set the type attribute of the device object dev.setType(self.getType(cl.group(2))) #get the vendorid row = f.readline().replace("\n","") if row: vn = re.match('Vendor\:(.*)\[(.*)\]',row,re.I) if vn: dev.setVendorId(vn.group(2).replace("\t","")) #get the productid row = f.readline().replace("\n","") if row: pr = re.match('Device\:(.*)\[(.*)\]',row,re.I) if pr: dev.setProductId(pr.group(2).replace("\t","")) dev.setModel(pr.group(1).replace("\t","")) dev.setKernel(self.getKernel()) self.devices['p_' + dev.getVendorId() + ':' + dev.getProductId()] = [dev,cl.group(2),'insert','0'] else: self._status = False self.errors.append('the lspci -vmmnn output is not a standard output, some products row not found') else: self._status = False self.errors.append('the lspci -vmmnn output is not a standard output, some vendors row not found') else: self._status = False self.errors.append('the lspci -vmmnn output is not a standard output, some class row not found') f.close(); else: self._status = False self.errors.append('tmp folder not writable') #syncronize with the xml database def sync(self): #perform an http request self.request.contents = '' result = self.request.perform('download/all/en') #print result if result: #loop the found devices for key,dev in self.devices.iteritems(): #find the class #Class = dev[1] vendorid_productid = key[2:] #find the controller #controller = self.getController(Class) ##perform an http request #self.request.contents = '' #result = self.request.perform('download/' + controller + '/en') #parse the xml database try: xmldoc = minidom.parseString(self.request.contents) devices = xmldoc.getElementsByTagName("device") for device in devices: #check it is not a notebook deviceType = device.getElementsByTagName("type")[0].childNodes[0].data.encode('utf-8') if deviceType != 'notebook': code = device.getElementsByTagName("vendorid_productid")[0] if code.hasChildNodes(): if (code.childNodes[0].data == vendorid_productid): modelName = device.getElementsByTagName("model_name")[0].childNodes[0].data.encode('utf-8') interface = device.getElementsByTagName("interface")[0].childNodes[0].data.encode('utf-8') distribution = device.getElementsByTagName("distribution")[0].childNodes[0].data.encode('utf-8') idDevice = device.getElementsByTagName("id")[0].childNodes[0].data.encode('utf-8') works = device.getElementsByTagName("it_works")[0].childNodes[0].data.encode('utf-8') year = device.getElementsByTagName("year")[0].childNodes[0].data.encode('utf-8') if device.getElementsByTagName("description")[0].hasChildNodes(): description = device.getElementsByTagName("description")[0].childNodes[0].data.encode('utf-8') dev[0].setDescription(dev[0].htmlentitiesDecode(description)) else: dev[0].setDescription('') if device.getElementsByTagName("kernel_libre")[0].hasChildNodes(): kernel_libre = device.getElementsByTagName("kernel_libre")[0].childNodes[0].data.encode('utf-8') dev[0].setKernel(kernel_libre) if device.getElementsByTagName("driver")[0].hasChildNodes(): driver = device.getElementsByTagName("driver")[0].childNodes[0].data.encode('utf-8') dev[0].setDriver(driver) else: dev[0].setDriver('') #print modelName dev[0].setModel(modelName) dev[0].setInterface(interface) dev[0].addDistributions(distribution) dev[0].setHowItWorks(works) dev[0].setYear(year) dev[2] = 'update' dev[3] = idDevice except: self.errors.append("the server is not up-to-date: unable to parse the xml database") else: self.errors.append("unable to connect to server") def submit(self,deviceCode = None): for key,dev in self.devices.iteritems(): if key == deviceCode or deviceCode == None: dev[0].setPost() post = dev[0].getPost() #get the node controller controller = self.getController(dev[1]) #get the user info info = self.getUserInfo() token = info['token'] post['from_client'] = 'yes' if dev[2] == 'update': post['id_hard'] = dev[3] post['updateAction'] = 'update' url = controller + '/update/en/' + token elif dev[2] == 'insert': post['insertAction'] = 'insert' url = controller + '/insert/en/' + token self.request.setPost(post) self.request.perform(url.encode('utf-8')) #parse the response xmldoc = minidom.parseString(self.request.contents) deviceType = xmldoc.getElementsByTagName("status")[0].childNodes[0].data.encode('utf-8') notice = xmldoc.getElementsByTagName("notice")[0].childNodes[1].data.encode('utf-8') self.errors.append(notice.lstrip().rstrip()) if deviceType == 'executed': return True else: return False