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

Last change on this file since 7730 was 7730, checked in by Juanma, 17 months ago

Availabe snaps are loaded from available sections

File size: 20.7 KB
Line 
1#The name of the main class must match the file name in lowercase
2import re
3import urllib
4from urllib.request import Request
5from urllib.request import urlretrieve
6import shutil
7import json
8import os
9import sys
10import threading
11import queue
12import time
13import random
14import gi
15from gi.repository import Gio
16gi.require_version('AppStreamGlib', '1.0')
17from gi.repository import AppStreamGlib as appstream
18from bs4 import BeautifulSoup
19#from subprocess import call
20
21class appimagemanager:
22        def __init__(self):
23                self.dbg=False
24                self.progress=0
25                self.partial_progress=0
26                self.plugin_actions={'install':'appimage','remove':'appimage','pkginfo':'appimage','load':'appimage'}
27                self.result={}
28                self.result['data']={}
29                self.result['status']={}
30                self.cache_dir=os.getenv("HOME")+"/.cache/lliurex-store"
31                self.icons_dir=self.cache_dir+"/icons"
32                self.bundles_dir=self.cache_dir+"/xmls/appimage"
33                self.bundle_types=['appimg']
34                self.appimage_dir=os.getenv("HOME")+"/.local/bin"
35                #To get the description of an app we must go to a specific url defined in url_info.
36                #$(appname) we'll be replaced with the appname so the url matches the right one.
37                #If other site has other url naming convention it'll be mandatory to define it with the appropiate replacements
38                self.repos={'appimagehub':{'type':'json','url':'https://appimage.github.io/feed.json','url_info':''}}
39                #Appimges not stored in a repo must be listed in this file, providing the download url and the info url (if there's any)
40                self.external_appimages="/usr/share/lliurex-store/files/external_appimages.json"
41                self.locale=['ca_ES@valencia','ca@valencia','qcv','ca','ca_ES','es_ES','es','en_US','en_GB','en','C']
42                self.disabled=False
43                self.icon_cache_enabled=True
44                self.image_cache_enabled=True
45                self.apps_for_store=queue.Queue()
46        #def __init__
47
48        def set_debug(self,dbg=True):
49                self.dbg=dbg
50                self._debug ("Debug enabled")
51        #def set_debug
52
53        def _debug(self,msg=''):
54                if self.dbg:
55                        print ('DEBUG appimage: %s'%msg)
56        #def debug
57
58        def register(self):
59                return(self.plugin_actions)
60
61        def enable(self,state=False):
62                self.disable=state
63
64        def execute_action(self,action,applist=None,store=None):
65                if store:
66                        self.store=store
67                else:
68                        self.store=appstream.Store()
69                self.appimage_store=appstream.Store()
70                self.progress=0
71                self.result['status']={'status':-1,'msg':''}
72                self.result['data']=[]
73                self.threads=[]
74                dataList=[]
75                if self.disabled:
76                        self._set_status(9)
77                        self.result['data']=self.store
78                else:
79                        self._chk_installDir()
80                        if action=='load':
81                                self._load_appimage_store()
82                                #wait till threads end (if any)
83                                self._debug("Ending threads...")
84                                for th in threading.enumerate():
85                                        if th.is_alive():
86                                                try:
87                                                        th.join(5)
88                                                except:
89                                                        pass
90                                while not self.apps_for_store.empty():
91                                        app=self.apps_for_store.get()
92                                        self.store.add_app(app)
93                                self.result['data']=self.store
94                        else:
95                                for app_info in applist:
96                                        self.partial_progress=0
97                                        if action=='install':
98                                                dataList.append(self._install_appimage(app_info))
99                                        if action=='remove':
100                                                dataList.append(self._remove_appimage(app_info))
101                                        if action=='pkginfo':
102                                                dataList.append(self._get_info(app_info))
103                                        self.progress+=int(self.partial_progress/len(applist))-1
104                                self.result['data']=list(dataList)
105                self.progress=100
106                return(self.result)
107
108        def _set_status(self,status,msg=''):
109                self.result['status']={'status':status,'msg':msg}
110        #def _set_status
111
112        def _callback(self,partial_size=0,total_size=0):
113                limit=99
114                if partial_size!=0 and total_size!=0:
115                        inc=round(partial_size/total_size,2)*100
116                        self.progress=inc
117                else:
118                        inc=1
119                        margin=limit-self.progress
120                        inc=round(margin/limit,3)
121                        self.progress=(self.progress+inc)
122                if (self.progress>limit):
123                        self.progress=limit
124
125        def _chk_installDir(self):
126                msg_status=True
127                if not os.path.isdir(self.appimage_dir):
128                        try:
129                                os.makedirs(self.appimage_dir)
130                        except:
131                                msg_status=False
132                return msg_status                               
133
134        def _install_appimage(self,app_info):
135                app_info=self._get_info(app_info)
136                self._debug("Installing %s"%app_info)
137                if app_info['state']=='installed':
138                        self._set_status(4)
139                else:
140                        if 'appimage' in app_info['channel_releases'].keys():
141                                appimage_url=app_info['channel_releases']['appimage'][0]
142                        self._debug("Downloading "+appimage_url)
143                        dest_path=self.appimage_dir+'/'+app_info['package']
144                        if appimage_url:
145                                try:
146                                        req=Request(appimage_url, headers={'User-Agent':'Mozilla/5.0'})
147                                        with urllib.request.urlopen(req) as response, open(dest_path, 'wb') as out_file:
148                                                bf=16*1024
149                                                acumbf=0
150                                                app_size=int(response.info()['Content-Length'])
151                                                while True:
152                                                        if acumbf>=app_size:
153                                                            break
154                                                        shutil.copyfileobj(response, out_file,bf)
155                                                        acumbf=acumbf+bf
156                                                        self._callback(acumbf,app_size)
157                                        st = os.stat(dest_path)
158                                        os.chmod(dest_path, st.st_mode | 0o111)
159                                        self._set_status(0)
160                                except Exception as e:
161                                        print(e)
162                                        self._set_status(5)
163                        else:
164                                self._set_status(12)
165                return app_info
166        #def _install_appimage
167
168        def _remove_appimage(self,app_info):
169                self._debug("Removing "+app_info['package'])
170                if os.path.isfile(self.appimage_dir+'/'+app_info['package']):
171                        try:
172                                call([self.appimage_dir+"/"+app_info['package'], "--remove-appimage-desktop-integration"])
173                        except:
174                                pass
175                        try:
176                                os.remove(self.appimage_dir+"/"+app_info['package'])
177                                self._set_status(0)
178                        except:
179                                self._set_status(6)
180                return(app_info)
181        #def _remove_appimage
182
183        def _load_appimage_store(self,store=None):
184                self._get_bundles_catalogue()
185                self._get_external_catalogue()
186                if os.path.exists(self.bundles_dir):
187                        for bundle_type in self.bundle_types:
188                                self._debug("Loading %s catalog"%bundle_type)
189                                store=self._generic_file_load(self.bundles_dir+'/'+bundle_type,store)
190                return(store)
191        #def load_bundles_catalog(self)
192       
193        def _generic_file_load(self,target_path,store):
194                icon_path='/usr/share/icons/hicolor/128x128'
195                if not os.path.isdir(target_path):
196                        os.makedirs(target_path)
197                files=os.listdir(target_path)
198                for target_file in os.listdir(target_path):
199                        if target_file.endswith('appdata.xml'):
200                                store_path=Gio.File.new_for_path(target_path+'/'+target_file)
201                                self._debug("Adding file "+target_path+'/'+target_file)
202                                try:
203                                        store.from_file(store_path,icon_path,None)
204                                except Exception as e:
205                                        self._debug("Couldn't add file "+target_file+" to store")
206                                        self._debug("Reason: "+str(e))
207                return(store)
208        #def _generic_file_load
209
210        def _get_bundles_catalogue(self):
211                applist=[]
212                appdict={}
213                all_apps=[]
214                outdir=self.bundles_dir+'/appimg/'
215                #Load repos
216                for repo_name,repo_info in self.repos.items():
217                        if not os.path.isdir(self.bundles_dir):
218                                try:
219                                        os.makedirs(self.bundles_dir)
220                                except:
221                                        self._debug("appImage catalogue could not be fetched: Permission denied")
222                        self._debug("Fetching repo %s"%repo_info['url'])
223                        if repo_info['type']=='json':
224                                applist=self._process_appimage_json(self._fetch_repo(repo_info['url']),repo_name)
225
226                        self._debug("Fetched repo "+repo_info['url'])
227                        self._th_generate_xml_catalog(applist,outdir,repo_info['url_info'],repo_info['url'],repo_name)
228                        all_apps.extend(applist)
229                return True
230
231        def _get_external_catalogue(self):
232                applist=[]
233                all_apps=[]
234                outdir=self.bundles_dir+'/appimg/'
235                #Load external apps
236                for app_name,app_info in self._get_external_appimages().items():
237                        if os.path.isdir(self.bundles_dir):
238                                appinfo=self._init_appinfo()
239                                if 'name' in app_info.keys():
240                                        appinfo['name']=app_info['name']
241                                else:
242                                        appinfo['name']=app_info['url'].split('/')[-1]
243                                appinfo['package']=app_info['url'].split('/')[-1]
244                                if 'homepage' in app_info.keys():
245                                        appinfo['homepage']=app_info['homepage']
246                                else:
247                                        appinfo['homepage']='/'.join(app_info['url'].split('/')[0:-1])
248                                appinfo['installerUrl']=app_info['url']
249                                if 'description' in app_info.keys():
250                                        if type(app_info['description'])==type({}):
251                                                for lang in app_info['description']:
252                                                        appinfo['description'].update({lang:app_info['description'][lang]})
253                                        else:
254                                                appinfo['description'].update({"C":appimage['description']})
255                                if 'categories' in app_info.keys():
256                                        appinfo['categories']=app_info['categories']
257                                if 'keywords' in app_info.keys():
258                                        appinfo['keywords']=app_info['keywords']
259                                if 'version' in app_info.keys():
260                                        appinfo['reywords']=app_info['keywords']
261                                self._debug("Fetching external appimage %s"%app_info['url'])
262                                appinfo['bundle']='appimage'
263                                self._debug("External:\n%s\n-------"%appinfo)
264                                applist.append(appinfo)
265                        else:
266                                self._debug("External appImage could not be fetched: Permission denied")
267                self._th_generate_xml_catalog(applist,outdir,app_info['url_info'],app_info['url'],app_name)
268                self._debug("Fetched appimage "+app_info['url'])
269                all_apps.extend(applist)
270                self._debug("Removing old entries...")
271#               self._clean_bundle_catalogue(all_apps,outdir)
272                return(True)
273        #def _get_bundles_catalogue
274       
275        def _fetch_repo(self,repo):
276                req=Request(repo, headers={'User-Agent':'Mozilla/5.0'})
277                with urllib.request.urlopen(req) as f:
278                        content=(f.read().decode('utf-8'))
279               
280                return(content)
281        #def _fetch_repo
282       
283        def _get_external_appimages(self):
284                external_appimages={}
285                if os.path.isfile(self.external_appimages):
286                        try:
287                                with open(self.external_appimages) as appimages:
288                                        external_appimages=json.load(appimages)
289                        except:
290                                self._debug("Can't load %s"%self.external_appimages)
291                return external_appimages
292        #def _get_external_appimages
293       
294        def _process_appimage_json(self,data,repo_name):
295                applist=[]
296                json_data=json.loads(data)
297                if 'items' in json_data.keys():
298                        for appimage in json_data['items']:
299                                appinfo=self._th_process_appimage(appimage)
300                                if appinfo:
301                                        applist.append(appinfo)
302                return (applist)
303        #_process_appimage_json
304
305        def _th_process_appimage(self,appimage):
306                appinfo=None
307                releases=[]
308                if 'links' in appimage.keys():
309                        if appimage['links']:
310                                appinfo=self.load_json_appinfo(appimage)
311                return(appinfo)
312        #def _th_process_appimage
313
314        def load_json_appinfo(self,appimage):
315                self._debug(appimage)
316                appinfo=self._init_appinfo()
317                appinfo['name']=appimage['name']
318                appinfo['package']=appimage['name']
319                if 'license' in appimage.keys():
320                        appinfo['license']=appimage['license']
321                appinfo['summary']=''
322                if 'description' in appimage.keys():
323                        if type(appimage['description'])==type({}):
324                                for lang in appinfo['description'].keys():
325                                        appinfo['description'].update({lang:appimage['description'][lang]})
326                        else:
327                                appinfo['description']={"C":appimage['description']}
328                if 'categories' in appimage.keys():
329                        appinfo['categories']=appimage['categories']
330                if 'icon' in appimage.keys():
331                        appinfo['icon']=appimage['icon']
332                if 'icons' in appimage.keys():
333                        self._debug("Loading icon %s"%appimage['icons'])
334                        if appimage['icons']:
335                                self._debug("Loading icon %s"%appimage['icons'][0])
336                                appinfo['icon']=appimage['icons'][0]
337                if 'screenshots' in appimage.keys():
338                        appinfo['thumbnails']=appimage['screenshots']
339                if 'links' in appimage.keys():
340                        if appimage['links']:
341                                for link in appimage['links']:
342                                        if 'url' in link.keys() and link['type']=='Download':
343                                                appinfo['installerUrl']=link['url']
344                if 'authors' in appimage.keys():
345                        if appimage['authors']:
346                                for author in appimage['authors']:
347                                        if 'url' in author.keys():
348                                                self._debug("Author: %s"%author['url'])
349                                                appinfo['homepage']=author['url']
350                else:
351                        appinfo['homepage']='/'.join(appinfo['installerUrl'].split('/')[0:-1])
352                appinfo['bundle']=['appimage']
353                return appinfo
354        #def load_json_appinfo
355
356        def _th_generate_xml_catalog(self,applist,outdir,info_url,repo,repo_name):
357                maxconnections = 2
358                threads=[]
359                semaphore = threading.BoundedSemaphore(value=maxconnections)
360                random_applist = list(applist)
361                random.shuffle(random_applist)
362                for app in applist:
363                        th=threading.Thread(target=self._th_write_xml, args = (app,outdir,info_url,repo,repo_name,semaphore))
364                        threads.append(th)
365                        th.start()
366        #def _th_generate_xml_catalog
367
368        def     _th_write_xml(self,appinfo,outdir,info_url,repo,repo_name,semaphore):
369                semaphore.acquire()
370                self._add_appimage(appinfo)
371                semaphore.release()
372        #def _th_write_xml
373
374        def _add_appimage(self,appinfo):
375                #Search in local store for the app
376                sw_new=False
377                app=appstream.App()
378                app_orig=self.store.get_app_by_pkgname(appinfo['name'].lower())
379                if not app_orig:
380                        app_orig=self.store.get_app_by_id(appinfo['name'].lower()+".desktop")
381                if app_orig:
382                        self._debug("Extending app %s"%appinfo['package'])
383                        app=self._copy_app_from_appstream(app_orig,app)
384                else:
385                        self._debug("Generating new %s"%appinfo['package'])
386                if appinfo['name'].endswith('.appimage'):
387                        app.set_id("appimagehub.%s"%appinfo['name'].lower())
388                        app.set_name("C",appinfo['name'])
389                else:
390                        app.set_id("appimagehub.%s"%appinfo['name'].lower()+'.appimage')
391                        app.set_name("C",appinfo['name']+".appimage")
392                if appinfo['package'].endswith('.appimage'):
393                        app.add_pkgname(appinfo['package'].lower())
394                else:
395                        app.add_pkgname(appinfo['package'].lower()+".appimage")
396                app.set_id_kind=appstream.IdKind.DESKTOP
397                sw_new=True
398
399                icon=appstream.Icon()
400                screenshot=appstream.Screenshot()
401                if appinfo['license']:
402                        app.set_project_license(appinfo['license'])
403                bundle=appstream.Bundle()
404                bundle.set_kind(bundle.kind_from_string('APPIMAGE'))
405                if appinfo['package'].endswith('.appimage'):
406                        bundle.set_id(appinfo['package'])
407                else:
408                        bundle.set_id(appinfo['package']+'.appimage')
409                app.add_bundle(bundle)
410                if 'keywords' in appinfo.keys():
411                        for keyword in appinfo['keywords']:
412                                app.add_keyword("C",keyword)
413                        if 'appimage' not in appinfo['keywords']:
414                                app.add_keyword("C","appimage")
415                else:
416                        app.add_keyword("C","appimage")
417                app.add_url(appstream.UrlKind.UNKNOWN,appinfo['installerUrl'])
418                app.add_url(appstream.UrlKind.HOMEPAGE,appinfo['homepage'])
419                if sw_new:
420                        app.add_keyword("C",appinfo['package'])
421                        if not appinfo['name'].endswith('.appimage'):
422                                app.set_name("C",appinfo['name']+".appimage")
423                        if appinfo['description']:
424                                for lang,desc in appinfo['description'].items():
425                                        description="This is an AppImage bundle of app %s. It hasn't been tested by our developers and comes from a 3rd party dev team. Please use it carefully.\n%s"%(appinfo['name'],desc)
426                                        summary=' '.join(list(description.split(' ')[:8]))
427                                        app.set_description(lang,description)
428                                        app.set_comment(lang,summary)
429                        else:
430                                description="This is an AppImage bundle of app %s. It hasn't been tested by our developers and comes from a 3rd party dev team. Please use it carefully"%(appinfo['name'])
431                                summary=' '.join(list(description.split(' ')[:8]))
432                                app.set_description("C",description)
433                                app.set_comment("C",summary)
434
435                        if 'categories' in appinfo.keys():
436                                for category in appinfo['categories']:
437                                        app.add_category(category)
438                                if 'appimage' not in appinfo['categories']:
439                                        app.add_category("appimage")
440                        else:
441                                app.add_category("appimage")
442                if appinfo['icon']:
443                        if self.icon_cache_enabled:
444                                icon.set_kind(appstream.IconKind.LOCAL)
445                                icon.set_name(self._download_file(appinfo['icon'],appinfo['name'],self.icons_dir))
446                        else:
447                                icon.set_kind(appstream.IconKind.REMOTE)
448                                icon.set_name(pkg.get_icon())
449                                icon.set_url(pkg.get_icon())
450                        app.add_icon(icon)
451                if appinfo['thumbnails']:
452                        img=appstream.Image()
453                        if not appinfo['thumbnails'][0].startswith('http'):
454                                        appinfo['screenshot']=appinfo['thumbnails'][0]
455                                        appinfo['screenshot']="https://appimage.github.io/database/%s"%appinfo['screenshot']
456                        img.set_kind(appstream.ImageKind.SOURCE)
457                        img.set_url(appinfo['screenshot'])
458                        screenshot.add_image(img)
459                        app.add_screenshot(screenshot)
460                #Adds the app to the store
461                self.apps_for_store.put(app)
462                if not os.path.isfile(self.bundles_dir+'/'+app.get_id_filename()):
463                        gioFile=Gio.File.new_for_path('%s/%s.xml'%(self.bundles_dir,app.get_id_filename()))
464                        app.to_file(gioFile)
465        #def _add_appimage
466
467        def _copy_app_from_appstream(self,app_orig,app):
468                app.set_id("appimage."+app_orig.get_id())
469                for category in app_orig.get_categories():
470                        app.add_category(category)
471                for screenshot in app_orig.get_screenshots():
472                        app.add_screenshot(screenshot)
473                for icon in app_orig.get_icons():
474                        app.add_icon(icon)
475                for localeItem in self.locale:
476                        if app_orig.get_name(localeItem):
477                                app.set_name(localeItem,app_orig.get_name(localeItem)+".appimage")
478                        if app_orig.get_description(localeItem):
479                                app.set_description(localeItem,app_orig.get_description(localeItem))
480                        if app_orig.get_comment(localeItem):
481                                app.set_comment(localeItem,app_orig.get_comment(localeItem))
482                app.set_origin(app_orig.get_origin())
483                return app
484
485        def _clean_bundle_catalogue(self,applist,outdir):
486                xml_files_list=[]
487                applist=[item.lower() for item in applist]
488                for xml_file in os.listdir(outdir):
489                        if xml_file.endswith('appdata.xml'):
490                                xml_files_list.append(xml_file.lower().replace('appdata.xml','appimage'))
491       
492                if xml_files_list:
493                        xml_discard_list=list(set(xml_files_list).difference(applist))
494                        for discarded_file in xml_discard_list:
495                                os.remove(outdir+'/'+discarded_file.replace('appimage','appdata.xml'))
496        #def _clean_bunlde_catalogue
497
498        def _download_file(self,url,app_name,dest_dir):
499                target_file=dest_dir+'/'+app_name+".png"
500                if not url.startswith('http'):
501                        url="https://appimage.github.io/database/%s"%url
502                if not os.path.isfile(target_file):
503                        if not os.path.isfile(target_file):
504                                self._debug("Downloading %s to %s"%(url,target_file))
505                                try:
506                                        with urllib.request.urlopen(url) as response, open(target_file, 'wb') as out_file:
507                                                bf=16*1024
508                                                acumbf=0
509                                                file_size=int(response.info()['Content-Length'])
510                                                while True:
511                                                        if acumbf>=file_size:
512                                                            break
513                                                        shutil.copyfileobj(response, out_file,bf)
514                                                        acumbf=acumbf+bf
515                                        st = os.stat(target_file)
516                                except Exception as e:
517                                        self._debug("Unable to download %s"%url)
518                                        self._debug("Reason: %s"%e)
519                                        target_file=''
520                return(target_file)
521        #def _download_file
522       
523        def _chk_bundle_dir(self,outdir):
524                msg_status=True
525                if not os.path.isdir(outdir):
526                        try:
527                                os.makedirs(outdir)
528                        except Exception as e:
529                                msg_status=False
530                                print(e)
531                return(os.access(outdir,os.W_OK|os.R_OK|os.X_OK|os.F_OK))
532        #def _chk_bundle_dir
533       
534        def _init_appinfo(self):
535                appInfo={'appstream_id':'',\
536                'id':'',\
537                'name':'',\
538                'version':'',\
539                'channel_releases':{},\
540                'component':'',\
541                'package':'',\
542                'license':'',\
543                'summary':'',\
544                'description':{},\
545                'categories':[],\
546                'icon':'',\
547                'screenshot':'',\
548                'thumbnails':[],\
549                'video':'',\
550                'homepage':'',\
551                'installerUrl':'',\
552                'state':'',\
553                'depends':'',\
554                'kudos':'',\
555                'suggests':'',\
556                'extraInfo':'',\
557                'size':'',\
558                'bundle':'',\
559                'updatable':'',\
560                }
561                return(appInfo)
562        #def _init_appinfo
563       
564        def _get_info(self,app_info):
565                if app_info['installerUrl']:
566                        self._debug("installer: %s"%app_info['installerUrl'])
567                        app_info['channel_releases']={'appimage':[]}
568                        app_info['channel_releases']['appimage']=self._get_releases(app_info)
569                app_info['state']='available'
570                if os.path.isfile(self.appimage_dir+'/'+app_info['package']):
571                        app_info['state']='installed'
572                #Get size
573                if 'appimage' in app_info['channel_releases'].keys():
574                        if app_info['channel_releases']['appimage'][0]:
575                                appimage_url=app_info['channel_releases']['appimage'][0]
576                                dest_path=self.appimage_dir+'/'+app_info['package']
577                                if appimage_url:
578                                        try:
579                                                with urllib.request.urlopen(appimage_url) as response:
580                                                        app_info['size']=(response.info()['Content-Length'])
581                                        except:
582                                                app_info['size']=0
583                        else:
584                                app_info['size']=0
585                        #Version (unaccurate aprox)
586                        app_info['version']=app_info['channel_releases']['appimage'][0].split('/')[-2]
587
588                self._set_status(0)
589                self.partial_progress=100
590                return(app_info)
591        #def _get_info
592
593        def _get_releases(self,app_info):
594                releases=[]
595                releases_page=''
596                self._debug("Info url: %s"%app_info['installerUrl'])
597                url_source=""
598                try:
599                        if 'github' in app_info['installerUrl']:
600                                releases_page="https://github.com"
601                        if 'gitlab' in app_info['installerUrl']:
602                                releases_page="https://gitlab.com"
603                        if 'opensuse' in app_info['installerUrl'].lower():
604                                releases_page=""
605                                url_source="opensuse"
606#                               app_info['installerUrl']=app_info['installerUrl']+"/download"
607
608                        if url_source or releases_page:
609                                with urllib.request.urlopen(app_info['installerUrl']) as f:
610                                        content=(f.read().decode('utf-8'))
611                                        soup=BeautifulSoup(content,"html.parser")
612                                        package_a=soup.findAll('a', attrs={ "href" : re.compile(r'.*\.[aA]pp[iI]mage$')})
613
614                                        for package_data in package_a:
615                                                if url_source=="opensuse":
616                                                        package_name=package_data.findAll('a', attrs={"class" : "mirrorbrain-btn"})
617                                                else:
618                                                        package_name=package_data.findAll('strong', attrs={ "class" : "pl-1"})
619                                                package_link=package_data['href']
620                                                if releases_page or url_source:
621                                                        package_link=releases_page+package_link
622                                                        releases.append(package_link)
623                                                        self._debug("Link: %s"%package_link)
624                        else:
625                                releases=[app_info['installerUrl']]
626                except Exception as e:
627                        print(e)
628                self._debug(releases)
629                return releases
630        #def _get_releases
631       
Note: See TracBrowser for help on using the repository browser.