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

Last change on this file since 5187 was 5187, checked in by Juanma, 2 years ago

added appImage catalogue download

File size: 8.1 KB
Line 
1#The name of the main class must match the file name in lowercase
2import urllib
3import shutil
4import json
5import os
6from subprocess import call
7import sys
8import threading
9from bs4 import BeautifulSoup
10import random
11import time
12class appimagemanager:
13        def __init__(self):
14                self.example='This is an example plugin'
15                self.dbg=0
16                self.progress=0
17                #This dict defines wich package_type relies on what action
18                #action=example
19                #package='*' (in this case all packages)
20                self.pluginInfo={'install':'appimage','remove':'appimage','pkginfo':'appimage','loadCatalogue':'appimage'}
21                self.result={}
22                self.result['data']={}
23                self.result['status']={}
24                self.appImageFolder='/tmp'
25                self.pkginfo=self.appImageFolder+'/.bundles.json'
26                self.count=0
27        #def __init__
28
29        def set_debug(self,dbg='1'):
30                self.dbg=int(dbg)
31                self._debug ("Debug enabled")
32        #def set_debug
33
34        def _debug(self,msg=''):
35                if self.dbg==1:
36                        print ('DEBUG appImg: '+msg)
37        #def debug
38
39        def register(self):
40                return(self.pluginInfo)
41
42        def execute_action(self,action,applist=None):
43                self.progress=0
44                self.result['status']={'status':-1,'msg':''}
45                self.result['data']=''
46                dataList=[]
47                if action=='loadCatalogue':
48                        dataList.append(self._download_appImg_catalogue())
49                else:
50                        for appInfo in applist:
51                                if action=='install':
52                                        dataList.append(self._install_appImg(appInfo))
53                                if action=='remove':
54                                        dataList.append(self._remove_appImg(appInfo))
55                                if action=='pkginfo':
56                                        dataList.append(self._get_info(appInfo))
57                self.result['data']=list(dataList)
58                self.progress=100
59                return(self.result)
60
61        def _set_status(self,status,msg=''):
62                self.result['status']={'status':status,'msg':msg}
63        #def _set_status
64
65        def _callback(self,partialSize=0,totalSize=0):
66                limit=99
67                if partialSize!=0 and totalSize!=0:
68                        inc=round(partialSize/totalSize,2)*100
69                        self.progress=inc
70                else:
71                        inc=1
72                        margin=limit-self.progress
73                        inc=round(margin/limit,3)
74                        self.progress=(self.progress+inc)
75                if (self.progress>limit):
76                        self.progress=limit
77
78        def _install_appImg(self,appInfo):
79                appInfo=self._get_info(appInfo)
80                if appInfo['state']=='installed':
81                        self._set_status(4)
82                else:
83                        appImgUrl='https://dl.bintray.com/probono/AppImages/'+appInfo['appImage']
84                        self._debug("Downloading "+appImgUrl)
85                        dest_path='/tmp/'+appInfo['appImage']
86                        if appImgUrl:
87                                try:
88                                        with urllib.request.urlopen(appImgUrl) as response, open(dest_path, 'wb') as out_file:
89                                                bf=16*1024
90                                                acumbf=0
91                                                appSize=int(response.info()['Content-Length'])
92                                                while True:
93                                                        if acumbf>=appSize:
94                                                            break
95                                                        shutil.copyfileobj(response, out_file,bf)
96                                                        acumbf=acumbf+bf
97                                                        self._callback(acumbf,appSize)
98                                        st = os.stat(dest_path)
99                                        os.chmod(dest_path, st.st_mode | 0o111)
100                                        self._set_status(0)
101                                        self._write_info(appInfo,'install')
102                                except:
103                                        self._set_status(5)
104                        else:
105                                self._set_status(12)
106                return appInfo
107        #def _install_appImg
108
109        def _remove_appImg(self,appInfo):
110                appInfo=self._get_info(appInfo)
111                if appInfo['state']=='available':
112                        self._set_status(3)
113                else:
114                        try:
115                                call([self.appImageFolder+"/"+appInfo['appImage'], "--remove-appimage-desktop-integration"])
116                                os.remove(self.appImageFolder+"/"+appInfo['appImage'])
117                                self._write_info(appInfo,'remove')
118                                self._set_status(0)
119                        except:
120                                self._set_status(6)
121                return(appInfo)
122        #def _remove_appImg
123       
124        def _write_info(self,appInfo,action):
125                infoBundle={}
126                if os.path.isfile(self.pkginfo):
127                        try:
128                                infoFile=open(self.pkginfo).read()
129                                infoBundle=json.loads(infoFile)
130                        except:
131                                pass
132                if action=='remove':
133                        infoBundle.pop(appInfo['appImage'],None)
134                if action=='install':
135                        infoBundle.update({appInfo['appImage']:'installed'})
136                try:
137                        with open(self.pkginfo, 'w') as infoFile:
138                            infoBundle=json.dump(infoBundle,infoFile)
139                except:
140                        pass
141        #def _write_info
142
143        def _get_info(self,appInfo):
144                appInfo['state']='available'
145                if os.path.isfile(self.pkginfo):
146                        try:
147                                infoFile=open(self.pkginfo).read()
148                                infoBundle=json.loads(infoFile)
149                                if appInfo['appImage'] in infoBundle:
150                                        appInfo['state']=infoBundle[appInfo['appImage']]
151                        except:
152                                pass
153                self._set_status(0)
154                return(appInfo)
155        #def _get_info
156
157        def _download_appImg_catalogue(self):
158                outfile='appimage.yml'
159                outdir="/usr/share/metainfo"
160                outdir="/tmp"
161                content=''
162                applist=[]
163                repolist=['https://dl.bintray.com/probono/AppImages']
164                self.descDict={}
165                for repo in repolist:
166                        self._debug(("Fetching repo %s")%(repo))
167                        applist=self._generate_applist(self._fetch_repo(repo))
168                        self._debug("Processing info...")
169                        self._th_generate_xml_catalog(applist,outdir)
170                        self._debug("Fetched repo "+repo)
171                self._debug("Setting status to 0")
172                self._set_status(0)
173                return("Repo fetched")
174
175        def _fetch_repo(self,repo):
176            with urllib.request.urlopen('https://dl.bintray.com/probono/AppImages') as f:
177                content=(f.read().decode('utf-8'))
178            return(content)
179
180        def _generate_applist(self,content):
181                garbageList=[]
182                applist=[]
183                garbageList=content.split(' ')
184                for garbageLine in garbageList:
185                        if garbageLine.endswith('AppImage"'):
186                                app=garbageLine.replace('href=":','')
187                                applist.append(app.replace('"',''))
188                return(applist)
189
190        def _get_description(self,appName):
191                desc=''
192                self._debug("Getting description from 'https://bintray.com/probono/AppImages/'"+appName)
193                try:
194                        with urllib.request.urlopen('https://bintray.com/probono/AppImages/'+appName) as f:
195                                content=(f.read().decode('utf-8'))
196                                soup=BeautifulSoup(content,"html.parser")
197                                descDiv=soup.findAll('div', attrs={ "class" : "description-text"})
198                        if len(descDiv)>0:
199                                desc=descDiv[0].text
200                                desc=desc.replace(':','.')
201                                desc=desc.replace('&','&')
202                except:
203                        pass
204                return(desc)
205
206        def _th_generate_xml_catalog(self,applist,outdir):
207                oldName=''
208                oldDesc=''
209                maxconnections = 10
210                semaphore = threading.BoundedSemaphore(value=maxconnections)
211                randomList = list(applist)
212                random.shuffle(randomList)
213                lenAppList=len(randomList)
214                self.progress=25
215                for app in randomList:
216                        th=threading.Thread(target=self._th_write_xml, args = (app,outdir,semaphore))
217                        th.start()
218                        self._callback(1,len(randomList))
219                while (len(threading.enumerate())>3):
220                        self._callback()
221                        time.sleep(0.5)
222
223        def _th_write_xml(self,app,outdir,semaphore):
224            semaphore.acquire()
225            lock=threading.Lock()
226            self._debug("Generating "+app+" xml")
227            nameSplitted=app.split('-')
228            name=nameSplitted[0]
229            version=nameSplitted[1]
230            arch=nameSplitted[2]
231            f=open(outdir+'/'+name+"_"+version+".appdata.xml",'w')
232            f.write('<?xml version="1.0" encoding="UTF-8"?>'+"\n")
233            f.write("<components version=\"0.10\">\n")
234            f.write("<component  type=\"desktop-application\">\n")
235            f.write("  <id>"+app.lower()+"</id>\n")
236            f.write("  <pkgname>"+app+"</pkgname>\n")
237            f.write("  <name>"+name+"</name>\n")
238            f.write("  <summary>"+name+" AppImage Bundle</summary>\n")
239            f.write("  <metadata_license>CC0-1.0</metadata_license>\n")
240            f.write("  <provides><binary>"+app+"</binary></provides>\n")
241            f.write("  <releases>\n")
242            f.write("  <release version=\""+version+"\" timestamp=\"1408573857\"></release>\n")
243            f.write("  </releases>\n")
244            f.write("  <launchable type=\"desktop-id\">"+name+".desktop</launchable>\n")
245            with lock:
246                if name in self.descDict.keys():
247                    description=self.descDict[name]
248                else:
249                    description=self._get_description(name)
250                    self.descDict.update({name:description})
251            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")
252            f.write("  <bundle type=\"appimage\">"+app+"</bundle>\n")
253            f.write("  <keywords>\n")
254            f.write("    <keyword>"+name+"</keyword>\n")
255            f.write("    <keyword>appimage</keyword>\n")
256            f.write("  </keywords>\n")
257            f.write("  <categories>\n")
258            f.write("    <category>AppImage</category>\n")
259            f.write("    <category>GTK</category>\n")
260            f.write("  </categories>\n")
261            f.write("<icon type=\"cached\">"+name+"_"+name+".png</icon>\n")
262            f.write("</component>\n")
263            f.write("</components>\n")
264            f.close()
265            semaphore.release()
Note: See TracBrowser for help on using the repository browser.