Commit eca1d42f authored by echel0n's avatar echel0n
Browse files

Refactored naming convention of misc helper functions.

Added ability to disable stripping of special permissions from files.
parent 63f98ef0
# Changelog # Changelog
- * fec82ca - 2018-11-11: Cleaned up subtitles code. - * 23957a3 - 2018-11-11: Refactored naming convention of misc helper functions. Added ability to disable stripping of special permissions from files.
- * 63f98ef - 2018-11-11: Cleaned up subtitles code.
- * 1558dce - 2018-11-10: Fixed issue with displaying language flags in views - * 1558dce - 2018-11-10: Fixed issue with displaying language flags in views
- * 0173a0f - 2018-11-10: Release v9.4.28 - * 0173a0f - 2018-11-10: Release v9.4.28
- * c5ea143 - 2018-11-10: Release v9.4.27 - * c5ea143 - 2018-11-10: Release v9.4.27
......
...@@ -204,13 +204,13 @@ class Core(object): ...@@ -204,13 +204,13 @@ class Core(object):
# migrate old database file names to new ones # migrate old database file names to new ones
if os.path.isfile(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db'))): if os.path.isfile(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db'))):
if os.path.isfile(os.path.join(self.data_dir, 'sickrage.db')): if os.path.isfile(os.path.join(self.data_dir, 'sickrage.db')):
helpers.moveFile(os.path.join(self.data_dir, 'sickrage.db'), helpers.move_file(os.path.join(self.data_dir, 'sickrage.db'),
os.path.join(self.data_dir, '{}.bak-{}' os.path.join(self.data_dir, '{}.bak-{}'
.format('sickrage.db', .format('sickrage.db',
datetime.datetime.now().strftime( datetime.datetime.now().strftime(
'%Y%m%d_%H%M%S')))) '%Y%m%d_%H%M%S'))))
helpers.moveFile(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db')), helpers.move_file(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db')),
os.path.abspath(os.path.join(self.data_dir, 'sickrage.db'))) os.path.abspath(os.path.join(self.data_dir, 'sickrage.db')))
# load config # load config
......
...@@ -28,7 +28,7 @@ from hachoir_metadata import extractMetadata ...@@ -28,7 +28,7 @@ from hachoir_metadata import extractMetadata
from hachoir_parser import guessParser from hachoir_parser import guessParser
import sickrage import sickrage
from sickrage.core.helpers import copyFile from sickrage.core.helpers import copy_file
from sickrage.metadata import GenericMetadata from sickrage.metadata import GenericMetadata
...@@ -226,7 +226,7 @@ class ImageCache(object): ...@@ -226,7 +226,7 @@ class ImageCache(object):
os.makedirs(self._thumbnails_dir()) os.makedirs(self._thumbnails_dir())
sickrage.app.log.info("Copying from " + image_path + " to " + dest_path) sickrage.app.log.info("Copying from " + image_path + " to " + dest_path)
copyFile(image_path, dest_path) copy_file(image_path, dest_path)
return True return True
......
...@@ -433,6 +433,7 @@ class Config(object): ...@@ -433,6 +433,7 @@ class Config(object):
self.calendar_icons = False self.calendar_icons = False
self.no_restart = False self.no_restart = False
self.allowed_video_file_exts = [] self.allowed_video_file_exts = []
self.strip_special_file_bits = False
self.thetvdb_apitoken = "" self.thetvdb_apitoken = ""
self.trakt_api_key = '5c65f55e11d48c35385d9e8670615763a605fad28374c8ae553a7b7a50651ddd' self.trakt_api_key = '5c65f55e11d48c35385d9e8670615763a605fad28374c8ae553a7b7a50651ddd'
self.trakt_api_secret = 'b53e32045ac122a445ef163e6d859403301ffe9b17fb8321d428531b69022a82' self.trakt_api_secret = 'b53e32045ac122a445ef163e6d859403301ffe9b17fb8321d428531b69022a82'
...@@ -830,7 +831,8 @@ class Config(object): ...@@ -830,7 +831,8 @@ class Config(object):
'keep_processed_dir': True, 'keep_processed_dir': True,
'processor_follow_symlinks': False, 'processor_follow_symlinks': False,
'allowed_extensions': 'srt,nfo,srr,sfv', 'allowed_extensions': 'srt,nfo,srr,sfv',
'view_changelog': False 'view_changelog': False,
'strip_special_file_bits': True
}, },
'NZBget': { 'NZBget': {
'nzbget_host': '', 'nzbget_host': '',
...@@ -1517,6 +1519,7 @@ class Config(object): ...@@ -1517,6 +1519,7 @@ class Config(object):
self.random_user_agent = self.check_setting_bool('General', 'random_user_agent') self.random_user_agent = self.check_setting_bool('General', 'random_user_agent')
self.allowed_extensions = self.check_setting_str('General', 'allowed_extensions') self.allowed_extensions = self.check_setting_str('General', 'allowed_extensions')
self.view_changelog = self.check_setting_bool('General', 'view_changelog') self.view_changelog = self.check_setting_bool('General', 'view_changelog')
self.strip_special_file_bits = self.check_setting_bool('General', 'strip_special_file_bits')
# GUI SETTINGS # GUI SETTINGS
self.gui_lang = self.check_setting_str('GUI', 'gui_lang') self.gui_lang = self.check_setting_str('GUI', 'gui_lang')
...@@ -2018,7 +2021,8 @@ class Config(object): ...@@ -2018,7 +2021,8 @@ class Config(object):
'processor_follow_symlinks': int(self.processor_follow_symlinks), 'processor_follow_symlinks': int(self.processor_follow_symlinks),
'delete_non_associated_files': int(self.delete_non_associated_files), 'delete_non_associated_files': int(self.delete_non_associated_files),
'allowed_extensions': self.allowed_extensions, 'allowed_extensions': self.allowed_extensions,
'view_changelog': int(self.view_changelog) 'view_changelog': int(self.view_changelog),
'strip_special_file_bits': int(self.strip_special_file_bits)
}, },
'GUI': { 'GUI': {
'gui_lang': self.gui_lang, 'gui_lang': self.gui_lang,
......
...@@ -21,6 +21,7 @@ from __future__ import unicode_literals ...@@ -21,6 +21,7 @@ from __future__ import unicode_literals
import base64 import base64
import ctypes import ctypes
import datetime import datetime
import errno
import io import io
import os import os
import platform import platform
...@@ -435,39 +436,42 @@ def list_media_files(path): ...@@ -435,39 +436,42 @@ def list_media_files(path):
return files return files
def copyFile(srcFile, destFile): def copy_file(src_file, dest_file):
""" """
Copy a file from source to destination Copy a file from source to destination
:param srcFile: Path of source file :param src_file: Path of source file
:param destFile: Path of destination file :param dest_file: Path of destination file
""" """
try: try:
shutil.copyfile(srcFile, destFile) shutil.copyfile(src_file, dest_file)
except Exception as e: except OSError as e:
sickrage.app.log.warning(str(e)) if e.errno == errno.ENOSPC:
sickrage.app.log.warning(e)
else:
sickrage.app.log.error(e)
else: else:
try: try:
shutil.copymode(srcFile, destFile) shutil.copymode(src_file, dest_file)
except OSError: except OSError:
pass pass
def moveFile(srcFile, destFile): def move_file(src_file, dest_file):
""" """
Move a file from source to destination Move a file from source to destination
:param srcFile: Path of source file :param src_file: Path of source file
:param destFile: Path of destination file :param dest_file: Path of destination file
""" """
try: try:
shutil.move(srcFile, destFile) shutil.move(src_file, dest_file)
fixSetGroupID(destFile) fix_set_group_id(dest_file)
except OSError: except OSError:
copyFile(srcFile, destFile) copy_file(src_file, dest_file)
os.unlink(srcFile) os.unlink(src_file)
def link(src, dst): def link(src, dst):
...@@ -486,21 +490,27 @@ def link(src, dst): ...@@ -486,21 +490,27 @@ def link(src, dst):
os.link(src, dst) os.link(src, dst)
def hardlinkFile(srcFile, destFile): def hardlink_file(src_file, dest_file):
""" """
Create a hard-link (inside filesystem link) between source and destination Create a hard-link (inside filesystem link) between source and destination
:param srcFile: Source file :param src_file: Source file
:param destFile: Destination file :param dest_file: Destination file
""" """
try: try:
link(srcFile, destFile) link(src_file, dest_file)
fixSetGroupID(destFile) fix_set_group_id(dest_file)
except Exception as e: except OSError as e:
sickrage.app.log.warning("Failed to create hardlink of %s at %s. Error: %r. Copying instead" if e.errno == errno.EEXIST:
% (srcFile, destFile, e)) # File exists. Don't fallback to copy
copyFile(srcFile, destFile) sickrage.app.log.warning('Failed to create hardlink of {src} at {dest}. Error: {error!r}'.format(
**{'src': src_file, 'dest': dest_file, 'error': e}))
else:
sickrage.app.log.warning(
"Failed to create hardlink of {src} at {dest}. Error: {error!r}. Copying instead".format(
**{'src': src_file, 'dest': dest_file, 'error': e}))
copy_file(src_file, dest_file)
def symlink(src, dst): def symlink(src, dst):
...@@ -519,23 +529,28 @@ def symlink(src, dst): ...@@ -519,23 +529,28 @@ def symlink(src, dst):
os.symlink(src, dst) os.symlink(src, dst)
def moveAndSymlinkFile(srcFile, destFile): def move_and_symlink_file(src_file, dest_file):
""" """
Move a file from source to destination, then create a symlink back from destination from source. If this fails, copy Move a file from source to destination, then create a symlink back from destination from source. If this fails, copy
the file from source to destination the file from source to destination
:param srcFile: Source file :param src_file: Source file
:param destFile: Destination file :param dest_file: Destination file
""" """
try: try:
shutil.move(srcFile, destFile) shutil.move(src_file, dest_file)
fixSetGroupID(destFile) fix_set_group_id(dest_file)
symlink(destFile, srcFile) symlink(dest_file, src_file)
except Exception as e: except OSError as e:
sickrage.app.log.warning("Failed to create symlink of %s at %s. Error: %r. Copying instead" if e.errno == errno.EEXIST:
% (srcFile, destFile, e)) # File exists. Don't fallback to copy
copyFile(srcFile, destFile) sickrage.app.log.warning('Failed to create symlink of {src} at {dest}. Error: {error!r}'.format(
**{'src': src_file, 'dest': dest_file, 'error': e}))
else:
sickrage.app.log.warning("Failed to create symlink of {src} at {dest}. Error: {error!r}. Copying instead".format(
**{'src': src_file, 'dest': dest_file, 'error': e}))
copy_file(src_file, dest_file)
def make_dirs(path): def make_dirs(path):
...@@ -573,7 +588,7 @@ def make_dirs(path): ...@@ -573,7 +588,7 @@ def make_dirs(path):
sickrage.app.log.debug("Folder %s didn't exist, creating it" % sofar) sickrage.app.log.debug("Folder %s didn't exist, creating it" % sofar)
os.mkdir(sofar) os.mkdir(sofar)
# use normpath to remove end separator, otherwise checks permissions against itself # use normpath to remove end separator, otherwise checks permissions against itself
chmodAsParent(os.path.normpath(sofar)) chmod_as_parent(os.path.normpath(sofar))
# do the library update for synoindex # do the library update for synoindex
sickrage.app.notifier_providers['synoindex'].addFolder(sofar) sickrage.app.notifier_providers['synoindex'].addFolder(sofar)
except (OSError, IOError) as e: except (OSError, IOError) as e:
...@@ -621,7 +636,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None): ...@@ -621,7 +636,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None):
pass pass
def fileBitFilter(mode): def file_bit_filter(mode):
""" """
Strip special filesystem bits from file Strip special filesystem bits from file
...@@ -636,9 +651,10 @@ def fileBitFilter(mode): ...@@ -636,9 +651,10 @@ def fileBitFilter(mode):
return mode return mode
def chmodAsParent(childPath): def chmod_as_parent(child_path):
""" """
Retain permissions of parent for childs Retain permissions of parent for childs
(Does not work for Windows hosts) (Does not work for Windows hosts)
:param childPath: Child Path to change permissions to sync from parent :param childPath: Child Path to change permissions to sync from parent
...@@ -647,82 +663,85 @@ def chmodAsParent(childPath): ...@@ -647,82 +663,85 @@ def chmodAsParent(childPath):
if os.name == 'nt' or os.name == 'ce': if os.name == 'nt' or os.name == 'ce':
return return
parentPath = os.path.dirname(childPath) parent_path = os.path.dirname(child_path)
if not parentPath: if not parent_path:
sickrage.app.log.debug("No parent path provided in " + childPath + ", unable to get permissions from it") sickrage.app.log.debug("No parent path provided in " + child_path + ", unable to get permissions from it")
return return
childPath = os.path.join(parentPath, os.path.basename(childPath)) child_path = os.path.join(parent_path, os.path.basename(child_path))
if not os.path.exists(child_path):
return
parentPathStat = os.stat(parentPath) parent_path_stat = os.stat(parent_path)
parentMode = stat.S_IMODE(parentPathStat[stat.ST_MODE]) parent_mode = stat.S_IMODE(parent_path_stat[stat.ST_MODE])
childPathStat = os.stat(childPath) child_path_stat = os.stat(child_path)
childPath_mode = stat.S_IMODE(childPathStat[stat.ST_MODE]) child_path_mode = stat.S_IMODE(child_path_stat[stat.ST_MODE])
if os.path.isfile(childPath): if os.path.isfile(child_path) and sickrage.app.config.strip_special_file_bits:
childMode = fileBitFilter(parentMode) child_mode = file_bit_filter(parent_mode)
else: else:
childMode = parentMode child_mode = parent_mode
if childPath_mode == childMode: if child_path_mode == child_mode:
return return
childPath_owner = childPathStat.st_uid child_path_owner = child_path_stat.st_uid
user_id = os.geteuid() user_id = os.geteuid()
if user_id != 0 and user_id != childPath_owner: if user_id not in (0, child_path_owner):
sickrage.app.log.debug("Not running as root or owner of " + childPath + ", not trying to set permissions") sickrage.app.log.debug("Not running as root or owner of " + child_path + ", not trying to set permissions")
return return
try: try:
os.chmod(childPath, childMode) os.chmod(child_path, child_mode)
sickrage.app.log.debug( sickrage.app.log.debug(
"Setting permissions for %s to %o as parent directory has %o" % (childPath, childMode, parentMode)) "Setting permissions for %s to %o as parent directory has %o" % (child_path, child_mode, parent_mode))
except OSError: except OSError:
sickrage.app.log.debug("Failed to set permission for %s to %o" % (childPath, childMode)) sickrage.app.log.debug("Failed to set permission for %s to %o" % (child_path, child_mode))
def fixSetGroupID(childPath): def fix_set_group_id(child_path):
""" """
Inherid SGID from parent Inherit SGID from parent
(does not work on Windows hosts) (does not work on Windows hosts)
:param childPath: Path to inherit SGID permissions from parent :param child_path: Path to inherit SGID permissions from parent
""" """
if os.name == 'nt' or os.name == 'ce': if os.name == 'nt' or os.name == 'ce':
return return
parentPath = os.path.dirname(childPath) parent_path = os.path.dirname(child_path)
parentStat = os.stat(parentPath) parent_stat = os.stat(parent_path)
parentMode = stat.S_IMODE(parentStat[stat.ST_MODE]) parent_mode = stat.S_IMODE(parent_stat[stat.ST_MODE])
childPath = os.path.join(parentPath, os.path.basename(childPath)) child_path = os.path.join(parent_path, os.path.basename(child_path))
if parentMode & stat.S_ISGID: if parent_mode & stat.S_ISGID:
parentGID = parentStat[stat.ST_GID] parent_gid = parent_stat[stat.ST_GID]
childStat = os.stat(childPath) child_stat = os.stat(child_path)
childGID = childStat[stat.ST_GID] child_gid = child_stat[stat.ST_GID]
if childGID == parentGID: if child_gid == parent_gid:
return return
childPath_owner = childStat.st_uid child_path_owner = child_stat.st_uid
user_id = os.geteuid() user_id = os.geteuid()
if user_id != 0 and user_id != childPath_owner: if user_id not in (0, child_path_owner):
sickrage.app.log.debug( sickrage.app.log.debug(
"Not running as root or owner of {}, not trying to set the set-group-ID".format(childPath)) "Not running as root or owner of {}, not trying to set the set-group-ID".format(child_path))
return return
try: try:
os.chown(childPath, -1, parentGID) # @UndefinedVariable - only available on UNIX os.chown(child_path, -1, parent_gid)
sickrage.app.log.debug("Respecting the set-group-ID bit on the parent directory for {}".format(childPath)) sickrage.app.log.debug("Respecting the set-group-ID bit on the parent directory for {}".format(child_path))
except OSError: except OSError:
sickrage.app.log.error("Failed to respect the set-group-ID bit on the parent directory for {} (setting " sickrage.app.log.error("Failed to respect the set-group-ID bit on the parent directory for {} (setting "
"group ID {})".format(childPath, parentGID)) "group ID {})".format(child_path, parent_gid))
def sanitizeSceneName(name, anime=False): def sanitizeSceneName(name, anime=False):
...@@ -948,7 +967,7 @@ def restoreConfigZip(archive, targetDir, restore_database=True, restore_config=T ...@@ -948,7 +967,7 @@ def restoreConfigZip(archive, targetDir, restore_database=True, restore_config=T
return tail or os.path.basename(head) return tail or os.path.basename(head)
bakFilename = '{0}-{1}'.format(path_leaf(targetDir), datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) bakFilename = '{0}-{1}'.format(path_leaf(targetDir), datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))
moveFile(targetDir, os.path.join(os.path.dirname(targetDir), bakFilename)) move_file(targetDir, os.path.join(os.path.dirname(targetDir), bakFilename))
with zipfile.ZipFile(archive, 'r', allowZip64=True) as zip_file: with zipfile.ZipFile(archive, 'r', allowZip64=True) as zip_file:
for member in zip_file.namelist(): for member in zip_file.namelist():
...@@ -1029,26 +1048,26 @@ def restoreSR(srcDir, dstDir): ...@@ -1029,26 +1048,26 @@ def restoreSR(srcDir, dstDir):
if os.path.exists(srcFile): if os.path.exists(srcFile):
if os.path.isfile(dstFile): if os.path.isfile(dstFile):
moveFile(dstFile, bakFile) move_file(dstFile, bakFile)
moveFile(srcFile, dstFile) move_file(srcFile, dstFile)
# databse # databse
if os.path.exists(os.path.join(srcDir, 'database')): if os.path.exists(os.path.join(srcDir, 'database')):
if os.path.exists(os.path.join(dstDir, 'database')): if os.path.exists(os.path.join(dstDir, 'database')):
moveFile(os.path.join(dstDir, 'database'), os.path.join(dstDir, '{}.bak-{}' move_file(os.path.join(dstDir, 'database'), os.path.join(dstDir, '{}.bak-{}'
.format('database', .format('database',
datetime.datetime.now().strftime( datetime.datetime.now().strftime(
'%Y%m%d_%H%M%S')))) '%Y%m%d_%H%M%S'))))
moveFile(os.path.join(srcDir, 'database'), dstDir) move_file(os.path.join(srcDir, 'database'), dstDir)
# cache # cache
if os.path.exists(os.path.join(srcDir, 'cache')): if os.path.exists(os.path.join(srcDir, 'cache')):
if os.path.exists(os.path.join(dstDir, 'cache')): if os.path.exists(os.path.join(dstDir, 'cache')):
moveFile(os.path.join(dstDir, 'cache'), os.path.join(dstDir, '{}.bak-{}' move_file(os.path.join(dstDir, 'cache'), os.path.join(dstDir, '{}.bak-{}'
.format('cache', .format('cache',
datetime.datetime.now().strftime( datetime.datetime.now().strftime(
'%Y%m%d_%H%M%S')))) '%Y%m%d_%H%M%S'))))
moveFile(os.path.join(srcDir, 'cache'), dstDir) move_file(os.path.join(srcDir, 'cache'), dstDir)
return True return True
except Exception as e: except Exception as e:
...@@ -1357,7 +1376,7 @@ def restoreVersionedFile(backup_file, version): ...@@ -1357,7 +1376,7 @@ def restoreVersionedFile(backup_file, version):
sickrage.app.log.debug("Trying to backup %s to %s.r%s before restoring backup" sickrage.app.log.debug("Trying to backup %s to %s.r%s before restoring backup"
% (new_file, new_file, version)) % (new_file, new_file, version))
moveFile(new_file, new_file + '.' + 'r' + str(version)) move_file(new_file, new_file + '.' + 'r' + str(version))
except Exception as e: except Exception as e:
sickrage.app.log.warning("Error while trying to backup file %s before proceeding with restore: %r" sickrage.app.log.warning("Error while trying to backup file %s before proceeding with restore: %r"
% (restore_file, e)) % (restore_file, e))
......
...@@ -31,7 +31,8 @@ from sickrage.core.common import Quality, ARCHIVED, DOWNLOADED ...@@ -31,7 +31,8 @@ from sickrage.core.common import Quality, ARCHIVED, DOWNLOADED
from sickrage.core.exceptions import EpisodeNotFoundException, EpisodePostProcessingFailedException, \ from sickrage.core.exceptions import EpisodeNotFoundException, EpisodePostProcessingFailedException, \
NoFreeSpaceException NoFreeSpaceException
from sickrage.core.helpers import findCertainShow, show_names, replaceExtension, makeDir, \ from sickrage.core.helpers import findCertainShow, show_names, replaceExtension, makeDir, \
chmodAsParent, moveFile, copyFile, hardlinkFile, moveAndSymlinkFile, remove_non_release_groups, remove_extension, \ chmod_as_parent, move_file, copy_file, hardlink_file, move_and_symlink_file, remove_non_release_groups, \
remove_extension, \
isFileLocked, verify_freespace, delete_empty_folders, make_dirs, symlink, is_rar_file, glob_escape isFileLocked, verify_freespace, delete_empty_folders, make_dirs, symlink, is_rar_file, glob_escape
from sickrage.core.nameparser import InvalidNameException, InvalidShowException, \ from sickrage.core.nameparser import InvalidNameException, InvalidShowException, \
NameParser NameParser
...@@ -365,7 +366,7 @@ class PostProcessor(object): ...@@ -365,7 +366,7 @@ class PostProcessor(object):
if not dir_exists: if not dir_exists:
sickrage.app.log.warning("Unable to create subtitles folder " + subs_new_path) sickrage.app.log.warning("Unable to create subtitles folder " + subs_new_path)
else: else:
chmodAsParent(subs_new_path) chmod_as_parent(subs_new_path)
new_file_path = os.path.join(subs_new_path, new_file_name) new_file_path = os.path.join(subs_new_path, new_file_name)
else: else