Merge branch 'feature/AddWebapiDiskspace' into dev

This commit is contained in:
JackDandy 2023-03-12 02:25:31 +00:00
commit 4dc131bc7d
4 changed files with 93 additions and 45 deletions

View file

@ -9,6 +9,12 @@
* Change codebase cleanups
* Change improve perf by using generators with `any`
* Change deprecate processEpisode used by nzbToMedia to advise how to configure API instead
* Change optionally add disk free space in response to three Web API endpoints
* Change increase API version number to 15
* Add actually use mount points to get disk free space
* Add optional "freespace" parameter to endpoints: sg.getrootdirs, sg.addrootdir, sg.deleterootdir
* Change update help of affected endpoints
* Fix explicitly save rootdirs after adding or deleting via Web API
[develop changelog]

View file

@ -70,11 +70,11 @@ addList("Command", "Help", "?cmd=help", "sg.functions-list", "","", "default");
addOption("sg.functions-list", "$k", "&subject=$k", "", "", "#echo ('sb', 'sg')['sg' in $k]#")
#end for
addList("Command", "SickBeard.AddRootDir", "?cmd=sb.addrootdir", "sb.addrootdir");
addList("Command", "SickGear.AddRootDir", "?cmd=sg.addrootdir", "sb.addrootdir");
addList("Command", "SickGear.AddRootDir", "?cmd=sg.addrootdir", "sg.addrootdir");
addOption("Command", "SickBeard.CheckScheduler", "?cmd=sb.checkscheduler");
addOption("Command", "SickGear.CheckScheduler", "?cmd=sg.checkscheduler");
addList("Command", "SickBeard.DeleteRootDir", "?cmd=sb.deleterootdir", "sb.deleterootdir");
addList("Command", "SickGear.DeleteRootDir", "?cmd=sg.deleterootdir", "sb.deleterootdir");
addList("Command", "SickGear.DeleteRootDir", "?cmd=sg.deleterootdir", "sg.deleterootdir");
addOption("Command", "SickBeard.ForceSearch", "?cmd=sb.forcesearch");
addList("Command", "SickGear.ForceSearch", "?cmd=sg.forcesearch", "sg.forcesearch");
addOption("Command", "SickGear.SearchQueue", "?cmd=sg.searchqueue");
@ -88,7 +88,7 @@ addList("Command", "SickGear.GetIndexers", "?cmd=sg.getindexers", "listindexers"
addList("Command", "SickGear.GetIndexerIcon", "?cmd=sg.getindexericon", "getindexericon");
addList("Command", "SickGear.GetNetworkIcon", "?cmd=sg.getnetworkicon", "getnetworkicon");
addOption("Command", "SickBeard.GetRootDirs", "?cmd=sb.getrootdirs");
addOption("Command", "SickGear.GetRootDirs", "?cmd=sg.getrootdirs");
addList("Command", "SickGear.GetRootDirs", "?cmd=sg.getrootdirs", "sg.addfreespace");
addList("Command", "SickBeard.PauseBacklog", "?cmd=sb.pausebacklog", "sb.pausebacklog");
addList("Command", "SickGear.PauseBacklog", "?cmd=sg.pausebacklog", "sb.pausebacklog");
addOption("Command", "SickBeard.Ping", "?cmd=sb.ping");
@ -621,10 +621,26 @@ addOption("sb.addrootdir-opt", "Optional Param", "", 1);
addOption("sb.addrootdir-opt", "Default", "&default=1");
addOption("sb.addrootdir-opt", "Not Default", "&default=0");
addOption("sb.deleterootdir", "C:\\Temp", "&location=C:\\Temp", "", 1);
addList("sg.addrootdir", "C:\\Temp", "&location=C:\\Temp", "sg.addrootdir-opt");
addList("sg.addrootdir", "/usr/bin", "&location=/usr/bin/", "sg.addrootdir-opt");
addList("sg.addrootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location", "sg.addrootdir-opt");
addList("sg.addrootdir-opt", "Optional Param", "", "sg.addfreespace");
addList("sg.addrootdir-opt", "Default", "&default=1", "sg.addfreespace");
addList("sg.addrootdir-opt", "Not Default", "&default=0", "sg.addfreespace");
addOption("sb.deleterootdir", "C:\\Temp", "&location=C:\\Temp", 1);
addOption("sb.deleterootdir", "/usr/bin", "&location=/usr/bin/");
addOption("sb.deleterootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location");
addList("sg.deleterootdir", "C:\\Temp", "&location=C:\\Temp", "sg.addfreespace");
addList("sg.deleterootdir", "/usr/bin", "&location=/usr/bin/", "sg.addfreespace");
addList("sg.deleterootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location", "sg.addfreespace");
addOption("sg.addfreespace", "Optional Param", "", 1)
addOption("sg.addfreespace", "incl Freespace", "&freespace=1")
addOption("sg.addfreespace", "excl Freespace", "&freespace=0")
#for $cur_show_obj in $sortedShowList:
addList("show.pause", "$cur_show_obj.name", "&indexerid=$cur_show_obj.prodid", "show.pause-opt");
#end for

View file

@ -34,7 +34,7 @@ import sickgear
from . import db, logger, notifiers
from .common import cpu_presets, mediaExtensions, Overview, Quality, statusStrings, subtitleExtensions, \
ARCHIVED, DOWNLOADED, FAILED, IGNORED, SKIPPED, SNATCHED_ANY, SUBTITLED, UNAIRED, UNKNOWN, WANTED
from .sgdatetime import SGDatetime
from .sgdatetime import SGDatetime
from lib.tvinfo_base.exceptions import *
from exceptions_helper import ex, MultipleShowObjectsException
@ -1031,7 +1031,7 @@ def clear_cache(force=False):
"""
# clean out cache directory, remove everything > 12 hours old
dirty = None
del_time = SGDatetime.timestamp_near(td=datetime.timedelta(hours=12))
del_time = SGDatetime.timestamp_near(td=datetime.timedelta(hours=12))
direntry_args = dict(follow_symlinks=False)
for direntry in scantree(sickgear.CACHE_DIR, ['images|rss|zoneinfo'], follow_symlinks=True):
if direntry.is_file(**direntry_args) and (force or del_time > direntry.stat(**direntry_args).st_mtime):
@ -1342,7 +1342,7 @@ def delete_not_changed_in(paths, days=30, minutes=0):
:param minutes: Purge files not modified in this number of minutes (default: 0 minutes)
:return: tuple; number of files that qualify for deletion, number of qualifying files that failed to be deleted
"""
del_time = SGDatetime.timestamp_near(td=datetime.timedelta(days=days, minutes=minutes))
del_time = SGDatetime.timestamp_near(td=datetime.timedelta(days=days, minutes=minutes))
errors = 0
qualified = 0
for cur_path in (paths, [paths])[not isinstance(paths, list)]:
@ -1367,7 +1367,7 @@ def set_file_timestamp(filename, min_age=3, new_time=None):
:param new_time:
:type new_time: None or int
"""
min_time = SGDatetime.timestamp_near(td=datetime.timedelta(days=min_age))
min_time = SGDatetime.timestamp_near(td=datetime.timedelta(days=min_age))
try:
if os.path.isfile(filename) and os.path.getmtime(filename) < min_time:
os.utime(filename, new_time)
@ -1412,6 +1412,19 @@ def is_link(filepath):
return os.path.islink(filepath)
def find_mount_point(path):
# type: (AnyStr) -> AnyStr
"""
returns the mount point for the given path
:param path: path to find the mount point
:return: mount point for path
"""
path = os.path.realpath(os.path.abspath(path))
while not os.path.ismount(path):
path = os.path.dirname(path)
return path
def df():
"""
Return disk free space at known parent locations
@ -1424,17 +1437,9 @@ def df():
if sickgear.ROOT_DIRS and sickgear.DISPLAY_FREESPACE:
targets = []
for path in sickgear.ROOT_DIRS.split('|')[1:]:
location_parts = os.path.splitdrive(path)
target = location_parts[0]
if 'win32' == sys.platform:
if not re.match('(?i)[a-z]:(?:\\\\)?$', target):
# simple drive letter not found, fallback to full path
target = path
min_output = False
elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform:
target = path
min_output = False
target = find_mount_point(path)
if target and target not in targets:
min_output = False
targets += [target]
free = freespace(path)
if None is not free:

View file

@ -43,7 +43,7 @@ from . import classes, db, helpers, history, image_cache, logger, network_timezo
from .common import ARCHIVED, DOWNLOADED, FAILED, IGNORED, SKIPPED, SNATCHED, SNATCHED_ANY, SNATCHED_BEST, \
SNATCHED_PROPER, UNAIRED, UNKNOWN, WANTED, Quality, qualityPresetStrings, statusStrings
from .name_parser.parser import NameParser
from .helpers import starify
from .helpers import df, find_mount_point, starify
from .indexers import indexer_api, indexer_config
from .indexers.indexer_config import *
from lib.tvinfo_base.exceptions import *
@ -150,7 +150,7 @@ else:
class Api(webserve.BaseHandler):
""" api class that returns json results """
version = 14 # use an int since float-point is unpredictable
version = 15 # use an int since float-point is unpredictable
def check_xsrf_cookie(self):
pass
@ -801,38 +801,45 @@ def _getQualityMap():
return quality_map_inversed
def _getRootDirs():
if "" == sickgear.ROOT_DIRS:
return {}
def _get_root_dirs(get_freespace=False):
# type: (bool) -> List[Dict]
"""
:param get_freespace: include disk free space info in response
"""
dir_list = []
if not sickgear.ROOT_DIRS:
return dir_list
rootDir = {}
root_dirs = sickgear.ROOT_DIRS.split('|')
default_index = int(sickgear.ROOT_DIRS.split('|')[0])
rootDir["default_index"] = int(sickgear.ROOT_DIRS.split('|')[0])
# remove default_index value from list (this fixes the offset)
root_dirs.pop(0)
default_index = int(root_dirs.pop(0))
if len(root_dirs) < default_index:
return {}
return dir_list
# clean up the list - replace %xx escapes by their single-character equivalent
root_dirs = [unquote_plus(x) for x in root_dirs]
default_dir = root_dirs[default_index]
dir_list = []
for root_dir in root_dirs:
valid = 1
if root_dirs and get_freespace:
diskfree, _ = df()
for cur_root_dir in root_dirs:
try:
os.listdir(root_dir)
os.listdir(cur_root_dir)
valid = 1
except (BaseException, Exception):
valid = 0
default = 0
if root_dir is default_dir:
default = 1
dir_list.append({'valid': valid, 'location': root_dir, 'default': default})
new_entry = {'valid': valid, 'location': cur_root_dir, 'default': int(cur_root_dir is default_dir)}
if get_freespace:
# noinspection PyUnboundLocalVariable
new_entry.update({'free_space': next((space for disk, space in diskfree or []
if disk == find_mount_point(cur_root_dir)), '')})
dir_list.append(new_entry)
return dir_list
@ -1975,7 +1982,8 @@ class CMD_SickGearAddRootDir(ApiCall):
_help = {"desc": "add a user configured parent directory",
"requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}
},
"optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"}
"optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"},
"freespace": {"desc": "include free space of paths in response"}
}
}
@ -1984,6 +1992,7 @@ class CMD_SickGearAddRootDir(ApiCall):
self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
# optional
self.default, args = self.check_params(args, kwargs, "default", 0, False, "bool", [])
self.freespace, args = self.check_params(args, kwargs, "freespace", 0, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
@ -2026,7 +2035,9 @@ class CMD_SickGearAddRootDir(ApiCall):
root_dirs_new = '|'.join([text_type(x) for x in root_dirs_new])
sickgear.ROOT_DIRS = root_dirs_new
return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directories updated")
sickgear.save_config()
return _responds(RESULT_SUCCESS, _get_root_dirs(not self.sickbeard_call and self.freespace),
msg="Root directories updated")
class CMD_SickBeardAddRootDir(CMD_SickGearAddRootDir):
@ -2084,20 +2095,24 @@ class CMD_SickBeardCheckScheduler(CMD_SickGearCheckScheduler):
class CMD_SickGearDeleteRootDir(ApiCall):
_help = {"desc": "delete a user configured parent directory",
"requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}}
"requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}},
"optionalParameters": {"freespace": {"desc": "include free space of paths in response"}
}
}
def __init__(self, handler, args, kwargs):
# required
self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
# optional
self.freespace, args = self.check_params(args, kwargs, "freespace", 0, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
""" delete a user configured parent directory """
if sickgear.ROOT_DIRS == "":
return _responds(RESULT_FAILURE, _getRootDirs(), msg="No root directories detected")
return _responds(RESULT_FAILURE, _get_root_dirs(not self.sickbeard_call and self.freespace),
msg="No root directories detected")
newIndex = 0
root_dirs_new = []
@ -2124,8 +2139,10 @@ class CMD_SickGearDeleteRootDir(ApiCall):
root_dirs_new = "|".join([text_type(x) for x in root_dirs_new])
sickgear.ROOT_DIRS = root_dirs_new
sickgear.save_config()
# what if the root dir was not found?
return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directory deleted")
return _responds(RESULT_SUCCESS, _get_root_dirs(not self.sickbeard_call and self.freespace),
msg="Root directory deleted")
class CMD_SickBeardDeleteRootDir(CMD_SickGearDeleteRootDir):
@ -2374,18 +2391,22 @@ class CMD_SickGearGetqualityStrings(ApiCall):
class CMD_SickGearGetRootDirs(ApiCall):
_help = {"desc": "get list of user configured parent directories"}
_help = {"desc": "get list of user configured parent directories",
"optionalParameters": {"freespace": {"desc": "include free space of paths in response"}
}
}
def __init__(self, handler, args, kwargs):
# required
# optional
self.freespace, args = self.check_params(args, kwargs, "freespace", 0, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
""" get list of user configured parent directories """
return _responds(RESULT_SUCCESS, _getRootDirs())
return _responds(RESULT_SUCCESS, _get_root_dirs(not self.sickbeard_call and self.freespace))
class CMD_SickBeardGetRootDirs(CMD_SickGearGetRootDirs):