1 | #The name of the main class must match the file name in lowercase |
---|
2 | #Init: Could accept parameters if we declare them in storeManager's threads dict |
---|
3 | import os |
---|
4 | #import lliurexstore.plugins.debManager |
---|
5 | import lliurexstore.plugins.appImageManager |
---|
6 | import lliurexstore.plugins.snapManager |
---|
7 | import lliurexstore.plugins.infoManager |
---|
8 | import lliurexstore.plugins.zmdManager |
---|
9 | import random |
---|
10 | import sqlite3 |
---|
11 | import threading |
---|
12 | import time |
---|
13 | import psutil |
---|
14 | from queue import Queue as pool |
---|
15 | class cachemanager: |
---|
16 | def __init__(self): |
---|
17 | self.dbg=False |
---|
18 | self.progress=0 |
---|
19 | self.plugin_actions={'cache':'*','pkginfo':'*'} |
---|
20 | self.cli_mode=False |
---|
21 | self.autostart_actions={'cache':'store=self.store'} #List with actions that storeManager must launch automatically. The parameter list refers to 'stringfieds' storeManager members !! |
---|
22 | # self.postaction_actions={'install':'app','remove':'app'} |
---|
23 | self.requires={'cache':'store=self.store'} |
---|
24 | self.cache_dir=os.getenv("HOME")+"/.cache/lliurex-store" |
---|
25 | self.cache_db=self.cache_dir+'/data/info.db' |
---|
26 | self.db_cursor='' |
---|
27 | self.db='' |
---|
28 | self.processed=0 |
---|
29 | self.total=0 |
---|
30 | self.result={} |
---|
31 | self.disabled=None |
---|
32 | self.infomanager=lliurexstore.plugins.infoManager.infomanager() |
---|
33 | self.debmanager=lliurexstore.plugins.debManager.debmanager() |
---|
34 | self.appimagemanager=lliurexstore.plugins.appImageManager.appimagemanager() |
---|
35 | self.snapmanager=lliurexstore.plugins.snapManager.snapmanager() |
---|
36 | self.zmdmanager=lliurexstore.plugins.zmdManager.zmdmanager() |
---|
37 | self.insert_count=0 |
---|
38 | self.data_pool=pool() |
---|
39 | self.apps_per_cycle=5 #Apps that will be processed per cycle |
---|
40 | self.cycles_for_commit=1 #Processed cycles needed for a commit |
---|
41 | self.sleep_between_cycles=10 #Time the cache plugin will sleep between a process cycle and the next |
---|
42 | self.sleep_between_apps=0.1 #Time the cache plugin will sleep between process one app and the next |
---|
43 | #def __init__ |
---|
44 | |
---|
45 | def set_debug(self,dbg=True): |
---|
46 | self.dbg=dbg |
---|
47 | #self._debug ("Debug enabled") |
---|
48 | #def set_debug |
---|
49 | |
---|
50 | def _debug(self,msg=''): |
---|
51 | if self.dbg: |
---|
52 | print ('DEBUG Cache: %s'%msg) |
---|
53 | #def debug |
---|
54 | |
---|
55 | def register(self): |
---|
56 | return(self.plugin_actions) |
---|
57 | #def register |
---|
58 | |
---|
59 | def execute_action(self,action,store=None,applist=None): |
---|
60 | self.progress=0 |
---|
61 | self.store=store |
---|
62 | self.result['status']={'status':-1,'msg':''} |
---|
63 | self.result['data']='' |
---|
64 | if self.disabled: |
---|
65 | self._set_status(9) |
---|
66 | else: |
---|
67 | self._set_db() |
---|
68 | if action=='cache': |
---|
69 | self._build_cache() |
---|
70 | if action=='install': |
---|
71 | self._update(applist,'installed') |
---|
72 | if action=='remove': |
---|
73 | self._update(applist,'available') |
---|
74 | if action=='pkginfo': |
---|
75 | dataList=[] |
---|
76 | for appinfo in applist: |
---|
77 | #self._debug("Looking for %s"%appinfo['package']) |
---|
78 | dataList.append(self._get_info(appinfo)) |
---|
79 | self.result['data']=list(dataList) |
---|
80 | self.progress=100 #When all actions are launched we must assure that progress=100. |
---|
81 | self.db.close() |
---|
82 | return(self.result) |
---|
83 | #def execute_action |
---|
84 | |
---|
85 | def set_sleep_between_apps(self,seconds): |
---|
86 | self.sleep_between_apps=seconds |
---|
87 | #def set_sleep_between_apps |
---|
88 | |
---|
89 | def set_sleep_between_cycles(self,seconds): |
---|
90 | self.sleep_between_cycles=seconds |
---|
91 | #def set_sleep_between_cycles |
---|
92 | |
---|
93 | def set_cycles_for_commit(self,count): |
---|
94 | self.cycles_for_commit=count |
---|
95 | #def set_cycles_for_commit |
---|
96 | |
---|
97 | def set_apps_per_cycle(self,count): |
---|
98 | self.apps_per_cycle=count |
---|
99 | #def set_apps_per_cycle |
---|
100 | |
---|
101 | def _callback(self): |
---|
102 | self.progress=self.progress+1 |
---|
103 | #def _callback |
---|
104 | |
---|
105 | def _set_status(self,status,msg=''): |
---|
106 | self.result['status']={'status':status,'msg':msg} |
---|
107 | #def _set_status |
---|
108 | |
---|
109 | def _set_db(self): |
---|
110 | sw_db_exists=False |
---|
111 | if os.path.isfile(self.cache_db): |
---|
112 | sw_db_exists=True |
---|
113 | else: |
---|
114 | try: |
---|
115 | os.makedirs(os.path.dirname(self.cache_db)) |
---|
116 | except Exception as e: |
---|
117 | #self._debug(e) |
---|
118 | pass |
---|
119 | try: |
---|
120 | self.db=sqlite3.connect(self.cache_db) |
---|
121 | except Exception as e: |
---|
122 | #self._debug(e) |
---|
123 | pass |
---|
124 | self.db_cursor=self.db.cursor() |
---|
125 | if sw_db_exists==False: |
---|
126 | #self._debug("Creating cache table") |
---|
127 | self.db_cursor.execute('''CREATE TABLE data(app TEXT PRIMARY KEY, size INTEGER, state TEXT, version TEXT)''') |
---|
128 | self.db_cursor.execute("SELECT count(*) FROM data") |
---|
129 | self.processed=self.db_cursor.fetchone() |
---|
130 | #self._debug("%s apps present"%self.processed) |
---|
131 | #def _set_db |
---|
132 | |
---|
133 | def _build_cache(self): |
---|
134 | threads=[] |
---|
135 | semaphore = threading.BoundedSemaphore(value=self.apps_per_cycle) |
---|
136 | storeapps=self.store.get_apps() |
---|
137 | processed=[] |
---|
138 | for item in range(len(storeapps)-1): |
---|
139 | cursor=random.randint(0,len(storeapps)-1) |
---|
140 | app=storeapps[cursor] |
---|
141 | pkgname=app.get_pkgname_default() |
---|
142 | while pkgname in processed and len(processed)<len(storeapps): |
---|
143 | #self._debug("%s already processed"%pkgname) |
---|
144 | cursor=random.randint(0,len(storeapps)-1) |
---|
145 | app=storeapps[cursor] |
---|
146 | pkgname=app.get_pkgname_default() |
---|
147 | processed.append(pkgname) |
---|
148 | th=threading.Thread(target=self._th_get_data_for_app, args = (app,semaphore)) |
---|
149 | threads.append(th) |
---|
150 | th.start() |
---|
151 | self.insert_count+=1 |
---|
152 | if self.insert_count==self.apps_per_cycle: |
---|
153 | for thread in threads: |
---|
154 | try: |
---|
155 | thread.join() |
---|
156 | except: |
---|
157 | pass |
---|
158 | time.sleep(self.sleep_between_cycles) |
---|
159 | threads=[] |
---|
160 | if self.insert_count==self.cycles_for_commit*self.apps_per_cycle: |
---|
161 | self.insert_count=0 |
---|
162 | self._write_info() |
---|
163 | time.sleep(self.sleep_between_apps) |
---|
164 | #self._debug("Cache finished. Processed %s apps"%str(len(storeapps)-1)) |
---|
165 | #def _build_cache |
---|
166 | |
---|
167 | def _write_info(self): |
---|
168 | processed=[] |
---|
169 | while self.data_pool.qsize(): |
---|
170 | appinfo=self.data_pool.get() |
---|
171 | if appinfo in processed: |
---|
172 | #self._debug("Duplicated %s"%appinfo['package']) |
---|
173 | continue |
---|
174 | processed.append(appinfo) |
---|
175 | #self._debug("Writing %s to db"%appinfo['package']) |
---|
176 | self.db_cursor.execute('''REPLACE into data values (?,?,?,?)''', (appinfo['package'],appinfo['size'],appinfo['state'],appinfo['version'])) |
---|
177 | time.sleep(self.sleep_between_apps) |
---|
178 | self._commit_bd() |
---|
179 | #def _write_info |
---|
180 | |
---|
181 | def _commit_bd(self): |
---|
182 | try: |
---|
183 | self.db.commit() |
---|
184 | except Exception as e: |
---|
185 | #self._debug("Commit error: %s. Rollback launched\n"%e) |
---|
186 | self.db.rollback() |
---|
187 | #def _commit_bd |
---|
188 | |
---|
189 | def _th_get_data_for_app(self,app,semaphore): |
---|
190 | #self._debug("Processing %s"%app.get_pkgname_default()) |
---|
191 | app_info=self.infomanager.execute_action('info',[app],True)['data'] |
---|
192 | package_type=self._check_package_type(app_info[0]) |
---|
193 | #self._debug("Type %s"%package_type) |
---|
194 | appinfo=None |
---|
195 | if package_type=='deb': |
---|
196 | appinfo=self.debmanager.execute_action('pkginfo',app_info)['data'] |
---|
197 | if package_type=='appimage': |
---|
198 | appinfo=self.appimagemanager.execute_action('pkginfo',app_info)['data'] |
---|
199 | if package_type=='zmd': |
---|
200 | appinfo=self.zmdmanager.execute_action('pkginfo',app_info)['data'] |
---|
201 | if package_type=='snap': |
---|
202 | appinfo=self.snapmanager.execute_action('pkginfo',app_info)['data'] |
---|
203 | #Disabling state |
---|
204 | try: |
---|
205 | appinfo[-1]['state']='' |
---|
206 | #self._debug("Storing %s"%appinfo[-1]['package']) |
---|
207 | self.data_pool.put(appinfo[-1]) |
---|
208 | except Exception as e: |
---|
209 | #self._debug("_th_get_data_for_app: %s"%e) |
---|
210 | pass |
---|
211 | #def _th_get_data_for_app |
---|
212 | |
---|
213 | def _check_package_type(self,appinfo): |
---|
214 | #Standalone installers must have the subcategory "installer" |
---|
215 | #Zomandos must have the subcategory "Zomando" |
---|
216 | #self._debug("Checking package type for app "+appinfo['name']) |
---|
217 | package_type='' |
---|
218 | if appinfo['bundle']: |
---|
219 | package_type=appinfo['bundle'] |
---|
220 | if type(package_type)==type([]): |
---|
221 | package_type=package_type[0] |
---|
222 | else: |
---|
223 | if "Zomando" in appinfo['categories']: |
---|
224 | package_type="zmd" |
---|
225 | if 'component' in appinfo.keys(): |
---|
226 | if appinfo['component']!='': |
---|
227 | package_type='deb' |
---|
228 | #Standalone installers must have an installerUrl field loaded from a bundle type=script description |
---|
229 | if appinfo['installerUrl']!='': |
---|
230 | package_type="sh" |
---|
231 | return(package_type) |
---|
232 | #def _check_package_type |
---|
233 | |
---|
234 | def _get_info(self,appinfo): |
---|
235 | #self._debug("Searching %s"%appinfo['package']) |
---|
236 | self.db_cursor.execute('''SELECT * FROM data WHERE app=?''',(appinfo['package'],)) |
---|
237 | row=self.db_cursor.fetchone() |
---|
238 | if row: |
---|
239 | #self._debug("Row: %s"%str(row)) |
---|
240 | appinfo['size']=str(row[1]) |
---|
241 | # appinfo['state']=row[2] |
---|
242 | appinfo['state']='' |
---|
243 | appinfo['version']=row[3] |
---|
244 | self._set_status(0) |
---|
245 | else: |
---|
246 | self._set_status(1) |
---|
247 | return(appinfo) |
---|
248 | #def _get_info |
---|
249 | |
---|
250 | def _update(self,app,state): |
---|
251 | return |
---|
252 | self.db_cursor.execute('''SELECT * FROM data WHERE app=?''',(app,)) |
---|
253 | row=self.db_cursor.fetchone() |
---|
254 | if row: |
---|
255 | #self._debug("Updating %s with state %s"%(app,state)) |
---|
256 | query_data=(state,app) |
---|
257 | self.db_cursor.execute('''UPDATE data set state=? WHERE app=?''',(query_data)) |
---|
258 | self._commit_bd() |
---|
259 | #def _update |
---|