16
16
# -*- coding: utf-8 -*-
17
17
18
18
from datetime import timedelta as datetime_timedelta
19
- from json import loads as json_loads
19
+ from json import dumps as json_dumps , loads as json_loads
20
20
from os .path import join as os_path_join , sep as os_path_sep
21
- from re import compile as re_compile , search as re_search
21
+ from re import compile as re_compile , findall as re_findall
22
22
from simplecache import SimpleCache
23
23
from six import PY2
24
24
from six .moves import urllib
37
37
from xbmcvfs import translatePath as xbmcvfs_translatePath
38
38
39
39
# Plugin Info
40
- ADDON_ID = 'script.kodinerds.android.update'
40
+ ADDON_ID = 'script.kodinerds.android.update'
41
41
REAL_SETTINGS = xbmcaddon_Addon (id = ADDON_ID )
42
- ADDON_NAME = REAL_SETTINGS .getAddonInfo ('name' )
43
- SETTINGS_LOC = '/storage/emulated/0/download'
44
- ADDON_PATH = REAL_SETTINGS .getAddonInfo ('path' )
42
+ ADDON_NAME = REAL_SETTINGS .getAddonInfo ('name' )
43
+ SETTINGS_LOC = '/storage/emulated/0/download'
44
+ ADDON_PATH = REAL_SETTINGS .getAddonInfo ('path' )
45
45
ADDON_VERSION = REAL_SETTINGS .getAddonInfo ('version' )
46
- ICON = REAL_SETTINGS .getAddonInfo ('icon' )
47
- LANGUAGE = REAL_SETTINGS .getLocalizedString
48
-
49
- ## GLOBALS ##
50
- TIMEOUT = 15
51
- MIN_VER = 5 #Minimum Android Version Compatible with Kodi
52
- MAIN = [
53
- {'Matrix' : 'https://www.dropbox.com/sh/0n37s3j3iybww9q/AABN74pzb3CMJBiI76r4jZ48a?dl=0' },
54
- {'Matrix-FireTV' : 'https://www.dropbox.com/sh/7n9tnp70jtjwegt/AAD2VUyUSJEq_iRyfxoHUTjHa?dl=0' },
55
- {'Nexus' : 'https://www.dropbox.com/sh/5fjkqnuvwa7z3ml/AACcKF7nZJyQrlc-mCb_k3A8a?dl=0' },
56
- {'Nexus-FireTV' : 'https://www.dropbox.com/sh/nmgbffz13vqtpza/AABawAkbvlsb4Z7HrfDFXjxwa?dl=0' }
46
+ ICON = REAL_SETTINGS .getAddonInfo ('icon' )
47
+ LANGUAGE = REAL_SETTINGS .getLocalizedString
48
+
49
+ # # GLOBALS ##
50
+ TIMEOUT = 15
51
+ MIN_VER = 5 # Minimum Android Version Compatible with Kodi
52
+ BASE_URL = 'https://repo.kodinerds.net/index.php'
53
+ MAIN = [
54
+ {'label' : 'Matrix' , 'url' : '{}?action=list&scope=all&version=matrix/' .format (BASE_URL ), 'regex' : 'download=(.*net\.kodinerds\.maven\.kodi\.arm.*)"' },
55
+ {'label' : 'Matrix-FireTV' , 'url' : '{}?action=list&scope=all&version=matrix/' .format (BASE_URL ), 'regex' : 'download=(.*net\.kodinerds\.maven\.kodi\.firetv\.arm.*)"' },
56
+ {'label' : 'Nexus' , 'url' : '{}?action=list&scope=all&version=nexus/' .format (BASE_URL ), 'regex' : 'download=(.*net\.kodinerds\.maven\.kodi20\.arm.*)"' },
57
+ {'label' : 'Nexus-FireTV' , 'url' : '{}?action=list&scope=all&version=nexus/' .format (BASE_URL ), 'regex' : 'download=(.*net\.kodinerds\.maven\.kodi20\.firetv\.arm.*)"' }
57
58
]
58
- DEBUG = REAL_SETTINGS .getSetting ('Enable_Debugging' ) == 'true'
59
- CLEAN = REAL_SETTINGS .getSetting ('Disable_Maintenance' ) == 'false'
60
- VERSION = REAL_SETTINGS .getSetting ('Version' )
61
- CUSTOM = (REAL_SETTINGS .getSetting ('Custom_Manager' ) or 'com.android.documentsui' )
62
- FMANAGER = {0 :'com.android.documentsui' ,1 :CUSTOM }[int (REAL_SETTINGS .getSetting ('File_Manager' ))]
59
+ DEBUG = REAL_SETTINGS .getSetting ('Enable_Debugging' ) == 'true'
60
+ CLEAN = REAL_SETTINGS .getSetting ('Disable_Maintenance' ) == 'false'
61
+ VERSION = REAL_SETTINGS .getSetting ('Version' )
62
+ CUSTOM = (REAL_SETTINGS .getSetting ('Custom_Manager' ) or 'com.android.documentsui' )
63
+ FMANAGER = {0 :'com.android.documentsui' , 1 :CUSTOM }[int (REAL_SETTINGS .getSetting ('File_Manager' ))]
63
64
64
65
65
66
def log (msg , level = xbmc_LOGDEBUG ):
66
67
if DEBUG == False and level != xbmc_LOGERROR : return
67
68
if level == xbmc_LOGERROR : msg += ', {0}' .format (traceback_format_exc ())
68
69
xbmc_log ('[{0}-{1}] {2}' .format (ADDON_ID , ADDON_VERSION , msg ), level )
69
70
71
+
70
72
def selectDialog (label , items , pselect = - 1 , uDetails = True ):
71
73
select = xbmcgui_Dialog ().select (label , items , preselect = pselect , useDetails = uDetails )
72
74
if select >= 0 : return select
73
75
return None
74
76
77
+
75
78
socket_setdefaulttimeout (TIMEOUT )
79
+
80
+
76
81
class Installer (object ):
82
+
83
+
77
84
def __init__ (self ):
78
85
self .myMonitor = xbmc_Monitor ()
79
86
self .cache = SimpleCache ()
80
87
if not self .chkVersion (): return
81
88
self .buildMain ('' )
82
-
83
-
89
+
90
+
84
91
def disable (self , build ):
85
92
xbmcgui_Dialog ().notification (ADDON_NAME , VERSION , ICON , 8000 )
86
- if not xbmcgui_Dialog ().yesno (ADDON_NAME , LANGUAGE (30011 ).format (build ), LANGUAGE (30012 )): return False
93
+ if not xbmcgui_Dialog ().yesno (ADDON_NAME , LANGUAGE (30011 ).format (build ), LANGUAGE (30012 )): return False
87
94
xbmc_executeJSONRPC ('{"jsonrpc": "2.0", "method":"Addons.SetAddonEnabled","params":{"addonid":"{0}","enabled":false}, "id": 1}' .format (ADDON_ID ))
88
95
xbmcgui_Dialog ().notification (ADDON_NAME , LANGUAGE (30009 ), ICON , 4000 )
89
96
return False
90
-
91
-
97
+
98
+
92
99
def chkVersion (self ):
93
- try :
100
+ try :
94
101
build = int (re_compile ('Android (\d+)' ).findall (VERSION )[0 ])
95
102
except : build = MIN_VER
96
103
if build >= MIN_VER : return True
97
104
else : return self .disable (build )
98
-
99
105
100
- def getAPKs (self , url ):
106
+
107
+ def getAPKs (self , url ):
101
108
log ('getAPKs: path = {0}' .format (url ))
102
109
try :
103
110
cacheResponse = self .cache .get ('{0}.openURL, url = {1}' .format (ADDON_NAME , url ))
104
111
if not cacheResponse :
105
112
cacheResponse = dict (entries = list ())
106
113
if url == '' :
107
114
for item in MAIN :
108
- for key in item . keys ():
109
- entry = dict ()
110
- entry .update (dict (tag = 'folder' ))
111
- entry .update (dict (name = key ))
112
- entry .update (dict (path_display = item [ key ] ))
113
- cacheResponse .get ('entries' ).append (entry )
115
+ entry = dict ()
116
+ entry . update ( dict (tag = 'folder' ) )
117
+ entry .update (dict (name = item . get ( 'label' ) ))
118
+ entry .update (dict (url = item . get ( 'url' ) ))
119
+ entry .update (dict (regex = item . get ( 'regex' ) ))
120
+ cacheResponse .get ('entries' ).append (entry )
114
121
else :
115
- request = urllib .request .Request (url )
116
- string_json = re_search ('responseReceived\("(.*)"\)}\)' , urllib .request .urlopen (request , timeout = TIMEOUT ).read ().decode ('utf-8' ))
117
- if string_json :
118
- json_entries = json_loads (string_json .group (1 ).replace ('\\ ' , '' )).get ('entries' )
119
- for json_entry in json_entries :
120
- entry = dict ()
121
- href = json_entry .get ('href' )
122
- if json_entry .get ('is_dir' ):
123
- entry .update (dict (tag = 'folder' ))
124
- else :
122
+ regex = url .rsplit ('/' , 1 )[1 ].split ('=' , 1 )[1 ]
123
+ if regex :
124
+ request = urllib .request .Request (url )
125
+ html = urllib .request .urlopen (request , timeout = TIMEOUT ).read ().decode ('utf-8' )
126
+ matches = re_findall (regex , html )
127
+ if matches :
128
+ for match in matches :
129
+ entry = dict ()
125
130
entry .update (dict (tag = 'file' ))
126
- entry .update (dict (size = json_entry . get ( 'bytes' ) ))
127
- href = href . replace ( 'dl=0' , 'dl=1' )
128
- entry .update (dict (name = json_entry . get ( 'filename' )))
129
- entry . update ( dict ( path_display = href ) )
130
- cacheResponse .get ('entries' ). append ( entry )
131
+ entry .update (dict (name = match . rsplit ( '/' , 1 )[ 1 ] ))
132
+ entry . update ( dict ( url = BASE_URL ) )
133
+ entry .update (dict (data = '{{"c_item[]": "download={}"}}' . format ( match )))
134
+ cacheResponse . get ( 'entries' ). append ( entry )
135
+ cacheResponse .update ( dict ( entries = sorted ( cacheResponse . get ('entries' ), key = lambda k : k [ 'name' ], reverse = True )) )
131
136
132
137
self .cache .set ('{0}.openURL, url = {1}' .format (ADDON_NAME , url ), cacheResponse , expiration = datetime_timedelta (minutes = 5 ))
133
138
return cacheResponse
134
139
except Exception as e :
135
140
log ('openURL Failed! {0}' .format (e ), xbmc_LOGERROR )
136
141
xbmcgui_Dialog ().notification (ADDON_NAME , LANGUAGE (30001 ), ICON , 4000 )
137
142
return None
138
-
139
-
143
+
144
+
140
145
def buildItems (self , path ):
141
146
entries = self .getAPKs (path ).get ('entries' , {})
142
147
if entries is None or len (entries ) == 0 : return
143
148
for entry in entries :
144
149
if entry .get ('tag' ) == 'file' and entry .get ('name' ).endswith ('.apk' ):
145
150
label = entry .get ('name' )
146
- li = xbmcgui_ListItem (label , '{0} {1:.02f} MB' .format (label .split ('.apk' )[1 ], entry .get ('size' ) / 1024 / 1024 ).replace ('.' , ',' ), path = entry .get ('path_display' ))
151
+ li = xbmcgui_ListItem (label , label .split ('.apk' )[1 ], path = entry .get ('url' ))
152
+ li .setProperty ('data' , entry .get ('data' ))
147
153
li .setArt ({'icon' : ICON })
148
154
yield (li )
149
155
elif entry .get ('tag' ) == 'folder' :
150
156
label = entry .get ('name' )
151
- li = xbmcgui_ListItem (label , path = entry .get ('path_display' ))
157
+ li = xbmcgui_ListItem (label , path = '{}®ex={}' . format ( entry .get ('url' ), entry . get ( 'regex' ) ))
152
158
li .setArt ({'icon' : ICON })
153
159
yield (li )
154
-
155
-
160
+
161
+
156
162
def buildMain (self , path ):
157
163
log ('buildMain' )
158
- items = list (self .buildItems (path ))
164
+ items = list (self .buildItems (path ))
159
165
if len (items ) == 0 : return
160
166
else : select = selectDialog (ADDON_NAME , items )
161
- if select is None or select < 0 : return #return on cancel.
162
- newURL = items [select ].getPath ()
163
- if newURL .find ('.apk' ) > 0 :
167
+ if select is None or select < 0 : return # return on cancel.
168
+ if items [select ].getProperty ('data' ):
164
169
dest = xbmcvfs_translatePath (os_path_join (SETTINGS_LOC , items [select ].getLabel ()))
165
170
REAL_SETTINGS .setSetting ("LastPath" , dest )
166
- return self .downloadAPK (items [select ].getPath (), dest )
171
+ return self .downloadAPK (items [select ].getPath (), dest , items [ select ]. getProperty ( 'data' ) )
167
172
else :
168
- self .buildMain (newURL )
169
-
173
+ self .buildMain (items [ select ]. getPath () )
174
+
170
175
171
176
def fileExists (self , dest ):
172
177
if xbmcvfs_exists (dest ):
173
178
if not xbmcgui_Dialog ().yesno (ADDON_NAME , '{0}: {1}' .format (LANGUAGE (30004 ), dest .rsplit (os_path_sep , 1 )[- 1 ]), nolabel = LANGUAGE (30005 ), yeslabel = LANGUAGE (30006 )): return True
174
179
elif CLEAN and xbmcvfs_exists (dest ): self .deleleAPK (dest )
175
180
return False
176
-
177
-
181
+
182
+
178
183
def deleleAPK (self , path ):
179
184
count = 0
180
- #some file systems don't release the file lock instantly.
185
+ # some file systems don't release the file lock instantly.
181
186
while not self .myMonitor .abortRequested () and count < 3 :
182
187
count += 1
183
- if self .myMonitor .waitForAbort (1 ): return
184
- try :
188
+ if self .myMonitor .waitForAbort (1 ): return
189
+ try :
185
190
if xbmcvfs_delete (path ): return
186
191
except : pass
187
-
188
-
189
- def downloadAPK (self , url , dest ):
192
+
193
+
194
+ def downloadAPK (self , url , dest , data ):
190
195
if self .fileExists (dest ): return self .installAPK (dest )
191
196
start_time = time_time ()
192
197
dia = xbmcgui_DialogProgress ()
193
198
fle = dest .rsplit (os_path_sep , 1 )[1 ]
194
199
dia .create (ADDON_NAME , LANGUAGE (30002 ).format (fle ))
195
- try : urllib .request .urlretrieve (url , dest , lambda nb , bs , fs : self .pbhook (nb , bs , fs , dia , start_time , fle ))
200
+ data = urllib .parse .urlencode (json_loads (data )).encode ()
201
+ try : urllib .request .urlretrieve (url , dest , lambda nb , bs , fs : self .pbhook (nb , bs , fs , dia , start_time , fle ), data )
196
202
except Exception as e :
197
203
dia .close ()
198
204
xbmcgui_Dialog ().notification (ADDON_NAME , LANGUAGE (30001 ), ICON , 4000 )
199
205
log ('downloadAPK, Failed! ({0}) {1}' .format (url , e ), xbmc_LOGERROR )
200
206
return self .deleleAPK (dest )
201
207
dia .close ()
202
208
return self .installAPK (dest )
203
-
204
-
209
+
210
+
205
211
def pbhook (self , numblocks , blocksize , filesize , dia , start_time , fle ):
206
- try :
207
- percent = min (numblocks * blocksize * 100 / filesize , 100 )
208
- currently_downloaded = float (numblocks ) * blocksize / (1024 * 1024 )
209
- kbps_speed = numblocks * blocksize / (time_time () - start_time )
212
+ try :
213
+ percent = min (numblocks * blocksize * 100 / filesize , 100 )
214
+ currently_downloaded = float (numblocks ) * blocksize / (1024 * 1024 )
215
+ kbps_speed = numblocks * blocksize / (time_time () - start_time )
210
216
if kbps_speed > 0 : eta = int ((filesize - numblocks * blocksize ) / kbps_speed )
211
- else : eta = 0
212
- kbps_speed = kbps_speed / 1024
217
+ else : eta = 0
218
+ kbps_speed = kbps_speed / 1024
213
219
if eta < 0 : eta = divmod (0 , 60 )
214
220
else : eta = divmod (eta , 60 )
215
- total = (float (filesize ) / (1024 * 1024 ))
216
- label = '[B]Downloading: [/B] {0}' .format (os_path_join (SETTINGS_LOC , fle ))
217
- label2 = '{0:.02f} MB of {1:.02f} MB' .format (currently_downloaded ,total )
221
+ total = (float (filesize ) / (1024 * 1024 ))
222
+ label = '[B]Downloading: [/B] {0}' .format (os_path_join (SETTINGS_LOC , fle ))
223
+ label2 = '{0:.02f} MB of {1:.02f} MB' .format (currently_downloaded , total )
218
224
label2 += ' | [B]Speed:[/B] {0:.02f} Kb/s' .format (kbps_speed )
219
225
label2 += ' | [B]ETA:[/B] {0:02d}:{1:02d}' .format (eta [0 ], eta [1 ])
220
226
dia .update (int (percent ), '{0} {1}' .format (label , label2 .replace ('.' , ',' )))
221
227
except Exception ('Download Failed' ): dia .update (100 )
222
228
if dia .iscanceled (): raise Exception ('Download Canceled' )
223
-
224
-
229
+
230
+
225
231
def installAPK (self , apkfile ):
226
232
xbmc_executebuiltin ('StartAndroidActivity({0},,,"content://{1}")' .format (FMANAGER , apkfile ))
227
233
228
234
229
- if __name__ == '__main__' : Installer ()
235
+ if __name__ == '__main__' : Installer ()
0 commit comments