455 lines
15 KiB
Python
455 lines
15 KiB
Python
# coding=utf-8
|
|
import datetime
|
|
import StringIO
|
|
import glob
|
|
import os
|
|
import traceback
|
|
import urlparse
|
|
|
|
from zipfile import ZipFile, ZIP_DEFLATED
|
|
|
|
from subzero.language import Language
|
|
|
|
from subzero.lib.io import FileIO
|
|
from subzero.constants import PREFIX, PLUGIN_IDENTIFIER
|
|
from menu_helpers import SubFolderObjectContainer, debounce, set_refresh_menu_state, ZipObject, ObjectContainer, route
|
|
from main import fatality
|
|
from support.helpers import timestamp, pad_title
|
|
from support.config import config
|
|
from support.lib import Plex
|
|
from support.storage import reset_storage, log_storage, get_subtitle_storage
|
|
from support.scheduler import scheduler
|
|
from support.items import set_mods_for_part, get_item_kind_from_rating_key
|
|
from support.i18n import _
|
|
|
|
|
|
@route(PREFIX + '/advanced')
|
|
def AdvancedMenu(randomize=None, header=None, message=None):
|
|
oc = SubFolderObjectContainer(
|
|
header=header or _("Internal stuff, pay attention!"),
|
|
message=message,
|
|
no_cache=True,
|
|
no_history=True,
|
|
replace_parent=False,
|
|
title2=_("Advanced"))
|
|
|
|
if config.lock_advanced_menu and not config.pin_correct:
|
|
oc.add(DirectoryObject(
|
|
key=Callback(
|
|
PinMenu,
|
|
randomize=timestamp(),
|
|
success_go_to=_("advanced")),
|
|
title=pad_title(_("Enter PIN")),
|
|
summary=_("The owner has restricted the access to this menu. Please enter the correct pin"),
|
|
))
|
|
return oc
|
|
|
|
oc.add(DirectoryObject(
|
|
key=Callback(TriggerRestart, randomize=timestamp()),
|
|
title=pad_title(_("Restart the plugin")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(GetLogsLink),
|
|
title=_("Get my logs (copy the appearing link and open it in your browser, please)"),
|
|
summary=_("Copy the appearing link and open it in your browser, please"),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(TriggerBetterSubtitles, randomize=timestamp()),
|
|
title=pad_title(_("Trigger find better subtitles")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(SkipFindBetterSubtitles, randomize=timestamp()),
|
|
title=pad_title(_("Skip next find better subtitles (sets last run to now)")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(SkipRecentlyAddedMissing, randomize=timestamp()),
|
|
title=pad_title(_("Skip next find recently added with missing subtitles (sets last run to now)")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(TriggerStorageMaintenance, randomize=timestamp()),
|
|
title=pad_title(_("Trigger subtitle storage maintenance")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(TriggerStorageMigration, randomize=timestamp()),
|
|
title=pad_title(_("Trigger subtitle storage migration (expensive)")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(TriggerCacheMaintenance, randomize=timestamp()),
|
|
title=pad_title(_("Trigger cache maintenance (refiners, providers and packs/archives)")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ApplyDefaultMods, randomize=timestamp()),
|
|
title=pad_title(_("Apply configured default subtitle mods to all (active) stored subtitles")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ReApplyMods, randomize=timestamp()),
|
|
title=pad_title(_("Re-Apply mods of all stored subtitles")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(LogStorage, key="tasks", randomize=timestamp()),
|
|
title=pad_title(_("Log the plugin's scheduled tasks state storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(LogStorage, key="ignore", randomize=timestamp()),
|
|
title=pad_title(_("Log the plugin's internal ignorelist storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(LogStorage, key=None, randomize=timestamp()),
|
|
title=pad_title(_("Log the plugin's complete state storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ResetStorage, key="tasks", randomize=timestamp()),
|
|
title=pad_title(_("Reset the plugin's scheduled tasks state storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ResetStorage, key="ignore", randomize=timestamp()),
|
|
title=pad_title(_("Reset the plugin's internal ignorelist storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ResetStorage, key="menu_history", randomize=timestamp()),
|
|
title=pad_title(_("Reset the plugin's menu history storage")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(InvalidateCache, randomize=timestamp()),
|
|
title=pad_title(_("Invalidate Sub-Zero metadata caches (subliminal)")),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(ResetProviderThrottle, randomize=timestamp()),
|
|
title=pad_title(_("Reset provider throttle states")),
|
|
))
|
|
return oc
|
|
|
|
|
|
def DispatchRestart():
|
|
Thread.CreateTimer(1.0, Restart)
|
|
|
|
|
|
@route(PREFIX + '/advanced/restart/trigger')
|
|
@debounce
|
|
def TriggerRestart(randomize=None):
|
|
set_refresh_menu_state(_("Restarting the plugin"))
|
|
DispatchRestart()
|
|
return fatality(
|
|
header=_("Restart triggered, please wait about 5 seconds"),
|
|
force_title=" ",
|
|
only_refresh=True,
|
|
replace_parent=True,
|
|
no_history=True,
|
|
randomize=timestamp())
|
|
|
|
|
|
@route(PREFIX + '/advanced/restart/execute')
|
|
@debounce
|
|
def Restart(randomize=None):
|
|
Plex[":/plugins"].restart(PLUGIN_IDENTIFIER)
|
|
|
|
|
|
@route(PREFIX + '/storage/reset', sure=bool)
|
|
@debounce
|
|
def ResetStorage(key, randomize=None, sure=False):
|
|
if not sure:
|
|
oc = SubFolderObjectContainer(
|
|
no_history=True,
|
|
title1=_("Reset subtitle storage"),
|
|
title2=_("Are you sure?"))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(
|
|
ResetStorage,
|
|
key=key,
|
|
sure=True,
|
|
randomize=timestamp()),
|
|
title=pad_title(_("Are you really sure?")),
|
|
|
|
))
|
|
return oc
|
|
|
|
reset_storage(key)
|
|
|
|
if key == "tasks":
|
|
# reinitialize the scheduler
|
|
scheduler.init_storage()
|
|
scheduler.setup_tasks()
|
|
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("Information Storage (%s) reset", key)
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/storage/log')
|
|
def LogStorage(key, randomize=None):
|
|
log_storage(key)
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("Information Storage (%s) logged", key)
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/triggerbetter')
|
|
@debounce
|
|
def TriggerBetterSubtitles(randomize=None):
|
|
scheduler.dispatch_task("FindBetterSubtitles")
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("FindBetterSubtitles triggered")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/skipbetter')
|
|
@debounce
|
|
def SkipFindBetterSubtitles(randomize=None):
|
|
task = scheduler.task("FindBetterSubtitles")
|
|
task.last_run = datetime.datetime.now()
|
|
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("FindBetterSubtitles skipped")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/skipram')
|
|
@debounce
|
|
def SkipRecentlyAddedMissing(randomize=None):
|
|
task = scheduler.task("SearchAllRecentlyAddedMissing")
|
|
task.last_run = datetime.datetime.now()
|
|
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("SearchAllRecentlyAddedMissing skipped")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/triggermaintenance')
|
|
@debounce
|
|
def TriggerStorageMaintenance(randomize=None):
|
|
scheduler.dispatch_task("SubtitleStorageMaintenance")
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("SubtitleStorageMaintenance triggered")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/triggerstoragemigration')
|
|
@debounce
|
|
def TriggerStorageMigration(randomize=None):
|
|
scheduler.dispatch_task("MigrateSubtitleStorage")
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("MigrateSubtitleStorage triggered")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/triggercachemaintenance')
|
|
@debounce
|
|
def TriggerCacheMaintenance(randomize=None):
|
|
scheduler.dispatch_task("CacheMaintenance")
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("TriggerCacheMaintenance triggered")
|
|
)
|
|
|
|
|
|
def apply_default_mods(reapply_current=False, scandir_generic=False):
|
|
storage = get_subtitle_storage()
|
|
subs_applied = 0
|
|
|
|
try:
|
|
for fn in storage.get_all_files(scandir_generic=scandir_generic):
|
|
data = storage.load(None, filename=fn)
|
|
if data:
|
|
video_id = data.video_id
|
|
item_type = get_item_kind_from_rating_key(video_id)
|
|
if not item_type:
|
|
continue
|
|
|
|
for part_id, part in data.parts.iteritems():
|
|
for lang, subs in part.iteritems():
|
|
current_sub = subs.get("current")
|
|
if not current_sub:
|
|
continue
|
|
sub = subs[current_sub]
|
|
|
|
if not sub.content:
|
|
continue
|
|
|
|
current_mods = sub.mods or []
|
|
if not reapply_current:
|
|
add_mods = list(set(config.default_mods).difference(set(current_mods)))
|
|
if not add_mods:
|
|
continue
|
|
else:
|
|
if not current_mods:
|
|
continue
|
|
add_mods = []
|
|
|
|
try:
|
|
set_mods_for_part(video_id, part_id, Language.fromietf(lang), item_type, add_mods, mode="add")
|
|
except:
|
|
Log.Error("Couldn't set mods for %s:%s: %s", video_id, part_id, traceback.format_exc())
|
|
continue
|
|
|
|
subs_applied += 1
|
|
except OSError:
|
|
return apply_default_mods(reapply_current=reapply_current, scandir_generic=True)
|
|
storage.destroy()
|
|
Log.Debug("Applied mods to %i items" % subs_applied)
|
|
|
|
|
|
@route(PREFIX + '/applydefaultmods')
|
|
@debounce
|
|
def ApplyDefaultMods(randomize=None):
|
|
Thread.CreateTimer(1.0, apply_default_mods)
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("This may take some time ...")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/reapplyallmods')
|
|
@debounce
|
|
def ReApplyMods(randomize=None):
|
|
Thread.CreateTimer(1.0, apply_default_mods, reapply_current=True)
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("This may take some time ...")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/get_logs_link')
|
|
def GetLogsLink():
|
|
if not config.plex_token:
|
|
oc = ObjectContainer(
|
|
title2=_("Download Logs"),
|
|
no_cache=True,
|
|
no_history=True,
|
|
header=_("Sorry, feature unavailable"),
|
|
message=_("Universal Plex token not available"))
|
|
return oc
|
|
|
|
# try getting the link base via the request in context, first, otherwise use the public ip
|
|
req_headers = Core.sandbox.context.request.headers
|
|
get_external_ip = True
|
|
link_base = ""
|
|
|
|
if "Origin" in req_headers:
|
|
link_base = req_headers["Origin"]
|
|
Log.Debug("Using origin-based link_base")
|
|
get_external_ip = False
|
|
|
|
elif "Referer" in req_headers:
|
|
parsed = urlparse.urlparse(req_headers["Referer"])
|
|
link_base = "%s://%s%s" % (parsed.scheme, parsed.hostname, (":%s" % parsed.port) if parsed.port else "")
|
|
Log.Debug("Using referer-based link_base")
|
|
get_external_ip = False
|
|
|
|
if get_external_ip or "plex.tv" in link_base:
|
|
ip = Core.networking.http_request("http://www.plexapp.com/ip.php", cacheTime=7200).content.strip()
|
|
link_base = "https://%s:32400" % ip
|
|
Log.Debug("Using ip-based fallback link_base")
|
|
|
|
logs_link = "%s%s?X-Plex-Token=%s" % (link_base, PREFIX + '/logs', config.plex_token)
|
|
oc = ObjectContainer(
|
|
title2=logs_link,
|
|
no_cache=True,
|
|
no_history=True,
|
|
header=_("Copy this link and open this in your browser, please"),
|
|
message=logs_link)
|
|
return oc
|
|
|
|
|
|
@route(PREFIX + '/logs')
|
|
def DownloadLogs():
|
|
buff = StringIO.StringIO()
|
|
zip_archive = ZipFile(buff, mode='w', compression=ZIP_DEFLATED)
|
|
|
|
logs = sorted(glob.glob(config.plugin_log_path + '*')) + [config.server_log_path]
|
|
for path in logs:
|
|
data = StringIO.StringIO()
|
|
data.write(FileIO.read(path))
|
|
zip_archive.writestr(os.path.basename(path), data.getvalue())
|
|
|
|
zip_archive.close()
|
|
|
|
return ZipObject(buff.getvalue())
|
|
|
|
|
|
@route(PREFIX + '/invalidatecache')
|
|
@debounce
|
|
def InvalidateCache(randomize=None):
|
|
from subliminal.cache import region
|
|
if config.new_style_cache:
|
|
region.backend.clear()
|
|
else:
|
|
region.invalidate()
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("Cache invalidated")
|
|
)
|
|
|
|
|
|
@route(PREFIX + '/pin')
|
|
def PinMenu(pin="", randomize=None, success_go_to="channel"):
|
|
oc = ObjectContainer(
|
|
title2=_("Enter PIN number ") + str(len(pin) + 1),
|
|
no_cache=True,
|
|
no_history=True,
|
|
skip_pin_lock=True)
|
|
|
|
if pin == config.pin:
|
|
Dict["pin_correct_time"] = datetime.datetime.now()
|
|
config.locked = False
|
|
if success_go_to == "channel":
|
|
return fatality(
|
|
force_title=_("PIN correct"),
|
|
header=_("PIN correct"),
|
|
no_history=True)
|
|
elif success_go_to == "advanced":
|
|
return AdvancedMenu(randomize=timestamp())
|
|
|
|
for i in range(10):
|
|
oc.add(DirectoryObject(
|
|
key=Callback(
|
|
PinMenu,
|
|
randomize=timestamp(),
|
|
pin=pin + str(i),
|
|
success_go_to=success_go_to),
|
|
title=pad_title(str(i)),
|
|
))
|
|
oc.add(DirectoryObject(
|
|
key=Callback(
|
|
PinMenu,
|
|
randomize=timestamp(),
|
|
success_go_to=success_go_to),
|
|
title=pad_title(_("Reset")),
|
|
))
|
|
return oc
|
|
|
|
|
|
@route(PREFIX + '/pin_lock')
|
|
def ClearPin(randomize=None):
|
|
Dict["pin_correct_time"] = None
|
|
config.locked = True
|
|
return fatality(force_title=_("Menu locked"), header=" ", no_history=True)
|
|
|
|
|
|
@route(PREFIX + '/reset_throttle')
|
|
def ResetProviderThrottle(randomize=None):
|
|
Dict["provider_throttle"] = {}
|
|
Dict.Save()
|
|
return AdvancedMenu(
|
|
randomize=timestamp(),
|
|
header=_("Success"),
|
|
message=_("Provider throttles reset")
|
|
)
|