Merge pull request #548 from lopezvg/master
Kodi 18: Quasar: Sistema de actualización desde Alfa
This commit is contained in:
@@ -264,6 +264,7 @@ def submenu_novedades(item):
|
||||
itemlist = []
|
||||
itemlist_alt = []
|
||||
item.extra2 = ''
|
||||
thumb_buscar = get_thumb("search.png")
|
||||
|
||||
#Renombramos el canal al nombre de clone inicial desde la URL
|
||||
item.channel_host = host
|
||||
@@ -327,6 +328,9 @@ def submenu_novedades(item):
|
||||
item.post = "date=%s" % value
|
||||
itemlist.append(item.clone(action="listado_busqueda", title=title, url=item.url, post=item.post))
|
||||
|
||||
itemlist.append(
|
||||
Item(channel=item.channel, action="search", title="Buscar", url=item.channel_host + "buscar", thumbnail=thumb_buscar, category=item.category, channel_host=item.channel_host))
|
||||
|
||||
itemlist.append(item.clone(action='', title="[COLOR yellow]Lo Último en la Categoría:[/COLOR]"))
|
||||
for value, title in matches:
|
||||
if value.isdigit():
|
||||
|
||||
@@ -306,6 +306,8 @@ def submenu_tools(item):
|
||||
|
||||
itemlist.append(Item(channel=CHANNELNAME, action="check_quickfixes", folder=False,
|
||||
title="Comprobar actualizaciones urgentes", plot="Versión actual: %s" % config.get_addon_version() ))
|
||||
itemlist.append(Item(channel=CHANNELNAME, action="update_quasar", folder=False,
|
||||
title="Actualizar addon externo Quasar"))
|
||||
itemlist.append(Item(channel=CHANNELNAME, action="", title="", folder=False,
|
||||
thumbnail=get_thumb("setting_0.png")))
|
||||
|
||||
@@ -336,6 +338,18 @@ def check_quickfixes(item):
|
||||
return updater.check_addon_updates(verbose=True)
|
||||
|
||||
|
||||
def update_quasar(item):
|
||||
logger.info()
|
||||
|
||||
from platformcode import custom_code, platformtools
|
||||
stat = False
|
||||
stat = custom_code.update_external_addon("quasar")
|
||||
if stat:
|
||||
platformtools.dialog_notification("Actualización Quasar", "Realizada con éxito")
|
||||
else:
|
||||
platformtools.dialog_notification("Actualización Quasar", "Ha fallado. Consulte el log")
|
||||
|
||||
|
||||
def conf_tools(item):
|
||||
logger.info()
|
||||
|
||||
|
||||
294
plugin.video.alfa/lib/quasar/daemon.py
Normal file
294
plugin.video.alfa/lib/quasar/daemon.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import os
|
||||
import stat
|
||||
import time
|
||||
import xbmc
|
||||
import shutil
|
||||
import socket
|
||||
import urllib2
|
||||
import xbmcgui
|
||||
import threading
|
||||
import subprocess
|
||||
from quasar.logger import log
|
||||
from quasar.osarch import PLATFORM
|
||||
from quasar.config import QUASARD_HOST
|
||||
from quasar.addon import ADDON, ADDON_ID, ADDON_PATH
|
||||
from quasar.util import notify, system_information, getLocalizedString, getWindowsShortPath
|
||||
|
||||
def ensure_exec_perms(file_):
|
||||
st = os.stat(file_)
|
||||
os.chmod(file_, st.st_mode | stat.S_IEXEC)
|
||||
return file_
|
||||
|
||||
def android_get_current_appid():
|
||||
with open("/proc/%d/cmdline" % os.getpid()) as fp:
|
||||
return fp.read().rstrip("\0")
|
||||
|
||||
def get_quasard_checksum(path):
|
||||
try:
|
||||
with open(path) as fp:
|
||||
fp.seek(-40, os.SEEK_END) # we put a sha1 there
|
||||
return fp.read()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_quasar_binary():
|
||||
binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")
|
||||
|
||||
log.info("PLATFORM: %s" % str(PLATFORM))
|
||||
binary_dir = os.path.join(ADDON_PATH, "resources", "bin", "%(os)s_%(arch)s" % PLATFORM)
|
||||
if PLATFORM["os"] == "android":
|
||||
log.info("Detected binary folder: %s" % binary_dir)
|
||||
binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy")
|
||||
if os.path.exists(binary_dir_legacy):
|
||||
binary_dir = binary_dir_legacy
|
||||
log.info("Using binary folder: %s" % binary_dir)
|
||||
app_id = android_get_current_appid()
|
||||
xbmc_data_path = os.path.join("/data", "data", app_id)
|
||||
|
||||
try: #Test if there is any permisions problem
|
||||
f = open(os.path.join(xbmc_data_path, "test.txt"), "wb")
|
||||
f.write("test")
|
||||
f.close()
|
||||
os.remove(os.path.join(xbmc_data_path, "test.txt"))
|
||||
except:
|
||||
xbmc_data_path = ''
|
||||
|
||||
if not os.path.exists(xbmc_data_path):
|
||||
log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://xbmcbin/")))
|
||||
xbmc_data_path = xbmc.translatePath("special://xbmcbin/")
|
||||
|
||||
try: #Test if there is any permisions problem
|
||||
f = open(os.path.join(xbmc_data_path, "test.txt"), "wb")
|
||||
f.write("test")
|
||||
f.close()
|
||||
os.remove(os.path.join(xbmc_data_path, "test.txt"))
|
||||
except:
|
||||
xbmc_data_path = ''
|
||||
|
||||
if not os.path.exists(xbmc_data_path):
|
||||
log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://masterprofile/")))
|
||||
xbmc_data_path = xbmc.translatePath("special://masterprofile/")
|
||||
dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID, "bin", "%(os)s_%(arch)s" % PLATFORM)
|
||||
else:
|
||||
dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "bin", "%(os)s_%(arch)s" % PLATFORM)
|
||||
|
||||
log.info("Using destination binary folder: %s" % dest_binary_dir)
|
||||
binary_path = os.path.join(binary_dir, binary)
|
||||
dest_binary_path = os.path.join(dest_binary_dir, binary)
|
||||
|
||||
if not os.path.exists(binary_path):
|
||||
notify((getLocalizedString(30103) + " %(os)s_%(arch)s" % PLATFORM), time=7000)
|
||||
system_information()
|
||||
try:
|
||||
log.info("Source directory (%s):\n%s" % (binary_dir, os.listdir(os.path.join(binary_dir, ".."))))
|
||||
log.info("Destination directory (%s):\n%s" % (dest_binary_dir, os.listdir(os.path.join(dest_binary_dir, ".."))))
|
||||
except Exception:
|
||||
pass
|
||||
return False, False
|
||||
|
||||
if os.path.isdir(dest_binary_path):
|
||||
log.warning("Destination path is a directory, expected previous binary file, removing...")
|
||||
try:
|
||||
shutil.rmtree(dest_binary_path)
|
||||
except Exception as e:
|
||||
log.error("Unable to remove destination path for update: %s" % e)
|
||||
system_information()
|
||||
return False, False
|
||||
|
||||
if not os.path.exists(dest_binary_path) or get_quasard_checksum(dest_binary_path) != get_quasard_checksum(binary_path):
|
||||
log.info("Updating quasar daemon...")
|
||||
try:
|
||||
os.makedirs(dest_binary_dir)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
shutil.rmtree(dest_binary_dir)
|
||||
except Exception as e:
|
||||
log.error("Unable to remove destination path for update: %s" % e)
|
||||
system_information()
|
||||
pass
|
||||
try:
|
||||
shutil.copytree(binary_dir, dest_binary_dir)
|
||||
except Exception as e:
|
||||
log.error("Unable to copy to destination path for update: %s" % e)
|
||||
system_information()
|
||||
return False, False
|
||||
|
||||
# Clean stale files in the directory, as this can cause headaches on
|
||||
# Android when they are unreachable
|
||||
dest_files = set(os.listdir(dest_binary_dir))
|
||||
orig_files = set(os.listdir(binary_dir))
|
||||
log.info("Deleting stale files %s" % (dest_files - orig_files))
|
||||
for file_ in (dest_files - orig_files):
|
||||
path = os.path.join(dest_binary_dir, file_)
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
||||
return dest_binary_dir, ensure_exec_perms(dest_binary_path)
|
||||
|
||||
def clear_fd_inherit_flags():
|
||||
# Ensure the spawned quasar binary doesn't inherit open files from Kodi
|
||||
# which can break things like addon updates. [WINDOWS ONLY]
|
||||
from ctypes import windll
|
||||
|
||||
HANDLE_RANGE = xrange(0, 65536)
|
||||
HANDLE_FLAG_INHERIT = 1
|
||||
FILE_TYPE_DISK = 1
|
||||
|
||||
for hd in HANDLE_RANGE:
|
||||
if windll.kernel32.GetFileType(hd) == FILE_TYPE_DISK:
|
||||
if not windll.kernel32.SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0):
|
||||
log.error("Error clearing inherit flag, disk file handle %x" % hd)
|
||||
|
||||
|
||||
def jsonrpc_enabled(notify=False):
|
||||
try:
|
||||
s = socket.socket()
|
||||
s.connect(('127.0.0.1', 9090))
|
||||
s.close()
|
||||
log.info("Kodi's JSON-RPC service is available, starting up...")
|
||||
del s
|
||||
return True
|
||||
except Exception as e:
|
||||
log.error(repr(e))
|
||||
if notify:
|
||||
xbmc.executebuiltin("ActivateWindow(ServiceSettings)")
|
||||
dialog = xbmcgui.Dialog()
|
||||
dialog.ok("Quasar", getLocalizedString(30199))
|
||||
return False
|
||||
|
||||
def start_quasard(**kwargs):
|
||||
jsonrpc_failures = 0
|
||||
while jsonrpc_enabled() is False:
|
||||
jsonrpc_failures += 1
|
||||
log.warning("Unable to connect to Kodi's JSON-RPC service, retrying...")
|
||||
if jsonrpc_failures > 1:
|
||||
time.sleep(5)
|
||||
if not jsonrpc_enabled(notify=True):
|
||||
log.error("Unable to reach Kodi's JSON-RPC service, aborting...")
|
||||
return False
|
||||
else:
|
||||
break
|
||||
time.sleep(3)
|
||||
|
||||
quasar_dir, quasar_binary = get_quasar_binary()
|
||||
|
||||
if quasar_dir is False or quasar_binary is False:
|
||||
return False
|
||||
|
||||
lockfile = os.path.join(ADDON_PATH, ".lockfile")
|
||||
if os.path.exists(lockfile):
|
||||
log.warning("Existing process found from lockfile, killing...")
|
||||
try:
|
||||
with open(lockfile) as lf:
|
||||
pid = int(lf.read().rstrip(" \t\r\n\0"))
|
||||
os.kill(pid, 9)
|
||||
except Exception as e:
|
||||
log.error(repr(e))
|
||||
|
||||
if PLATFORM["os"] == "windows":
|
||||
log.warning("Removing library.db.lock file...")
|
||||
try:
|
||||
library_lockfile = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "library.db.lock")
|
||||
os.remove(library_lockfile)
|
||||
except Exception as e:
|
||||
log.error(repr(e))
|
||||
|
||||
SW_HIDE = 0
|
||||
STARTF_USESHOWWINDOW = 1
|
||||
|
||||
args = [quasar_binary]
|
||||
kwargs["cwd"] = quasar_dir
|
||||
|
||||
if PLATFORM["os"] == "windows":
|
||||
args[0] = getWindowsShortPath(quasar_binary)
|
||||
kwargs["cwd"] = getWindowsShortPath(quasar_dir)
|
||||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags = STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = SW_HIDE
|
||||
clear_fd_inherit_flags()
|
||||
kwargs["startupinfo"] = si
|
||||
else:
|
||||
env = os.environ.copy()
|
||||
env["LD_LIBRARY_PATH"] = "%s:%s" % (quasar_dir, env.get("LD_LIBRARY_PATH", ""))
|
||||
kwargs["env"] = env
|
||||
kwargs["close_fds"] = True
|
||||
|
||||
wait_counter = 1
|
||||
while xbmc.getCondVisibility('Window.IsVisible(10140)') or xbmc.getCondVisibility('Window.IsActive(10140)'):
|
||||
if wait_counter == 1:
|
||||
log.info('Add-on settings currently opened, waiting before starting...')
|
||||
if wait_counter > 300:
|
||||
break
|
||||
time.sleep(1)
|
||||
wait_counter += 1
|
||||
|
||||
return subprocess.Popen(args, **kwargs)
|
||||
|
||||
def shutdown():
|
||||
try:
|
||||
urllib2.urlopen(QUASARD_HOST + "/shutdown")
|
||||
except:
|
||||
pass
|
||||
|
||||
def wait_for_abortRequested(proc, monitor):
|
||||
monitor.closing.wait()
|
||||
log.info("quasard: exiting quasard daemon")
|
||||
try:
|
||||
proc.terminate()
|
||||
except OSError:
|
||||
pass # Process already exited, nothing to terminate
|
||||
log.info("quasard: quasard daemon exited")
|
||||
|
||||
def quasard_thread(monitor):
|
||||
crash_count = 0
|
||||
try:
|
||||
while not xbmc.abortRequested:
|
||||
log.info("quasard: starting quasard")
|
||||
proc = start_quasard(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
if not proc:
|
||||
break
|
||||
threading.Thread(target=wait_for_abortRequested, args=[proc, monitor]).start()
|
||||
|
||||
if PLATFORM["os"] == "windows":
|
||||
while proc.poll() is None:
|
||||
log.info(proc.stdout.readline())
|
||||
else:
|
||||
# Kodi hangs on some Android (sigh...) systems when doing a blocking
|
||||
# read. We count on the fact that Quasar daemon flushes its log
|
||||
# output on \n, creating a pretty clean output
|
||||
import fcntl
|
||||
import select
|
||||
fd = proc.stdout.fileno()
|
||||
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
while proc.poll() is None:
|
||||
try:
|
||||
to_read, _, _ = select.select([proc.stdout], [], [])
|
||||
for ro in to_read:
|
||||
line = ro.readline()
|
||||
if line == "": # write end is closed
|
||||
break
|
||||
log.info(line)
|
||||
except IOError:
|
||||
time.sleep(1) # nothing to read, sleep
|
||||
|
||||
if proc.returncode == 0 or xbmc.abortRequested:
|
||||
break
|
||||
|
||||
crash_count += 1
|
||||
notify(getLocalizedString(30100), time=3000)
|
||||
xbmc.executebuiltin("Dialog.Close(all, true)")
|
||||
system_information()
|
||||
time.sleep(5)
|
||||
if crash_count >= 3:
|
||||
notify(getLocalizedString(30110), time=3000)
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
map(log.error, traceback.format_exc().split("\n"))
|
||||
notify("%s: %s" % (getLocalizedString(30226), repr(e).encode('utf-8')))
|
||||
raise
|
||||
260
plugin.video.alfa/lib/quasar/navigation.py
Normal file
260
plugin.video.alfa/lib/quasar/navigation.py
Normal file
@@ -0,0 +1,260 @@
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import urllib2
|
||||
import urlparse
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
from quasar.logger import log
|
||||
from quasar.config import QUASARD_HOST
|
||||
from quasar.addon import ADDON, ADDON_ID, ADDON_PATH
|
||||
from quasar.util import notify, getLocalizedString, getLocalizedLabel, system_information
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
HANDLE = int(sys.argv[1])
|
||||
|
||||
|
||||
class InfoLabels(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict.get(self, key.lower(), "")
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
dict.__setitem__(self, key.lower(), val)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
for k, v in dict(*args, **kwargs).iteritems():
|
||||
self[k] = v
|
||||
|
||||
|
||||
class closing(object):
|
||||
def __init__(self, thing):
|
||||
self.thing = thing
|
||||
|
||||
def __enter__(self):
|
||||
return self.thing
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
self.thing.close()
|
||||
|
||||
|
||||
class NoRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
def http_error_302(self, req, fp, code, msg, headers):
|
||||
import urllib
|
||||
infourl = urllib.addinfourl(fp, headers, headers["Location"])
|
||||
infourl.status = code
|
||||
infourl.code = code
|
||||
return infourl
|
||||
http_error_300 = http_error_302
|
||||
http_error_301 = http_error_302
|
||||
http_error_303 = http_error_302
|
||||
http_error_307 = http_error_302
|
||||
|
||||
|
||||
def getInfoLabels():
|
||||
id_list = [int(s) for s in sys.argv[0].split("/") if s.isdigit()]
|
||||
tmdb_id = id_list[0] if id_list else None
|
||||
|
||||
if not tmdb_id:
|
||||
parsed_url = urlparse.urlparse(sys.argv[0] + sys.argv[2])
|
||||
query = urlparse.parse_qs(parsed_url.query)
|
||||
log.debug("Parsed URL: %s, Query: %s", repr(parsed_url), repr(query))
|
||||
if 'tmdb' in query and 'show' not in query:
|
||||
tmdb_id = query['tmdb'][0]
|
||||
url = "%s/movie/%s/infolabels" % (QUASARD_HOST, tmdb_id)
|
||||
elif 'show' in query:
|
||||
tmdb_id = query['show'][0]
|
||||
if 'season' in query and 'episode' in query:
|
||||
url = "%s/show/%s/season/%s/episode/%s/infolabels" % (QUASARD_HOST, tmdb_id, query['season'][0], query['episode'][0])
|
||||
else:
|
||||
url = "%s/show/%s/infolabels" % (QUASARD_HOST, tmdb_id)
|
||||
else:
|
||||
url = "%s/infolabels" % (QUASARD_HOST)
|
||||
elif 'movie' in sys.argv[0]:
|
||||
url = "%s/movie/%s/infolabels" % (QUASARD_HOST, tmdb_id)
|
||||
elif ('episode' in sys.argv[0] or 'show' in sys.argv[0]) and len(id_list) > 2:
|
||||
url = "%s/show/%s/season/%s/episode/%s/infolabels" % (QUASARD_HOST, tmdb_id, id_list[1], id_list[2])
|
||||
elif 'show' in sys.argv[0] and len(id_list) == 2:
|
||||
url = "%s/show/%s/season/%s/episode/%s/infolabels" % (QUASARD_HOST, tmdb_id, id_list[1], 1)
|
||||
else:
|
||||
url = "%s/infolabels" % (QUASARD_HOST)
|
||||
|
||||
log.debug("Resolving TMDB item by calling %s for %s" % (url, repr(sys.argv)))
|
||||
|
||||
try:
|
||||
with closing(urllib2.urlopen(url)) as response:
|
||||
resolved = json.loads(response.read())
|
||||
if not resolved:
|
||||
return {}
|
||||
|
||||
if 'info' in resolved and resolved['info']:
|
||||
resolved.update(resolved['info'])
|
||||
|
||||
if 'art' in resolved and resolved['art']:
|
||||
resolved['artbanner'] = ''
|
||||
for k, v in resolved['art'].items():
|
||||
resolved['art' + k] = v
|
||||
|
||||
if 'info' in resolved:
|
||||
del resolved['info']
|
||||
if 'art' in resolved:
|
||||
del resolved['art']
|
||||
if 'stream_info' in resolved:
|
||||
del resolved['stream_info']
|
||||
|
||||
if 'dbtype' not in resolved:
|
||||
resolved['dbtype'] = 'video'
|
||||
if 'mediatype' not in resolved or resolved['mediatype'] == '':
|
||||
resolved['Mediatype'] = resolved['dbtype']
|
||||
|
||||
return resolved
|
||||
except:
|
||||
log.debug("Could not resolve TMDB item: %s" % tmdb_id)
|
||||
return {}
|
||||
|
||||
|
||||
def _json(url):
|
||||
with closing(urllib2.urlopen(url)) as response:
|
||||
if response.code >= 300 and response.code <= 307:
|
||||
# Pause currently playing Quasar file to avoid doubling requests
|
||||
if xbmc.Player().isPlaying() and ADDON_ID in xbmc.Player().getPlayingFile():
|
||||
xbmc.Player().pause()
|
||||
_infoLabels = InfoLabels(getInfoLabels())
|
||||
|
||||
item = xbmcgui.ListItem(
|
||||
path=response.geturl(),
|
||||
label=_infoLabels["label"],
|
||||
label2=_infoLabels["label2"],
|
||||
thumbnailImage=_infoLabels["thumbnail"])
|
||||
|
||||
item.setArt({
|
||||
"poster": _infoLabels["artposter"],
|
||||
"banner": _infoLabels["artbanner"],
|
||||
"fanart": _infoLabels["artfanart"]
|
||||
})
|
||||
|
||||
item.setInfo(type='Video', infoLabels=_infoLabels)
|
||||
xbmcplugin.setResolvedUrl(HANDLE, True, item)
|
||||
return
|
||||
|
||||
payload = response.read()
|
||||
|
||||
try:
|
||||
if payload:
|
||||
return json.loads(payload)
|
||||
except:
|
||||
raise Exception(payload)
|
||||
|
||||
|
||||
def run(url_suffix=""):
|
||||
if not os.path.exists(os.path.join(ADDON_PATH, ".firstrun")):
|
||||
notify(getLocalizedString(30101))
|
||||
system_information()
|
||||
return
|
||||
|
||||
donatePath = os.path.join(ADDON_PATH, ".donate")
|
||||
if not os.path.exists(donatePath):
|
||||
with open(donatePath, "w"):
|
||||
os.utime(donatePath, None)
|
||||
dialog = xbmcgui.Dialog()
|
||||
dialog.ok("Quasar", getLocalizedString(30141))
|
||||
|
||||
socket.setdefaulttimeout(int(ADDON.getSetting("buffer_timeout")))
|
||||
urllib2.install_opener(urllib2.build_opener(NoRedirectHandler()))
|
||||
|
||||
# Pause currently playing Quasar file to avoid doubling requests
|
||||
if xbmc.Player().isPlaying() and ADDON_ID in xbmc.Player().getPlayingFile():
|
||||
xbmc.Player().pause()
|
||||
|
||||
url = sys.argv[0].replace("plugin://%s" % ADDON_ID, QUASARD_HOST + url_suffix) + sys.argv[2]
|
||||
log.debug("Requesting %s from %s" % (url, repr(sys.argv)))
|
||||
|
||||
try:
|
||||
data = _json(url)
|
||||
except urllib2.URLError as e:
|
||||
if 'Connection refused' in e.reason:
|
||||
notify(getLocalizedString(30116), time=7000)
|
||||
else:
|
||||
import traceback
|
||||
map(log.error, traceback.format_exc().split("\n"))
|
||||
notify(e.reason, time=7000)
|
||||
return
|
||||
except Exception as e:
|
||||
import traceback
|
||||
map(log.error, traceback.format_exc().split("\n"))
|
||||
try:
|
||||
msg = unicode(e)
|
||||
except:
|
||||
try:
|
||||
msg = str(e)
|
||||
except:
|
||||
msg = repr(e)
|
||||
notify(getLocalizedLabel(msg), time=7000)
|
||||
return
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
if data["content_type"]:
|
||||
content_type = data["content_type"]
|
||||
if data["content_type"].startswith("menus"):
|
||||
content_type = data["content_type"].split("_")[1]
|
||||
|
||||
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED)
|
||||
if content_type != "tvshows":
|
||||
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)
|
||||
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_DATE)
|
||||
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_GENRE)
|
||||
xbmcplugin.setContent(HANDLE, content_type)
|
||||
|
||||
listitems = range(len(data["items"]))
|
||||
for i, item in enumerate(data["items"]):
|
||||
# Translate labels
|
||||
if item["label"][0:8] == "LOCALIZE":
|
||||
item["label"] = unicode(getLocalizedLabel(item["label"]), 'utf-8')
|
||||
if item["label2"][0:8] == "LOCALIZE":
|
||||
item["label2"] = getLocalizedLabel(item["label2"])
|
||||
|
||||
listItem = xbmcgui.ListItem(label=item["label"], label2=item["label2"], iconImage=item["icon"], thumbnailImage=item["thumbnail"])
|
||||
if item.get("info"):
|
||||
listItem.setInfo("video", item["info"])
|
||||
if item.get("stream_info"):
|
||||
for type_, values in item["stream_info"].items():
|
||||
listItem.addStreamInfo(type_, values)
|
||||
if item.get("art"):
|
||||
listItem.setArt(item["art"])
|
||||
elif ADDON.getSetting('default_fanart') == 'true' and item["label"] != unicode(getLocalizedString(30218), 'utf-8'):
|
||||
fanart = os.path.join(ADDON_PATH, "fanart.jpg")
|
||||
listItem.setArt({'fanart': fanart})
|
||||
if item.get("context_menu"):
|
||||
# Translate context menus
|
||||
for m, menu in enumerate(item["context_menu"]):
|
||||
if menu[0][0:8] == "LOCALIZE":
|
||||
menu[0] = getLocalizedLabel(menu[0])
|
||||
listItem.addContextMenuItems(item["context_menu"])
|
||||
listItem.setProperty("isPlayable", item["is_playable"] and "true" or "false")
|
||||
if item.get("properties"):
|
||||
for k, v in item["properties"].items():
|
||||
listItem.setProperty(k, v)
|
||||
listitems[i] = (item["path"], listItem, not item["is_playable"])
|
||||
|
||||
xbmcplugin.addDirectoryItems(HANDLE, listitems, totalItems=len(listitems))
|
||||
|
||||
# Set ViewMode
|
||||
if data["content_type"]:
|
||||
viewMode = ADDON.getSetting("viewmode_%s" % data["content_type"])
|
||||
if viewMode:
|
||||
try:
|
||||
xbmc.executebuiltin('Container.SetViewMode(%s)' % viewMode)
|
||||
except Exception as e:
|
||||
log.warning("Unable to SetViewMode(%s): %s" % (viewMode, repr(e)))
|
||||
|
||||
xbmcplugin.endOfDirectory(HANDLE, succeeded=True, updateListing=False, cacheToDisc=True)
|
||||
56
plugin.video.alfa/lib/quasar/osarch.py
Normal file
56
plugin.video.alfa/lib/quasar/osarch.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import xbmc
|
||||
import sys
|
||||
import platform
|
||||
|
||||
def get_platform():
|
||||
build = xbmc.getInfoLabel("System.BuildVersion")
|
||||
kodi_version = int(build.split()[0][:2])
|
||||
ret = {
|
||||
"auto_arch": sys.maxsize > 2 ** 32 and "64-bit" or "32-bit",
|
||||
"arch": sys.maxsize > 2 ** 32 and "x64" or "x86",
|
||||
"os": "",
|
||||
"version": platform.release(),
|
||||
"kodi": kodi_version,
|
||||
"build": build
|
||||
}
|
||||
if xbmc.getCondVisibility("system.platform.android"):
|
||||
ret["os"] = "android"
|
||||
if "arm" in platform.machine() or "aarch" in platform.machine():
|
||||
ret["arch"] = "arm"
|
||||
if "64" in platform.machine() and ret["auto_arch"] == "64-bit":
|
||||
ret["arch"] = "arm"
|
||||
#ret["arch"] = "x64" #The binary is corrupted in install package
|
||||
elif xbmc.getCondVisibility("system.platform.linux"):
|
||||
ret["os"] = "linux"
|
||||
if "aarch" in platform.machine() or "arm64" in platform.machine():
|
||||
if xbmc.getCondVisibility("system.platform.linux.raspberrypi"):
|
||||
ret["arch"] = "armv7"
|
||||
elif ret["auto_arch"] == "32-bit":
|
||||
ret["arch"] = "armv7"
|
||||
elif ret["auto_arch"] == "64-bit":
|
||||
ret["arch"] = "arm64"
|
||||
elif platform.architecture()[0].startswith("32"):
|
||||
ret["arch"] = "arm"
|
||||
else:
|
||||
ret["arch"] = "arm64"
|
||||
elif "armv7" in platform.machine():
|
||||
ret["arch"] = "armv7"
|
||||
elif "arm" in platform.machine():
|
||||
ret["arch"] = "arm"
|
||||
elif xbmc.getCondVisibility("system.platform.xbox"):
|
||||
ret["os"] = "windows"
|
||||
ret["arch"] = "x64"
|
||||
elif xbmc.getCondVisibility("system.platform.windows"):
|
||||
ret["os"] = "windows"
|
||||
if platform.machine().endswith('64'):
|
||||
ret["arch"] = "x64"
|
||||
elif xbmc.getCondVisibility("system.platform.osx"):
|
||||
ret["os"] = "darwin"
|
||||
ret["arch"] = "x64"
|
||||
elif xbmc.getCondVisibility("system.platform.ios"):
|
||||
ret["os"] = "ios"
|
||||
ret["arch"] = "arm"
|
||||
return ret
|
||||
|
||||
|
||||
PLATFORM = get_platform()
|
||||
72
plugin.video.alfa/lib/quasar/util.py
Normal file
72
plugin.video.alfa/lib/quasar/util.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import platform
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
from quasar.logger import log
|
||||
from quasar.osarch import PLATFORM
|
||||
from quasar.addon import ADDON, ADDON_NAME, ADDON_ICON
|
||||
|
||||
|
||||
def notify(message, header=ADDON_NAME, time=5000, image=ADDON_ICON):
|
||||
sound = ADDON.getSetting('do_not_disturb') == 'false'
|
||||
dialog = xbmcgui.Dialog()
|
||||
return dialog.notification(toUtf8(header), toUtf8(message), toUtf8(image), time, sound)
|
||||
|
||||
def getLocalizedLabel(label):
|
||||
try:
|
||||
if "LOCALIZE" not in label:
|
||||
return label
|
||||
if ";;" not in label and label.endswith(']'):
|
||||
return getLocalizedString(int(label[9:-1]))
|
||||
else:
|
||||
parts = label.split(";;")
|
||||
translation = getLocalizedString(int(parts[0][9:14]))
|
||||
for i, part in enumerate(parts[1:]):
|
||||
if part[0:8] == "LOCALIZE":
|
||||
parts[i + 1] = getLocalizedString(int(part[9:14]))
|
||||
|
||||
return (translation.decode('utf-8', 'replace') % tuple(parts[1:])).encode('utf-8', 'ignore')
|
||||
except:
|
||||
return label
|
||||
|
||||
def getLocalizedString(stringId):
|
||||
try:
|
||||
return ADDON.getLocalizedString(stringId).encode('utf-8', 'ignore')
|
||||
except:
|
||||
return stringId
|
||||
|
||||
def toUtf8(string):
|
||||
if isinstance(string, unicode):
|
||||
return string.encode('utf-8', 'ignore')
|
||||
return string
|
||||
|
||||
def system_information():
|
||||
build = xbmc.getInfoLabel("System.BuildVersion")
|
||||
log.info("System information: %(os)s_%(arch)s %(version)s" % PLATFORM)
|
||||
log.info("Kodi build version: %s" % build)
|
||||
log.info("OS type: %s" % platform.system())
|
||||
log.info("uname: %s" % repr(platform.uname()))
|
||||
return PLATFORM
|
||||
|
||||
def getShortPath(path):
|
||||
if PLATFORM["os"] == "windows":
|
||||
return getWindowsShortPath(path)
|
||||
return path
|
||||
|
||||
def getWindowsShortPath(path):
|
||||
try:
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
|
||||
ctypes.windll.kernel32.GetShortPathNameW.argtypes = [
|
||||
ctypes.wintypes.LPCWSTR, # lpszLongPath
|
||||
ctypes.wintypes.LPWSTR, # lpszShortPath
|
||||
ctypes.wintypes.DWORD # cchBuffer
|
||||
]
|
||||
ctypes.windll.kernel32.GetShortPathNameW.restype = ctypes.wintypes.DWORD
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024) # adjust buffer size, if necessary
|
||||
ctypes.windll.kernel32.GetShortPathNameW(path, buf, len(buf))
|
||||
|
||||
return buf.value
|
||||
except:
|
||||
return path
|
||||
@@ -5,8 +5,11 @@
|
||||
|
||||
import os
|
||||
import json
|
||||
import traceback
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
|
||||
from platformcode import config, logger
|
||||
from platformcode import config, logger, platformtools
|
||||
|
||||
from core import jsontools
|
||||
from core import filetools
|
||||
@@ -54,6 +57,15 @@ def init():
|
||||
"""
|
||||
|
||||
try:
|
||||
#QUASAR: Preguntamos si se hacen modificaciones a Quasar
|
||||
if not filetools.exists(os.path.join(config.get_data_path(), "quasar.json")) and not config.get_setting('addon_quasar_update', default=False):
|
||||
question_update_external_addon("quasar")
|
||||
|
||||
#QUASAR: Hacemos las modificaciones a Quasar, si está permitido, y si está instalado
|
||||
if config.get_setting('addon_quasar_update', default=False):
|
||||
if not update_external_addon("quasar"):
|
||||
platformtools.dialog_notification("Actualización Quasar", "Ha fallado. Consulte el log")
|
||||
|
||||
#Existe carpeta "custom_code" ? Si no existe se crea y se sale
|
||||
custom_code_dir = os.path.join(config.get_data_path(), 'custom_code')
|
||||
if os.path.exists(custom_code_dir) == False:
|
||||
@@ -70,7 +82,7 @@ def init():
|
||||
#Se verifica si la versión del .json y del add-on son iguales. Si es así se sale. Si no se copia "custom_code" al add-on
|
||||
verify_copy_folders(custom_code_dir, custom_code_json_path)
|
||||
except:
|
||||
pass
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def create_folder_structure(custom_code_dir):
|
||||
@@ -88,11 +100,11 @@ def create_folder_structure(custom_code_dir):
|
||||
return
|
||||
|
||||
|
||||
def create_json(custom_code_json_path):
|
||||
def create_json(custom_code_json_path, json_name=json_data_file_name):
|
||||
logger.info()
|
||||
|
||||
#Guardamaos el json con la versión de Alfa vacía, para permitir hacer la primera copia
|
||||
json_data_file = filetools.join(custom_code_json_path, json_data_file_name)
|
||||
json_data_file = filetools.join(custom_code_json_path, json_name)
|
||||
json_file = open(json_data_file, "a+")
|
||||
json_file.write(json.dumps({"addon_version": ""}))
|
||||
json_file.close()
|
||||
@@ -123,3 +135,55 @@ def verify_copy_folders(custom_code_dir, custom_code_json_path):
|
||||
filetools.write(json_data_file, jsontools.dump(json_data))
|
||||
|
||||
return
|
||||
|
||||
|
||||
def question_update_external_addon(addon_name):
|
||||
logger.info(addon_name)
|
||||
|
||||
#Verificamos que el addon está instalado
|
||||
stat = False
|
||||
if xbmc.getCondVisibility('System.HasAddon("plugin.video.%s")' % addon_name):
|
||||
#Si es la primera vez que se pregunta por la actualización del addon externo, recogemos la respuesta,
|
||||
# guardaos un .json en userdat/alfa para no volver a preguntar otra vez, y se actualiza el setting en Alfa.
|
||||
stat = platformtools.dialog_yesno('Actualización de %s' % addon_name.capitalize(), '¿Quiere que actualicemos Quasar para que sea compatible con las últimas versiones de Kodi? (recomendado: SÍ)', '', 'Si actualiza Quasar, reinicie Kodi en un par de minutos')
|
||||
|
||||
#Con la respuesta actualizamos la variable en Alfa settings.xml. Se puede cambiar en Ajustes de Alfa, Otros
|
||||
if stat:
|
||||
config.set_setting('addon_quasar_update', True)
|
||||
else:
|
||||
config.set_setting('addon_quasar_update', False)
|
||||
|
||||
#Creamos un .json en userdata para no volver a preguntar otra vez
|
||||
create_json(config.get_data_path(), "%s.json" % addon_name)
|
||||
|
||||
return stat
|
||||
|
||||
def update_external_addon(addon_name):
|
||||
logger.info(addon_name)
|
||||
|
||||
#Verificamos que el addon está instalado
|
||||
if xbmc.getCondVisibility('System.HasAddon("plugin.video.%s")' % addon_name):
|
||||
#Path de actuali<aciones de Alfa
|
||||
alfa_addon_updates = filetools.join(config.get_runtime_path(), filetools.join("lib", addon_name))
|
||||
|
||||
#Path de destino en addon externo
|
||||
__settings__ = xbmcaddon.Addon(id="plugin.video." + addon_name)
|
||||
if addon_name.lower() in ['quasar', 'elementum']:
|
||||
addon_path = filetools.join(xbmc.translatePath(__settings__.getAddonInfo('Path')), filetools.join("resources", filetools.join("site-packages", addon_name)))
|
||||
else:
|
||||
addon_path = ''
|
||||
|
||||
#Hay modificaciones en Alfa? Las copiamos al addon
|
||||
if filetools.exists(alfa_addon_updates) and filetools.exists(addon_path):
|
||||
for root, folders, files in os.walk(alfa_addon_updates):
|
||||
for file in files:
|
||||
input_file = filetools.join(root, file)
|
||||
output_file = input_file.replace(alfa_addon_updates, addon_path)
|
||||
if filetools.copy(input_file, output_file, silent=True) == False:
|
||||
logger.error('Error en la copia: Input: %s o Output: %s' % (input_file, output_file))
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
logger.error('Alguna carpeta no existe: Alfa: %s o %s: %s' % (alfa_addon_updates, addon_name, addon_path))
|
||||
|
||||
return False
|
||||
@@ -124,9 +124,13 @@
|
||||
<setting type="sep"/>
|
||||
<setting label="Gestión de actualizaciones urgentes de módulos de Alfa (Quick Fixes):" type="lsep"/>
|
||||
<setting id="addon_update_timer" type="labelenum" values="0|6|12|24" label="Intervalo entre actualizaciones automáticas (horas)" default="12"/>
|
||||
<setting id="addon_update_message" type="bool" label="Quiere ver mensajes de las actualizaciones" default="false"/>
|
||||
<setting id="addon_update_message" type="bool" label="¿Quiere ver mensajes de las actualizaciones?" default="false"/>
|
||||
|
||||
<setting label="Lista activa" type="text" id="lista_activa" default="alfavorites-default.json" visible="false"/>
|
||||
|
||||
<setting type="sep"/>
|
||||
<setting label="Gestión de actualizaciones de otros addon relacionados con Alfa:" type="lsep"/>
|
||||
<setting id="addon_quasar_update" type="bool" label="¿Quiere actualizar Quasar para evitar errores?" default="false"/>
|
||||
</category>
|
||||
|
||||
</settings>
|
||||
|
||||
Reference in New Issue
Block a user