source: lliurex-store/trunk/fuentes/python3-lliurex-store.install/usr/share/lliurexstore/plugins/loadStore.py @ 5188

Last change on this file since 5188 was 5188, checked in by Juanma, 21 months ago

loadStore manages the appimage catalogue

File size: 11.5 KB
Line 
1import os
2import gi
3from gi.repository import Gio
4gi.require_version('AppStreamGlib', '1.0')
5from gi.repository import AppStreamGlib as appstream
6import subprocess
7import json
8import re
9import urllib
10import random
11import threading
12import time
13
14class loadstore:
15        def __init__(self):
16                self.dbg=0
17                self.pluginInfo={'load':'*'}
18                self.store=''
19                self.progress=0
20                self.error=0
21        #def __init__
22
23        def set_debug(self,dbg='1'):
24                self.dbg=int(dbg)
25                self._debug ("Debug enabled")
26        #def set_debug
27
28        def _debug(self,msg=''):
29                if self.dbg==1:
30                        print ('DEBUG Load: '+str(msg))
31        #def _debug
32
33        def register(self):
34                return(self.pluginInfo)
35        #def register
36
37        def execute_action(self,action,store=None,loadBundles=False):
38                self.progress=0
39                if store:
40                        self.store=store
41                else:
42                        self.store=appstream.Store()
43                if action=='load':
44                        self._load_store(self.store,loadBundles)
45                self.progress=100
46                return (self.store)
47        #def execute_action
48
49        def get_error(self):
50                return (self.error)
51        #def get_error
52
53        def _load_store(self,store,loadBundles=False):
54                iconPath='/usr/share/icons/hicolor/128x128'
55                flags=[appstream.StoreLoadFlags.APP_INFO_SYSTEM,appstream.StoreLoadFlags.APP_INSTALL,appstream.StoreLoadFlags.APP_INFO_USER,appstream.StoreLoadFlags.DESKTOP,appstream.StoreLoadFlags.APPDATA,appstream.StoreLoadFlags.ALLOW_VETO]
56                for flag in flags:
57                        try:
58                                self._debug("Loading "+str(flag))
59                                store.load(flag)
60                        except:
61                                print ("Failed to load"+str(flag))
62                                pass
63                store=self._sanitize_store(store)
64                if loadBundles:
65                        self._download_appImg_catalogue()
66                        store=self.load_appImg_catalog(store)
67                self.store=store
68                return(store)
69        #def load_store
70
71        def load_appImg_catalog(self,store):
72                iconPath='/usr/share/icons/hicolor/128x128'
73#               lliurex_dir="/home/juanma/svn/xenial/devtools/appImgdep11/dep11"
74                lliurex_dir="/tmp"
75                if os.path.exists(lliurex_dir):
76                        for lliurex in os.listdir(lliurex_dir):
77                                if lliurex.endswith('appdata.xml'):
78                                        storePath=Gio.File.new_for_path(lliurex_dir+'/'+lliurex)
79                                        self._debug("Adding file "+lliurex_dir+'/'+lliurex)
80                                        try:
81                                                store.from_file(storePath,iconPath,None)
82                                        except Exception as e:
83                                                self._debug("Couldn't add file "+lliurex+" to store")
84                                                self._debug("Reason: "+str(e))
85                return(store)
86
87        #def load_appImg_catalog(self)
88
89        def _parse_desktop(self,store): #DEPRECATED. Loads the apps from the available desktop files
90                desktopDir='/usr/share/applications'
91                applist=[]
92                for desktopFile in os.listdir(desktopDir):
93                        if desktopFile.endswith('desktop'):
94                                a=appstream.App()
95                                try:
96                                        a.parse_file(desktopDir+'/'+desktopFile,16)
97                                        a.set_priority(0)
98                                        for veto in a.get_vetos():
99                                                a.remove_veto(veto)
100                                        store.add_app(a)
101                                        self._debug("Adding app from desktop "+desktopFile)
102                                except:
103                                        pass
104                return(store)
105        #def _parse_desktop
106
107        def _sanitize_store(self,store):
108                applist=store.get_apps()
109                uniqDict={}
110                lliurexApps={}
111                zmdList=[]
112                for app in applist:
113                        #Zomandos get max priority
114                        if app.has_category('Zomando'):
115                                self._debug("Prioritize zmd "+str(app.get_id()))
116                                app.set_priority(400)
117                                lliurexApps.update({app.get_id_filename():app})
118                                id=str(app.get_id_filename()).replace('zero-lliurex-','')
119                                zmdList.append(id)
120                        #Prioritize Lliurex apps
121                        elif app.has_category('Lliurex'):
122                                self._debug("Prioritize app "+str(app.get_id()))
123                                app.set_priority(200)
124                                lliurexApps.update({app.get_id_filename():app})
125                        elif str(app.get_origin()).find('lliurex')>=0:
126                                self._debug("Prioritize app "+app.get_id())
127                                app.set_priority(100)
128                                lliurexApps.update({app.get_id_filename():app})
129                        else:
130                                app.set_priority(0)
131                                if app.get_id_filename() in lliurexApps.keys():
132#                                       app.set_merge_kind(appstream.AppMergeKind.APPEND)
133                                        self._debug("Mergin app "+str(app.get_id())+" as is in Lliurex")
134#                                       app.set_id(lliurexApps[app.get_id_filename()])
135                                        lliurexApps[app.get_id_filename()].subsume_full(app,appstream.AppSubsumeFlags.BOTH_WAYS)
136                                        store.remove_app(app)
137                        #Remove apps whitout pkgname
138                        if not app.get_pkgnames():
139                                store.remove_app(app)
140                        #Remove add-on apps (as are included in the main packages)
141                        if app.get_kind()==appstream.AppKind.ADDON:
142                                self._debug("Removed addon "+str(app.get_pkgnames()))
143                                store.remove_app(app)
144                        #Remove duplicated apps
145                        #Unlike gnome-store we'll try to compare the info of the package in order of discard only the "part-of" packages
146                        pkg=app.get_pkgname_default()
147                        if pkg in uniqDict.keys():
148                                fn=app.get_id_no_prefix()
149                                self._debug("Comparing "+fn+" with "+uniqDict[pkg]['fn'])
150                                if fn != uniqDict[pkg]['fn']:
151                                        if fn != pkg:
152                                                self._debug("Removed duplicated "+app.get_id())
153                                                store.remove_app(app)
154                                        else:
155                                                self._debug("Removed duplicated "+uniqDict[pkg]['app'].get_id())
156                                                uniqDict.update({pkg:{'fn':app.get_id_no_prefix(),'app':app}})
157                                                store.remove_app(uniqDict[pkg]['app'])
158                        elif pkg:
159#                               self._debug("Adding "+app.get_id_filename()+" to uniq dict")
160                                uniqDict.update({pkg:{'fn':app.get_id_filename(),'app':app}})
161                #Delete zomando-related debs
162                store=self._purge_zomandos(zmdList,store)
163                #Check the blacklist
164                store=self._apply_blacklist(store)
165                return (store)
166        #def _sanitize_store
167
168        def _purge_zomandos(self,zmdList,store):
169                for appId in zmdList:
170                        self._debug("Searching debs related to "+appId)
171                        purgeList=store.get_apps_by_id(appId)
172                        purgeList.extend(store.get_apps_by_id(appId+".desktop"))
173                        for purgeApp in purgeList:
174                                if purgeApp:
175                                        if not purgeApp.has_category('Zomando'):
176                                                self._debug("Removed related zomando app "+str(purgeApp.get_id()))
177                                                store.remove_app(purgeApp)
178                return(store)
179        #def _purge_zomandos
180
181        def _apply_blacklist(self,store):
182                try:
183                        flavour=subprocess.check_output(["lliurex-version","-f"]).rstrip()
184                        flavour=flavour.decode("utf-8")
185                        if flavour=='None':
186                                self._debug("Unknown flavour. Switching to desktop")
187                                flavour='desktop'
188                except (subprocess.CalledProcessError,FileNotFoundError) as e:
189                                self._debug("Running on a non Lliurex host")
190                                flavour='desktop'
191                try:
192                        if os.path.isfile('/usr/share/lliurex-store/files/blacklist.json'):
193                                blFile=open('/usr/share/lliurex-store/files/blacklist.json').read()
194                                blacklist=json.loads(blFile)
195                                blApps=[]
196                                if flavour in blacklist:
197                                        blApps=blacklist[flavour]
198                                if "all" in blacklist:
199                                        blApps.extend(blacklist["all"])
200                                blRegEx=[]
201                                for blApp in blApps:
202                                        self._debug("Blacklisted app: "+blApp)
203                                        resultRe=re.search('([^a-zA-Z0-9_-])',blApp)
204#                                       if blApp[-1]!='*':
205                                        if resultRe:
206                                                if blApp[0]=='*':
207                                                        blApp='.'+blApp
208                                                blRegEx.append("("+blApp+")")
209                                        else:
210                                                app=store.get_app_by_pkgname(blApp)
211                                                if app:
212                                                        self._debug("Removed "+str(app))
213                                                        store.remove_app(app)
214                                                else:
215                                                        self._debug("App "+blApp+" from blacklist not found in store. Assigned to RE blacklist")
216                                                        blRegEx.append("("+blApp+")")
217                                if blRegEx:
218                                        self._debug("Attempting to remove apps by RE match")
219                                        for app in store.get_apps():
220                                                for blApp in blRegEx:
221                                                        resultRe=re.search(blApp,app.get_id())
222                                                        if resultRe:
223#                                                       if blApp.lower() in app.get_id_filename().lower():
224                                                                store.remove_app(app)
225                                                                self._debug("Removed "+str(app.get_id()) +" as matches with "+blApp)
226                        else:
227                                self._debug('No blacklist to check')
228                except Exception as e:
229                        self._debug("Error processing blacklist: "+str(e))
230                finally:
231                        return(store)
232        #def _apply_blacklist
233
234        def _download_appImg_catalogue(self):
235                CURSOR_UP='\033[F'
236                ERASE_LINE='\033[K'
237                outfile='appimage.yml'
238                outdir="/usr/share/metainfo"
239                outdir="/tmp"
240                content=''
241                applist=[]
242                progressBar="#"
243                repolist=['https://dl.bintray.com/probono/AppImages']
244                self.descDict={}
245                for repo in repolist:
246                        self._debug(("Fetching repo %s")%(repo))
247                        print ("Download Progress: "+progressBar,end="\r")
248                        progressBar=progressBar+"#"
249                        applist=self._generate_applist(self._fetch_repo(repo))
250                        print ("Download Progress: "+progressBar,end="\r")
251                        self._debug("Processing info...")
252                        self._th_generate_xml_catalog(applist,outdir,progressBar)
253                        self._debug("Fetched repo "+repo)
254                self._debug("Setting status to 0")
255                self._set_status(0)
256                return("Repo fetched")
257
258        def _fetch_repo(self,repo):
259                with urllib.request.urlopen('https://dl.bintray.com/probono/AppImages') as f:
260                        content=(f.read().decode('utf-8'))
261                return(content)
262
263        def _generate_applist(self,content):
264                garbageList=[]
265                applist=[]
266                garbageList=content.split(' ')
267                for garbageLine in garbageList:
268                        if garbageLine.endswith('AppImage"'):
269                                app=garbageLine.replace('href=":','')
270                                applist.append(app.replace('"',''))
271                return(applist)
272
273        def _get_description(self,appName):
274                desc=''
275                self._debug("Getting description from 'https://bintray.com/probono/AppImages/'"+appName)
276                try:
277                        with urllib.request.urlopen('https://bintray.com/probono/AppImages/'+appName) as f:
278                                content=(f.read().decode('utf-8'))
279                                soup=BeautifulSoup(content,"html.parser")
280                                descDiv=soup.findAll('div', attrs={ "class" : "description-text"})
281                        if len(descDiv)>0:
282                                desc=descDiv[0].text
283                                desc=desc.replace(':','.')
284                                desc=desc.replace('&','&')
285                except:
286                        pass
287                return(desc)
288
289        def _th_generate_xml_catalog(self,applist,outdir,progressBar=''):
290                CURSOR_UP='\033[F'
291                ERASE_LINE='\033[K'
292                oldName=''
293                oldDesc=''
294                maxconnections = 10
295                semaphore = threading.BoundedSemaphore(value=maxconnections)
296                randomList = list(applist)
297                random.shuffle(randomList)
298                lenAppList=len(randomList)
299                inc=25/lenAppList
300                for app in randomList:
301                        th=threading.Thread(target=self._th_write_xml, args = (app,outdir,semaphore,inc))
302                        th.start()
303#               while (len(threading.enumerate())>3):
304#                       self._callback()
305                        time.sleep(0.5)
306                        progressBar=self.progress
307                        print ("Download Progress: "+str(int(progressBar))+"%",end="\r")
308
309        def _th_write_xml(self,app,outdir,semaphore,inc):
310                semaphore.acquire()
311                lock=threading.Lock()
312                self._debug("Generating "+app+" xml")
313                nameSplitted=app.split('-')
314                name=nameSplitted[0]
315                version=nameSplitted[1]
316                arch=nameSplitted[2]
317                f=open(outdir+'/'+name+"_"+version+".appdata.xml",'w')
318                f.write('<?xml version="1.0" encoding="UTF-8"?>'+"\n")
319                f.write("<components version=\"0.10\">\n")
320                f.write("<component  type=\"desktop-application\">\n")
321                f.write("  <id>"+app.lower()+"</id>\n")
322                f.write("  <pkgname>"+app+"</pkgname>\n")
323                f.write("  <name>"+name+"</name>\n")
324                f.write("  <summary>"+name+" AppImage Bundle</summary>\n")
325                f.write("  <metadata_license>CC0-1.0</metadata_license>\n")
326                f.write("  <provides><binary>"+app+"</binary></provides>\n")
327                f.write("  <releases>\n")
328                f.write("  <release version=\""+version+"\" timestamp=\"1408573857\"></release>\n")
329                f.write("  </releases>\n")
330                f.write("  <launchable type=\"desktop-id\">"+name+".desktop</launchable>\n")
331                with lock:
332                        if name in self.descDict.keys():
333                                description=self.descDict[name]
334                        else:
335                                description=self._get_description(name)
336                                self.descDict.update({name:description})
337                        self.progress=self.progress+inc
338                f.write("  <description><p>This is an AppImage bundle of app "+name+". It hasn't been tested by our developers and comes from a 3rd party dev team. Please use it carefully.</p><p>"+description+"</p></description>\n")
339                f.write("  <bundle type=\"appimage\">"+app+"</bundle>\n")
340                f.write("  <keywords>\n")
341                f.write("       <keyword>"+name+"</keyword>\n")
342                f.write("       <keyword>appimage</keyword>\n")
343                f.write("  </keywords>\n")
344                f.write("  <categories>\n")
345                f.write("       <category>AppImage</category>\n")
346                f.write("       <category>GTK</category>\n")
347                f.write("  </categories>\n")
348                f.write("<icon type=\"cached\">"+name+"_"+name+".png</icon>\n")
349                f.write("</component>\n")
350                f.write("</components>\n")
351                f.close()
352                semaphore.release()
Note: See TracBrowser for help on using the repository browser.