Commit c8aeb224 authored by echel0n's avatar echel0n

Merge branch 'release/9.1.48'

parents ec907f74 b51b3121
# Changelog
- * 3923941 - 2017-10-21: Updated .gitignore file
- * b83ff0b - 2017-10-23: Release v9.1.48
- * 8c12317 - 2017-10-22: Refactored logo
- * 32e2fd7 - 2017-10-22: Refactored code
- * ec907f7 - 2017-10-21: Updated .gitignore file
- * 08384bf - 2017-10-21: Update Bug.md
- * b29dc99 - 2017-10-21: Update Bug.md
- * 37035a9 - 2017-10-21: Added .gitlab folder to gitignore file
......
......@@ -501,7 +501,7 @@ class Core(object):
for dbData in [x['doc'] for x in self.mainDB.db.all('tv_shows', with_doc=True)]:
try:
self.srLogger.debug("Loading data for show: [%s]", dbData['show_name'])
self.srLogger.debug("Loading data for show: [{}]".format(dbData['show_name']))
show = TVShow(int(dbData['indexer']), int(dbData['indexer_id']))
show.nextEpisode()
self.NAMECACHE.build(show)
......
......@@ -18,14 +18,10 @@
from __future__ import unicode_literals
import ast
import base64
import ctypes
import datetime
import hashlib
import httplib
import io
import operator
import os
import platform
import random
......@@ -38,7 +34,6 @@ import sys
import tempfile
import time
import traceback
import urllib2
import urlparse
import uuid
import webbrowser
......@@ -63,23 +58,6 @@ mediaExtensions = [
]
def get_remote_md5_sum(url, max_file_size=100 * 1024 * 1024):
remote = urllib2.urlopen(url)
hash = hashlib.md5()
total_read = 0
while True:
data = remote.read(4096)
total_read += 4096
if not data or total_read > max_file_size:
break
hash.update(data)
return hash.hexdigest()
def safe_getattr(object, name, default=None):
try:
return getattr(object, name, default)
......@@ -361,22 +339,6 @@ def isRarFile(filename):
return ret
def isBeingWritten(filepath):
"""
Check if file has been written in last 60 seconds
:param filepath: Filename to check
:return: True if file has been written recently, False if none
"""
# Return True if file was modified within 60 seconds. it might still be being written to.
ctime = max(os.path.getctime(filepath), os.path.getmtime(filepath))
if ctime > time.time() - 60:
return True
return False
def sanitizeFileName(name):
"""
>>> sanitizeFileName('a/b/c')
......@@ -771,42 +733,6 @@ def fixSetGroupID(childPath):
childPath, parentGID))
def is_anime_in_show_list():
"""
Check if any shows in list contain anime
:return: True if global showlist contains Anime, False if not
"""
for show in sickrage.srCore.SHOWLIST:
if show.is_anime:
return True
return False
def update_anime_support():
"""Check if we need to support anime, and if we do, enable the feature"""
sickrage.srCore.srConfig.ANIMESUPPORT = is_anime_in_show_list()
def get_all_episodes_from_absolute_number(show, absolute_numbers, indexer_id=None):
episodes = []
season = None
if len(absolute_numbers):
if not show and indexer_id:
show = findCertainShow(sickrage.srCore.SHOWLIST, indexer_id)
for absolute_number in absolute_numbers if show else []:
ep = show.getEpisode(None, None, absolute_number=absolute_number)
if ep:
episodes.append(ep.episode)
season = ep.season
return season, episodes
def sanitizeSceneName(name, anime=False):
"""
Takes a show name and returns the "scenified" version of it.
......@@ -836,39 +762,6 @@ def sanitizeSceneName(name, anime=False):
return name
_binOps = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.div,
ast.Mod: operator.mod
}
def arithmeticEval(s):
"""
A safe eval supporting basic arithmetic operations.
:param s: expression to evaluate
:return: value
"""
node = ast.parse(s, mode='eval')
def _eval(node):
if isinstance(node, ast.Expression):
return _eval(node.body)
elif isinstance(node, ast.Str):
return node.s
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.BinOp):
return _binOps[type(node.op)](_eval(node.left), _eval(node.right))
else:
raise Exception('Unsupported type {}'.format(node))
return _eval(node.body)
def create_https_certificates(ssl_cert, ssl_key):
"""This function takes a domain name as a parameter and then creates a certificate and key with the
domain name(replacing dots by underscores), finally signing the certificate using specified CA and
......@@ -930,22 +823,6 @@ def create_https_certificates(ssl_cert, ssl_key):
return True
def md5_for_file(filename):
"""
Generate an md5 hash for a file
:param filename: File to generate md5 hash for
:return MD5 hexdigest on success, or None on failure
"""
try:
md5 = hashlib.md5()
for byte in readFileBuffered(filename):
md5.update(byte)
return md5.hexdigest()
except Exception:
return None
def get_lan_ip():
"""Returns IP of system"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
......@@ -953,24 +830,6 @@ def get_lan_ip():
return s.getsockname()[0]
def check_url(url):
"""
Check if a URL exists without downloading the whole file.
We only check the URL header.
"""
# see also http://stackoverflow.com/questions/2924422
# http://stackoverflow.com/questions/1140661
good_codes = [httplib.OK, httplib.FOUND, httplib.MOVED_PERMANENTLY]
host, path = urlparse.urlparse(url)[1:3] # elems [1] and [2]
try:
conn = httplib.HTTPConnection(host)
conn.request('HEAD', path)
return conn.getresponse().status in good_codes
except StandardError:
return None
def anon_url(*url):
"""
Return a URL string consisting of the Anonymous redirect URL and an arbitrary number of values appended.
......@@ -1025,26 +884,7 @@ def real_path(path):
return os.path.normpath(os.path.normcase(os.path.realpath(path)))
def makeZip(fileList, archive):
"""
Create a ZIP of files
:param fileList: A list of file names - full path each name
:param archive: File name for the archive with a full path
"""
try:
a = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED, allowZip64=True)
for f in fileList:
a.write(f)
a.close()
return True
except Exception as e:
sickrage.srCore.srLogger.error("Zip creation error: %r " % repr(e))
return False
def extractZip(archive, targetDir):
def extract_zipfile(archive, targetDir):
"""
Unzip a file to a directory
......@@ -1075,7 +915,7 @@ def extractZip(archive, targetDir):
return False
def backupConfigZip(fileList, archive, arcname=None):
def create_zipfile(fileList, archive, arcname=None):
"""
Store the config file as a ZIP
......@@ -1134,7 +974,7 @@ def restoreConfigZip(archive, targetDir, restore_database=True, restore_config=T
shutil.rmtree(targetDir)
def backupSR(backupDir):
def backupSR(backupDir, keep_latest=False):
source = []
filesList = ['sickrage.db',
......@@ -1142,22 +982,42 @@ def backupSR(backupDir):
'cache.db',
os.path.basename(sickrage.CONFIG_FILE)]
def _keep_latest_backup():
import glob
_files = glob.glob(os.path.join(backupDir, '*.zip'))
now = time.time()
newest = _files[0], now - os.path.getctime(_files[0])
for f in _files[1:]:
age = now - os.path.getctime(f)
if age < newest[1]:
newest = f, age
_files.remove(newest[0])
for f in _files:
os.remove(f)
if keep_latest:
_keep_latest_backup()
# individual files
for f in filesList:
fp = os.path.join(sickrage.DATA_DIR, f)
if os.path.exists(fp):
source += [fp]
# database
for (path, dirs, files) in os.walk(os.path.join(sickrage.DATA_DIR, 'database'), topdown=True):
# database folder
for (path, __, files) in os.walk(os.path.join(sickrage.DATA_DIR, 'database'), topdown=True):
for filename in files:
source += [os.path.join(path, filename)]
# database backups
for (path, dirs, files) in os.walk(os.path.join(sickrage.DATA_DIR, 'db_backup'), topdown=True):
# db_backup folder
for (path, __, files) in os.walk(os.path.join(sickrage.DATA_DIR, 'db_backup'), topdown=True):
for filename in files:
source += [os.path.join(path, filename)]
# cache
# cache folder
if sickrage.CACHE_DIR:
for (path, dirs, files) in os.walk(sickrage.CACHE_DIR, topdown=True):
for dirname in dirs:
......@@ -1167,8 +1027,10 @@ def backupSR(backupDir):
for filename in files:
source += [os.path.join(path, filename)]
# ZIP filename
target = os.path.join(backupDir, 'sickrage-{}.zip'.format(datetime.datetime.now().strftime('%Y%m%d%H%M%S')))
return backupConfigZip(source, target, sickrage.DATA_DIR)
return create_zipfile(source, target, sickrage.DATA_DIR)
def restoreSR(srcDir, dstDir):
......@@ -1571,16 +1433,6 @@ def backupVersionedFile(old_file, version):
return True
def flatten_dict(d, delimiter='.'):
def expand(key, value):
if isinstance(value, dict):
return [(delimiter.join([key, k]), v) for k, v in flatten_dict(value, delimiter).items()]
else:
return [(key, value)]
return dict([item for k, v in d.items() for item in expand(k, v)])
@contextmanager
def bs4_parser(markup, features="html5lib", *args, **kwargs):
try:
......@@ -1603,8 +1455,7 @@ def getFileSize(file):
def get_temp_dir():
"""
Returns the [system temp dir]/thetvdb-u501 (or
thetvdb-myuser)
Returns the [system temp dir]/sickrage-u501 or sickrage-myuser
"""
import getpass
......
......@@ -28,8 +28,7 @@ from dateutil import parser
import sickrage
from sickrage.core.common import Quality
from sickrage.core.helpers import findCertainShow, full_sanitizeSceneName, get_all_episodes_from_absolute_number, \
remove_extension
from sickrage.core.helpers import findCertainShow, full_sanitizeSceneName, remove_extension
from sickrage.core.nameparser import regexes
from sickrage.core.scene_exceptions import get_scene_exception_by_name
from sickrage.core.scene_numbering import get_absolute_number_from_season_and_episode, get_indexer_absolute_numbering, \
......@@ -323,7 +322,7 @@ class NameParser(object):
bestResult.show.indexer, epAbsNo,
True, scene_season)
(s, e) = get_all_episodes_from_absolute_number(bestResult.show, [a])
(s, e) = bestResult.show.get_all_episodes_from_absolute_number([a])
new_absolute_numbers.append(a)
new_episode_numbers.extend(e)
......
......@@ -36,7 +36,8 @@ from configobj import ConfigObj
import sickrage
from sickrage.core.classes import srIntervalTrigger
from sickrage.core.common import SD, WANTED, SKIPPED, Quality
from sickrage.core.helpers import backupVersionedFile, makeDir, generateCookieSecret, auto_type, get_lan_ip, extractZip, \
from sickrage.core.helpers import backupVersionedFile, makeDir, generateCookieSecret, auto_type, get_lan_ip, \
extract_zipfile, \
try_int, checkbox_to_value
......@@ -301,7 +302,6 @@ class srConfig(object):
self.NMJ_HOST = None
self.NMJ_DATABASE = None
self.NMJ_MOUNT = None
self.ANIMESUPPORT = False
self.USE_ANIDB = False
self.ANIDB_USERNAME = None
self.ANIDB_PASSWORD = None
......@@ -998,7 +998,7 @@ class srConfig(object):
if (sickrage.srCore.srWebSession.download(
"https://sickrage.ca/downloads/unrar_win.zip", filename=unrar_zip,
) and extractZip(archive=unrar_zip, targetDir=unrar_dir)):
) and extract_zipfile(archive=unrar_zip, targetDir=unrar_dir)):
try:
os.remove(unrar_zip)
except OSError as e:
......
......@@ -25,6 +25,8 @@ import re
from logging import FileHandler, CRITICAL, DEBUG, ERROR, INFO, WARNING
from logging.handlers import RotatingFileHandler
from unidecode import unidecode
import sickrage
from sickrage.core import makeDir
......@@ -131,6 +133,7 @@ class srLogger(logging.getLoggerClass()):
# needed because Newznab apikey isn't stored as key=value in a section.
record.msg = re.sub(r"([&?]r|[&?]apikey|[&?]api_key)=[^&]*([&\w]?)", r"\1=**********\2", record.msg)
record.msg = unidecode(record.msg)
except:
pass
......
......@@ -40,8 +40,7 @@ from sickrage.core.common import Quality, SKIPPED, WANTED, UNKNOWN, DOWNLOADED,
UNAIRED, ARCHIVED, statusStrings, Overview, FAILED, SNATCHED_BEST
from sickrage.core.exceptions import MultipleShowObjectsException, ShowNotFoundException, \
EpisodeNotFoundException, EpisodeDeletedException, MultipleShowsInDatabaseException
from sickrage.core.helpers import list_media_files, isMediaFile, update_anime_support, findCertainShow, try_int, \
safe_getattr
from sickrage.core.helpers import list_media_files, isMediaFile, findCertainShow, try_int, safe_getattr
from sickrage.core.nameparser import NameParser, InvalidNameException, InvalidShowException
from sickrage.indexers import srIndexerApi
from sickrage.indexers.config import INDEXER_TVRAGE
......@@ -1337,8 +1336,6 @@ class TVShow(object):
except RecordNotFound:
sickrage.srCore.mainDB.db.insert(tv_show)
update_anime_support()
if self.imdbid and self.imdb_info:
try:
dbData = sickrage.srCore.mainDB.db.get('imdb_info', self.indexerid, with_doc=True)['doc']
......@@ -1559,6 +1556,19 @@ class TVShow(object):
return mapped
def get_all_episodes_from_absolute_number(self, absolute_numbers):
episodes = []
season = None
if len(absolute_numbers):
for absolute_number in absolute_numbers:
ep = self.getEpisode(absolute_number=absolute_number)
if ep:
episodes.append(ep.episode)
season = ep.season
return season, episodes
def __getstate__(self):
d = dict(self.__dict__)
del d['lock']
......@@ -1566,4 +1576,4 @@ class TVShow(object):
def __setstate__(self, d):
d['lock'] = threading.Lock()
self.__dict__.update(d)
self.__dict__.update(d)
\ No newline at end of file
......@@ -27,7 +27,6 @@ import stat
import subprocess
import tarfile
import threading
import time
import traceback
import sickrage
......@@ -85,7 +84,7 @@ class srVersionUpdater(object):
if not os.path.isdir(backupDir):
os.mkdir(backupDir)
if self._keeplatestbackup(backupDir) and backupSR(backupDir):
if backupSR(backupDir, keep_latest=True):
sickrage.srCore.srLogger.info("Config backup successful, updating...")
sickrage.srCore.srNotifications.message(_('Backup'), _('Config backup successful, updating...'))
return True
......@@ -98,29 +97,6 @@ class srVersionUpdater(object):
sickrage.srCore.srNotifications.message(_('Backup'), _('Config backup failed, aborting update'))
return False
@staticmethod
def _keeplatestbackup(backupDir=None):
if not backupDir:
return False
import glob
files = glob.glob(os.path.join(backupDir, '*.zip'))
if not files:
return True
now = time.time()
newest = files[0], now - os.path.getctime(files[0])
for f in files[1:]:
age = now - os.path.getctime(f)
if age < newest[1]:
newest = f, age
files.remove(newest[0])
for f in files:
os.remove(f)
return True
@staticmethod
def safe_to_update():
if sickrage.srCore.srConfig.DEVELOPER:
......
......@@ -128,7 +128,7 @@ class srWebServer(threading.Thread):
# favicon
(r'%s/(favicon\.ico)' % sickrage.srCore.srConfig.WEB_ROOT, StaticFileHandler,
{"path": os.path.join(sickrage.srCore.srConfig.GUI_STATIC_DIR, 'images/ico/favicon.ico')}),
{"path": os.path.join(sickrage.srCore.srConfig.GUI_STATIC_DIR, 'images/favicon.ico')}),
# images
(r'%s/images/(.*)' % sickrage.srCore.srConfig.WEB_ROOT, StaticImageHandler,
......
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="ms-icon-70x70.png"/><square150x150logo src="ms-icon-150x150.png"/><square310x310logo src="ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
{
"name": "App",
"icons": [
{
"src": "\/images\/ico\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/images\/ico\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/images\/ico\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/images\/ico\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/images\/ico\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/images\/ico\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
......@@ -65,20 +65,7 @@
data-content="${sickrage.srCore.srConfig.FANART_BACKGROUND_OPACITY}">
<%block name="metas" />
<link rel="apple-touch-icon" sizes="57x57" href="${srWebRoot}/images/ico/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="${srWebRoot}/images/ico/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="${srWebRoot}/images/ico/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="${srWebRoot}/images/ico/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="${srWebRoot}/images/ico/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="${srWebRoot}/images/ico/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="${srWebRoot}/images/ico/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="${srWebRoot}/images/ico/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="${srWebRoot}/images/ico/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="${srWebRoot}/images/ico/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="${srWebRoot}/images/ico/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="${srWebRoot}/images/ico/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="${srWebRoot}/images/ico/favicon-16x16.png">
<link rel="manifest" href="${srWebRoot}/images/ico/manifest.json">
<link rel="icon" type="image/png" sizes="32x32" href="${srWebRoot}/images/favicon.png">
% if sickrage.srCore.srConfig.GUI_LANG:
<link rel="gettext" type="application/json" href="${srWebRoot}/messages.json">
......@@ -105,7 +92,7 @@
<a class="navbar-brand" href="${srWebRoot}/home/">
<img alt="SiCKRAGE"
src="${srWebRoot}/images/logo.png"
style="width: 200px;height: 50px;"
style="width: 400px;height: 50px;"
class="img-responsive pull-left"/>
</a>
</div>
......
......@@ -6,7 +6,7 @@
<form action="" method="post">
<div class="row">
<div class="col-md-12" align="center">
<img src="${srWebRoot}/images/login.png" />
<img src="${srWebRoot}/images/login.png" style="width: 100%"/>
</div>
</div>
<div class="row">
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.