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

Last change on this file since 8139 was 8139, checked in by Juanma, 15 months ago

snapManager with threads

File size: 11.9 KB
Line 
1#The name of the main class must match the file name in lowercase
2import os
3import urllib
4import shutil
5import gi
6from gi.repository import Gio
7gi.require_version ('Snapd', '1')
8from gi.repository import Snapd
9gi.require_version('AppStreamGlib', '1.0')
10from gi.repository import AppStreamGlib as appstream
11import time
12import html
13import threading
14#Needed for async find method, perhaps only on xenial
15wrap=Gio.SimpleAsyncResult()
16class snapmanager:
17       
18        def __init__(self):
19                self.dbg=False
20                self.progress=0
21                self.partial_progress=0
22                self.plugin_actions={'install':'snap','remove':'snap','pkginfo':'snap','load':'snap'}
23                self.cache_dir=os.getenv("HOME")+"/.cache/lliurex-store"
24                self.cache_xmls=self.cache_dir+'/xmls/snap'
25                self.cache_last_update=self.cache_xmls+'/.snap.lu'
26                self.icons_folder=self.cache_dir+"/icons"
27                self.images_folder=self.cache_dir+"/images"
28                self.result={}
29                self.result['data']={}
30                self.result['status']={}
31                self.disabled=False
32                self.icon_cache_enabled=True
33                self.image_cache_enabled=True
34                self.cli_mode=False
35                if not os.path.isdir(self.icons_folder):
36                        try:
37                                os.makedirs(self.icons_folder)
38                        except:
39                                self.icon_cache_enabled=False
40                                self._debug("Icon cache disabled")
41                if not os.path.isdir(self.images_folder):
42                        try:
43                                os.makedirs(self.images_folder)
44                        except:
45                                self.image_cache_enabled=False
46                                self._debug("Image cache disabled")
47        #def __init__
48
49        def set_debug(self,dbg=True):
50                self.dbg=dbg
51                self._debug ("Debug enabled")
52        #def set_debug
53
54        def _debug(self,msg=''):
55                if self.dbg:
56                        print ('DEBUG snap: %s'%msg)
57        #def debug
58
59        def register(self):
60                return(self.plugin_actions)
61
62        def enable(self,state=False):
63                self.disabled=state
64
65        def execute_action(self,action,applist=None,store=None,results=0):
66                if store:
67                        self.store=store
68                else:
69                        self.store=appstream.Store()
70                self.progress=0
71                self.result['status']={'status':-1,'msg':''}
72                self.result['data']=''
73               
74
75                if self.disabled==True:
76                        self._set_status(9)
77                        self.result['data']=self.store
78                else:
79                        self._check_dirs()
80                        self.snap_client=Snapd.Client()
81                        self.snap_client.connect_sync(None)
82                        dataList=[]
83                        if action=='load':
84                                self.result['data']=self._load_snap_store(self.store)
85                        else:
86                                for app_info in applist:
87                                        self.partial_progress=0
88                                        if action=='install':
89                                                dataList.append(self._install_snap(app_info))
90                                        if action=='remove':
91                                                dataList.append(self._remove_snap(app_info))
92                                        if action=='pkginfo':
93                                                dataList.append(self._get_info(app_info))
94                                        self.progress+=round(self.partial_progress/len(applist),1)
95                                        if self.progress>98:
96                                                self.progress=98
97                                self.result['data']=list(dataList)
98                self.progress=100
99                return(self.result)
100
101        def _set_status(self,status,msg=''):
102                self.result['status']={'status':status,'msg':msg}
103        #def _set_status
104
105        def _callback(self,client,change, _,user_data):
106            # Interate over tasks to determine the aggregate tasks for completion.
107            total = 0
108            done = 0
109            for task in change.get_tasks():
110                total += task.get_progress_total()
111                done += task.get_progress_done()
112            self.progress = round((done/total)*100)
113        #def _callback
114
115        def _check_dirs(self):
116                if not os.path.isdir(self.cache_xmls):
117                        os.makedirs(self.cache_xmls)
118
119        def _load_snap_store(self,store):
120                pkgs=[]
121                #Look if cache is up-to-date
122                if os.path.isfile(self.cache_last_update):
123                        epoch_time=time.time()
124                        fcache=open(self.cache_last_update,'r')
125                        fcache_update=fcache.read()
126                        if not fcache_update:
127                                fcache_update=0
128                        if int(epoch_time)-int(fcache_update)<86400:
129                                if not os.listdir(os.path.dirname(self.cache_xmls)):
130                                        self._debug("Loading snap from cache")
131                                        store=self._load_from_cache(store)
132                                        return store
133
134                fcache=open(self.cache_last_update,'w')
135                fcache.write(str(int(time.time())))
136                if self.cli_mode:
137#                       pkgs=self._search_snap("*")
138                        pkgs=self._load_sections()
139                else:
140                        pkgs=self._load_sections()
141#                       pkgs=self._search_snap_async("*")
142                self._set_status(1)
143                for pkg in pkgs:
144                        maxconnections = 10
145                        threads=[]
146                        semaphore = threading.BoundedSemaphore(value=maxconnections)
147                        th=threading.Thread(target=self._th_load_store, args = (store,pkg,semaphore))
148                        threads.append(th)
149                        th.start()
150                return(store)
151
152        def _th_load_store(self,store,pkg,semaphore):
153                semaphore.acquire()
154                app=self.store.get_app_by_pkgname(pkg.get_name())
155                if not app:
156                        self._debug("Searching for %s"%pkg.get_name())
157                        app=self.store.get_app_by_id(pkg.get_name().lower()+".desktop")
158                        if app:
159                                bundle=appstream.Bundle()
160                                bundle.set_kind(bundle.kind_from_string('SNAP'))
161                                bundle.set_id(pkg.get_name()+'.snap')
162                                app.add_bundle(bundle)
163                                app.add_category("Snap")
164                                store.add_app(self._generate_appstream_app_from_snap(pkg))
165                        else:
166                                store.add_app(self._generate_appstream_app_from_snap(pkg))
167                semaphore.release()
168
169        def _load_from_cache(self,store):
170                for target_file in os.listdir(self.cache_xmls):
171                        if target_file.endswith('.xml'):
172                                store_file=Gio.File.new_for_path(self.cache_xmls+'/'+target_file)
173                                self._debug("Adding file %s/%s"%(self.cache_xmls,target_file))
174                                try:
175                                        store.from_file(store_file,None,None)
176                                except Exception as e:
177                                        self._debug("Couldn't add file %s to store"%target_file)
178                                        self._debug("Reason: %s"%e)
179                return store   
180
181        def _generate_appstream_app_from_snap(self,pkg):
182                bundle=appstream.Bundle()
183                app=appstream.App()
184                icon=appstream.Icon()
185                screenshot=appstream.Screenshot()
186#               bundle.set_kind(appstream.BundleKind.SNAP)
187                bundle.set_kind(bundle.kind_from_string('SNAP'))
188                bundle.set_id(pkg.get_name()+'.snap')
189                app.add_bundle(bundle)
190                app.set_name("C",pkg.get_name()+'.snap')
191                app.add_pkgname(pkg.get_name()+'.snap')
192                app.add_category("Snap")
193                release=appstream.Release()
194                release.set_version(pkg.get_version())
195                app.add_release(release)
196                app.set_id("io.snapcraft.%s"%pkg.get_name()+'.snap')
197                app.set_id_kind=appstream.IdKind.DESKTOP
198                app.set_metadata_license("CC0-1.0")
199                description="This is an Snap bundle. It hasn't been tested by our developers and comes from a 3rd party dev team. Please use it carefully."
200                pkg_description=pkg.get_description()
201                pkg_description=html.escape(pkg_description,quote=True)
202                pkg_description=pkg_description.replace("<","&gt;")
203                app.set_description("C","<p>%s</p><p>%s</p>"%(description,pkg_description))
204                app.set_comment("C",pkg.get_summary().rstrip('.'))
205
206                app.add_keyword("C",pkg.get_name())
207                for word in pkg.get_summary().split(' '):
208                        if len(word)>3:
209                                app.add_keyword("C",word)
210
211                if pkg.get_icon():
212                        if self.icon_cache_enabled:
213                                icon.set_kind(appstream.IconKind.LOCAL)
214                                       
215                                icon.set_name(self._download_file(pkg.get_icon(),pkg.get_name(),self.icons_folder))
216                        else:
217                                icon.set_kind(appstream.IconKind.REMOTE)
218                                icon.set_name(pkg.get_icon())
219                                icon.set_url(pkg.get_icon())
220                        app.add_icon(icon)
221
222                if pkg.get_license():
223                        app.set_project_license(pkg.get_license())
224
225                if pkg.get_screenshots():
226                        for snap_screen in pkg.get_screenshots():
227                                img=appstream.Image()
228                                img.set_kind(appstream.ImageKind.SOURCE)
229                                img.set_url(snap_screen.get_url())
230                                break
231                        screenshot.add_image(img)
232                        app.add_screenshot(screenshot)
233
234                if not os.path.isfile(self.cache_xmls+'/'+app.get_id_filename()):
235                        xml_path='%s/%s.xml'%(self.cache_xmls,app.get_id_filename())
236                        gioFile=Gio.File.new_for_path(xml_path)
237                        app.to_file(gioFile)
238                        #Fix some things in app_file...
239                        xml_file=open(xml_path,'r')
240                        xml_data=xml_file.readlines()
241                        xml_file.close()
242                        count=0
243                        xml_data[0]=xml_data[0]+"<components>\n"
244                        xml_data[-1]=xml_data[-1]+"\n"+"</components>"
245                        xml_file=open(xml_path,'w')
246                        xml_file.writelines(xml_data)
247                        xml_file.close()
248                return(app)
249
250        def _search_cb(self,obj,request,*args):
251                global wrap
252                wrap=request
253
254        def _load_sections(self):
255                sections=self.snap_client.get_sections_sync()
256                stable_pkgs=[]
257                for section in sections:
258                        apps=self.snap_client.find_section_sync(Snapd.FindFlags.MATCH_NAME,section,None)
259                        for pkg in apps:
260                                stable_pkgs.append(pkg)
261                return(stable_pkgs)
262
263        def _search_snap_async(self,tokens):
264                self._debug("Async Searching %s"%tokens)
265                pkgs=None
266                global wrap
267                self.snap_client.find_async(Snapd.FindFlags.MATCH_NAME,
268                                                        tokens,
269                                                        None,
270                                                        self._search_cb,(None,),None)
271                while 'Snapd' not in str(type(wrap)):
272                        time.sleep(0.1)
273#               snaps,curr=self.snap_client.find_finish(wrap)
274                snaps=self.snap_client.find_finish(wrap)
275                if type(snaps)!=type([]):
276                        pkgs=[snaps]
277                else:
278                        pkgs=snaps
279                stable_pkgs=[]
280                for pkg in pkgs:
281                        if pkg.get_channel()=='stable':
282                                stable_pkgs.append(pkg)
283                return(stable_pkgs)
284
285        def _search_snap(self,tokens):
286                self._debug("Searching %s"%tokens)
287                pkg=None
288                pkgs=None
289                try:
290#                       pkgs,curr=self.snap_client.find_sync(Snapd.FindFlags.MATCH_NAME,tokens,None)
291                        pkgs=self.snap_client.find_sync(Snapd.FindFlags.MATCH_NAME,tokens,None)
292                except Exception as e:
293                        print("ERR: %s"%e)
294                        self._set_status(1)
295                stable_pkgs=[]
296                for pkg in pkgs:
297                        if pkg.get_channel()=='stable':
298                                stable_pkgs.append(pkg)
299                        else:
300                                self._debug(pkg.get_channel())
301                self._debug("Done")
302                return(stable_pkgs)
303        #def _search_snap
304
305        def _download_file(self,url,app_name,dest_dir):
306#               target_file=self.icons_folder+'/'+app_name+".png"
307                target_file=dest_dir+'/'+app_name+".png"
308                if not os.path.isfile(target_file):
309#                       shutil.copy("/usr/share/icons/hicolor/128x128/apps/lliurex-store.png",target_file)
310#                       if not os.fork():
311                        if not os.path.isfile(target_file):
312                                self._debug("Downloading %s to %s"%(url,target_file))
313                                try:
314                                        with urllib.request.urlopen(url) as response, open(target_file, 'wb') as out_file:
315                                                bf=16*1024
316                                                acumbf=0
317                                                file_size=int(response.info()['Content-Length'])
318                                                while True:
319                                                        if acumbf>=file_size:
320                                                            break
321                                                        shutil.copyfileobj(response, out_file,bf)
322                                                        acumbf=acumbf+bf
323                                        st = os.stat(target_file)
324                                except Exception as e:
325                                        self._debug("Unable to download %s"%url)
326                                        self._debug("Reason: %s"%e)
327                                        target_file=''
328#                               os._exit(0)
329                return(target_file)
330        #def _download_file
331
332
333        def _get_info(self,app_info):
334                #switch to launch async method when running under a gui
335                #For an unknown reason request will block when sync mode under a gui and async blocks when on cli (really funny)
336                self._debug("Getting info for %s"%app_info)
337                pkg=None
338                try:
339                        pkg=self.snap_client.list_one_sync(app_info['package'].replace('.snap',''))
340                        app_info['state']='installed'
341                        pkgs=[pkg]
342                except:
343                        app_info['state']='available'
344                        if self.cli_mode:
345                                pkgs=self._search_snap(app_info['package'].replace('.snap',''))
346                        else:
347                                pkgs=self._search_snap_async(app_info['package'].replace('.snap',''))
348                        self._debug("Getting extended info for %s %s"%(app_info['name'],pkgs))
349                if type(pkgs)==type([]):
350                        for pkg in pkgs:
351                                self._debug("Getting extended info for %s"%app_info['name'])
352                                if pkg.get_download_size():
353                                        app_info['size']=str(pkg.get_download_size())
354                                else:
355                                        app_info['size']=str(pkg.get_installed_size())
356                                break
357                else:
358                        app_info['size']='0'
359                self._debug("Info for %s"%app_info)
360                self.partial_progress=100
361                return(app_info)
362        #def _get_info
363
364        def _install_snap(self,app_info):
365                self._debug("Installing %s"%app_info['name'])
366                if app_info['state']=='installed':
367                        self._set_status(4)
368                else:
369                        try:
370                                self.snap_client.install_sync(app_info['name'].replace('.snap',''),
371                        None, # channel
372                        self._callback, (None,),
373                        None) # cancellable
374                                app_info['state']='installed'
375                                self._set_status(0)
376                        except Exception as e:
377                                print("Install error %s"%e)
378                                self._set_status(5)
379
380                self._debug("Installed %s"%app_info)
381                return app_info
382        #def _install_snap
383
384        def _remove_snap(self,app_info):
385                if app_info['state']=='available':
386                        self._set_status(3)
387                else:
388                        try:
389                                self.snap_client.remove_sync(app_info['name'].replace('.snap',''),
390                       self._callback, (None,),
391                                                None) # cancellable
392                                app_info['state']='available'
393                                self._set_status(0)
394                        except Exception as e:
395                                print("Remove error %s"%e)
396                                self._set_status(6)
397                return app_info
398        #def _remove_snap
Note: See TracBrowser for help on using the repository browser.