mirror of https://github.com/evilhero/mylar
4146 lines
186 KiB
Python
Executable File
4146 lines
186 KiB
Python
Executable File
# This file is part of Mylar.
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Mylar is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Mylar is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Mylar. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import time
|
|
from operator import itemgetter
|
|
import datetime
|
|
from datetime import timedelta, date
|
|
import subprocess
|
|
import requests
|
|
import shlex
|
|
import Queue
|
|
import json
|
|
import re
|
|
import sys
|
|
import ctypes
|
|
import platform
|
|
import calendar
|
|
import itertools
|
|
import shutil
|
|
import hashlib
|
|
import gzip
|
|
import os, errno
|
|
from StringIO import StringIO
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
|
|
import mylar
|
|
import logger
|
|
from mylar import db, sabnzbd, nzbget, process, getcomics
|
|
|
|
def multikeysort(items, columns):
|
|
|
|
comparers = [((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]
|
|
|
|
def comparer(left, right):
|
|
for fn, mult in comparers:
|
|
result = cmp(fn(left), fn(right))
|
|
if result:
|
|
return mult * result
|
|
else:
|
|
return 0
|
|
|
|
return sorted(items, cmp=comparer)
|
|
|
|
def checked(variable):
|
|
if variable:
|
|
return 'Checked'
|
|
else:
|
|
return ''
|
|
|
|
def radio(variable, pos):
|
|
|
|
if variable == pos:
|
|
return 'Checked'
|
|
else:
|
|
return ''
|
|
|
|
def latinToAscii(unicrap):
|
|
"""
|
|
From couch potato
|
|
"""
|
|
xlate = {0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A',
|
|
0xc6: 'Ae', 0xc7: 'C',
|
|
0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E', 0x86: 'e',
|
|
0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I',
|
|
0xd0: 'Th', 0xd1: 'N',
|
|
0xd2: 'O', 0xd3: 'O', 0xd4: 'O', 0xd5: 'O', 0xd6: 'O', 0xd8: 'O',
|
|
0xd9: 'U', 0xda: 'U', 0xdb: 'U', 0xdc: 'U',
|
|
0xdd: 'Y', 0xde: 'th', 0xdf: 'ss',
|
|
0xe0: 'a', 0xe1: 'a', 0xe2: 'a', 0xe3: 'a', 0xe4: 'a', 0xe5: 'a',
|
|
0xe6: 'ae', 0xe7: 'c',
|
|
0xe8: 'e', 0xe9: 'e', 0xea: 'e', 0xeb: 'e', 0x0259: 'e',
|
|
0xec: 'i', 0xed: 'i', 0xee: 'i', 0xef: 'i',
|
|
0xf0: 'th', 0xf1: 'n',
|
|
0xf2: 'o', 0xf3: 'o', 0xf4: 'o', 0xf5: 'o', 0xf6: 'o', 0xf8: 'o',
|
|
0xf9: 'u', 0xfa: 'u', 0xfb: 'u', 0xfc: 'u',
|
|
0xfd: 'y', 0xfe: 'th', 0xff: 'y',
|
|
0xa1: '!', 0xa2: '{cent}', 0xa3: '{pound}', 0xa4: '{currency}',
|
|
0xa5: '{yen}', 0xa6: '|', 0xa7: '{section}', 0xa8: '{umlaut}',
|
|
0xa9: '{C}', 0xaa: '{^a}', 0xab: '<<', 0xac: '{not}',
|
|
0xad: '-', 0xae: '{R}', 0xaf: '_', 0xb0: '{degrees}',
|
|
0xb1: '{+/-}', 0xb2: '{^2}', 0xb3: '{^3}', 0xb4: "'",
|
|
0xb5: '{micro}', 0xb6: '{paragraph}', 0xb7: '*', 0xb8: '{cedilla}',
|
|
0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>',
|
|
0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?',
|
|
0xd7: '*', 0xf7: '/'
|
|
}
|
|
|
|
r = ''
|
|
for i in unicrap:
|
|
if xlate.has_key(ord(i)):
|
|
r += xlate[ord(i)]
|
|
elif ord(i) >= 0x80:
|
|
pass
|
|
else:
|
|
r += str(i)
|
|
return r
|
|
|
|
def convert_milliseconds(ms):
|
|
|
|
seconds = ms /1000
|
|
gmtime = time.gmtime(seconds)
|
|
if seconds > 3600:
|
|
minutes = time.strftime("%H:%M:%S", gmtime)
|
|
else:
|
|
minutes = time.strftime("%M:%S", gmtime)
|
|
|
|
return minutes
|
|
|
|
def convert_seconds(s):
|
|
|
|
gmtime = time.gmtime(s)
|
|
if s > 3600:
|
|
minutes = time.strftime("%H:%M:%S", gmtime)
|
|
else:
|
|
minutes = time.strftime("%M:%S", gmtime)
|
|
|
|
return minutes
|
|
|
|
def today():
|
|
today = datetime.date.today()
|
|
yyyymmdd = datetime.date.isoformat(today)
|
|
return yyyymmdd
|
|
|
|
def now():
|
|
now = datetime.datetime.now()
|
|
return now.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
def utctimestamp():
|
|
return time.time()
|
|
|
|
def bytes_to_mb(bytes):
|
|
|
|
mb = int(bytes) /1048576
|
|
size = '%.1f MB' % mb
|
|
return size
|
|
|
|
def human_size(size_bytes):
|
|
"""
|
|
format a size in bytes into a 'human' file size, e.g. bytes, KB, MB, GB, TB, PB
|
|
Note that bytes/KB will be reported in whole numbers but MB and above will have greater precision
|
|
e.g. 1 byte, 43 bytes, 443 KB, 4.3 MB, 4.43 GB, etc
|
|
"""
|
|
if size_bytes == 1:
|
|
# because I really hate unnecessary plurals
|
|
return "1 byte"
|
|
|
|
suffixes_table = [('bytes', 0), ('KB', 0), ('MB', 1), ('GB', 2), ('TB', 2), ('PB', 2)]
|
|
|
|
num = float(0 if size_bytes is None else size_bytes)
|
|
for suffix, precision in suffixes_table:
|
|
if num < 1024.0:
|
|
break
|
|
num /= 1024.0
|
|
|
|
if precision == 0:
|
|
formatted_size = "%d" % num
|
|
else:
|
|
formatted_size = str(round(num, ndigits=precision))
|
|
|
|
return "%s %s" % (formatted_size, suffix)
|
|
|
|
def human2bytes(s):
|
|
"""
|
|
>>> human2bytes('1M')
|
|
1048576
|
|
>>> human2bytes('1G')
|
|
1073741824
|
|
"""
|
|
symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
|
|
letter = s[-1:].strip().upper()
|
|
num = re.sub(',', '', s[:-1])
|
|
#assert num.isdigit() and letter in symbols
|
|
#use below assert statement to handle sizes with decimal places
|
|
if num != '0':
|
|
assert float(num) and letter in symbols
|
|
num = float(num)
|
|
prefix = {symbols[0]: 1}
|
|
for i, s in enumerate(symbols[1:]):
|
|
prefix[s] = 1 << (i +1) *10
|
|
return int(num * prefix[letter])
|
|
else:
|
|
return 0
|
|
|
|
def replace_all(text, dic):
|
|
for i, j in dic.iteritems():
|
|
text = text.replace(i, j)
|
|
return text.rstrip()
|
|
|
|
def cleanName(string):
|
|
|
|
pass1 = latinToAscii(string).lower()
|
|
out_string = re.sub('[\/\@\#\$\%\^\*\+\"\[\]\{\}\<\>\=\_]', ' ', pass1).encode('utf-8')
|
|
|
|
return out_string
|
|
|
|
def cleanTitle(title):
|
|
|
|
title = re.sub('[\.\-\/\_]', ' ', title).lower()
|
|
|
|
# Strip out extra whitespace
|
|
title = ' '.join(title.split())
|
|
|
|
title = title.title()
|
|
|
|
return title
|
|
|
|
def extract_logline(s):
|
|
# Default log format
|
|
pattern = re.compile(r'(?P<timestamp>.*?)\s\-\s(?P<level>.*?)\s*\:\:\s(?P<thread>.*?)\s\:\s(?P<message>.*)', re.VERBOSE)
|
|
match = pattern.match(s)
|
|
if match:
|
|
timestamp = match.group("timestamp")
|
|
level = match.group("level")
|
|
thread = match.group("thread")
|
|
message = match.group("message")
|
|
return (timestamp, level, thread, message)
|
|
else:
|
|
return None
|
|
|
|
def is_number(s):
|
|
try:
|
|
float(s)
|
|
except (ValueError, TypeError):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def decimal_issue(iss):
|
|
iss_find = iss.find('.')
|
|
dec_except = None
|
|
if iss_find == -1:
|
|
#no matches for a decimal, assume we're converting from decimal to int.
|
|
#match for special issues with alphanumeric numbering...
|
|
if 'au' in iss.lower():
|
|
dec_except = 'AU'
|
|
decex = iss.lower().find('au')
|
|
deciss = int(iss[:decex]) * 1000
|
|
else:
|
|
deciss = int(iss) * 1000
|
|
else:
|
|
iss_b4dec = iss[:iss_find]
|
|
iss_decval = iss[iss_find +1:]
|
|
if int(iss_decval) == 0:
|
|
iss = iss_b4dec
|
|
issdec = int(iss_decval)
|
|
else:
|
|
if len(iss_decval) == 1:
|
|
iss = iss_b4dec + "." + iss_decval
|
|
issdec = int(iss_decval) * 10
|
|
else:
|
|
iss = iss_b4dec + "." + iss_decval.rstrip('0')
|
|
issdec = int(iss_decval.rstrip('0')) * 10
|
|
deciss = (int(iss_b4dec) * 1000) + issdec
|
|
return deciss, dec_except
|
|
|
|
def rename_param(comicid, comicname, issue, ofilename, comicyear=None, issueid=None, annualize=None, arc=False):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
comicid = str(comicid) # it's coming in unicoded...
|
|
|
|
logger.fdebug(type(comicid))
|
|
logger.fdebug(type(issueid))
|
|
logger.fdebug('comicid: %s' % comicid)
|
|
logger.fdebug('issue# as per cv: %s' % issue)
|
|
# the issue here is a non-decimalized version, we need to see if it's got a decimal and if not, add '.00'
|
|
# iss_find = issue.find('.')
|
|
# if iss_find < 0:
|
|
# # no decimal in issue number
|
|
# iss = str(int(issue)) + ".00"
|
|
# else:
|
|
# iss_b4dec = issue[:iss_find]
|
|
# iss_decval = issue[iss_find+1:]
|
|
# if len(str(int(iss_decval))) == 1:
|
|
# iss = str(int(iss_b4dec)) + "." + str(int(iss_decval)*10)
|
|
# else:
|
|
# if issue.endswith(".00"):
|
|
# iss = issue
|
|
# else:
|
|
# iss = str(int(iss_b4dec)) + "." + iss_decval
|
|
# issue = iss
|
|
|
|
# print ("converted issue#: " + str(issue))
|
|
logger.fdebug('issueid:' + str(issueid))
|
|
|
|
if issueid is None:
|
|
logger.fdebug('annualize is ' + str(annualize))
|
|
if arc:
|
|
#this has to be adjusted to be able to include story arc issues that span multiple arcs
|
|
chkissue = myDB.selectone("SELECT * from storyarcs WHERE ComicID=? AND Issue_Number=?", [comicid, issue]).fetchone()
|
|
else:
|
|
chkissue = myDB.selectone("SELECT * from issues WHERE ComicID=? AND Issue_Number=?", [comicid, issue]).fetchone()
|
|
if all([chkissue is None, annualize is None, not mylar.CONFIG.ANNUALS_ON]):
|
|
chkissue = myDB.selectone("SELECT * from annuals WHERE ComicID=? AND Issue_Number=?", [comicid, issue]).fetchone()
|
|
|
|
if chkissue is None:
|
|
#rechk chkissue against int value of issue #
|
|
if arc:
|
|
chkissue = myDB.selectone("SELECT * from storyarcs WHERE ComicID=? AND Int_IssueNumber=?", [comicid, issuedigits(issue)]).fetchone()
|
|
else:
|
|
chkissue = myDB.selectone("SELECT * from issues WHERE ComicID=? AND Int_IssueNumber=?", [comicid, issuedigits(issue)]).fetchone()
|
|
if all([chkissue is None, annualize == 'yes', mylar.CONFIG.ANNUALS_ON]):
|
|
chkissue = myDB.selectone("SELECT * from annuals WHERE ComicID=? AND Int_IssueNumber=?", [comicid, issuedigits(issue)]).fetchone()
|
|
|
|
if chkissue is None:
|
|
logger.error('Invalid Issue_Number - please validate.')
|
|
return
|
|
else:
|
|
logger.info('Int Issue_number compare found. continuing...')
|
|
issueid = chkissue['IssueID']
|
|
else:
|
|
issueid = chkissue['IssueID']
|
|
|
|
#use issueid to get publisher, series, year, issue number
|
|
logger.fdebug('issueid is now : ' + str(issueid))
|
|
if arc:
|
|
issuenzb = myDB.selectone("SELECT * from storyarcs WHERE ComicID=? AND IssueID=? AND StoryArc=?", [comicid, issueid, arc]).fetchone()
|
|
else:
|
|
issuenzb = myDB.selectone("SELECT * from issues WHERE ComicID=? AND IssueID=?", [comicid, issueid]).fetchone()
|
|
if issuenzb is None:
|
|
logger.fdebug('not an issue, checking against annuals')
|
|
issuenzb = myDB.selectone("SELECT * from annuals WHERE ComicID=? AND IssueID=?", [comicid, issueid]).fetchone()
|
|
if issuenzb is None:
|
|
logger.fdebug('Unable to rename - cannot locate issue id within db')
|
|
return
|
|
else:
|
|
annualize = True
|
|
|
|
if issuenzb is None:
|
|
logger.fdebug('Unable to rename - cannot locate issue id within db')
|
|
return
|
|
|
|
#remap the variables to a common factor.
|
|
if arc:
|
|
issuenum = issuenzb['IssueNumber']
|
|
issuedate = issuenzb['IssueDate']
|
|
publisher = issuenzb['IssuePublisher']
|
|
series = issuenzb['ComicName']
|
|
seriesfilename = series #Alternate FileNaming is not available with story arcs.
|
|
seriesyear = issuenzb['SeriesYear']
|
|
arcdir = filesafe(issuenzb['StoryArc'])
|
|
if mylar.CONFIG.REPLACE_SPACES:
|
|
arcdir = arcdir.replace(' ', mylar.CONFIG.REPLACE_CHAR)
|
|
if mylar.CONFIG.STORYARCDIR:
|
|
storyarcd = os.path.join(mylar.CONFIG.DESTINATION_DIR, "StoryArcs", arcdir)
|
|
logger.fdebug('Story Arc Directory set to : ' + storyarcd)
|
|
else:
|
|
logger.fdebug('Story Arc Directory set to : ' + mylar.CONFIG.GRABBAG_DIR)
|
|
storyarcd = os.path.join(mylar.CONFIG.DESTINATION_DIR, mylar.CONFIG.GRABBAG_DIR)
|
|
|
|
comlocation = storyarcd
|
|
comversion = None #need to populate this.
|
|
|
|
else:
|
|
issuenum = issuenzb['Issue_Number']
|
|
issuedate = issuenzb['IssueDate']
|
|
comicnzb= myDB.selectone("SELECT * from comics WHERE comicid=?", [comicid]).fetchone()
|
|
publisher = comicnzb['ComicPublisher']
|
|
series = comicnzb['ComicName']
|
|
if comicnzb['AlternateFileName'] is None or comicnzb['AlternateFileName'] == 'None':
|
|
seriesfilename = series
|
|
else:
|
|
seriesfilename = comicnzb['AlternateFileName']
|
|
logger.fdebug('Alternate File Naming has been enabled for this series. Will rename series title to : ' + seriesfilename)
|
|
seriesyear = comicnzb['ComicYear']
|
|
comlocation = comicnzb['ComicLocation']
|
|
comversion = comicnzb['ComicVersion']
|
|
|
|
unicodeissue = issuenum
|
|
|
|
if type(issuenum) == unicode:
|
|
vals = {u'\xbd':'.5',u'\xbc':'.25',u'\xbe':'.75',u'\u221e':'9999999999',u'\xe2':'9999999999'}
|
|
else:
|
|
vals = {'\xbd':'.5','\xbc':'.25','\xbe':'.75','\u221e':'9999999999','\xe2':'9999999999'}
|
|
x = [vals[key] for key in vals if key in issuenum]
|
|
if x:
|
|
issuenum = x[0]
|
|
logger.fdebug('issue number formatted: %s' % issuenum)
|
|
|
|
#comicid = issuenzb['ComicID']
|
|
#issueno = str(issuenum).split('.')[0]
|
|
issue_except = 'None'
|
|
issue_exceptions = ['AU',
|
|
'INH',
|
|
'NOW',
|
|
'AI',
|
|
'MU',
|
|
'HU',
|
|
'A',
|
|
'B',
|
|
'C',
|
|
'X',
|
|
'O']
|
|
valid_spaces = ('.', '-')
|
|
for issexcept in issue_exceptions:
|
|
if issexcept.lower() in issuenum.lower():
|
|
logger.fdebug('ALPHANUMERIC EXCEPTION : [' + issexcept + ']')
|
|
v_chk = [v for v in valid_spaces if v in issuenum]
|
|
if v_chk:
|
|
iss_space = v_chk[0]
|
|
logger.fdebug('character space denoted as : ' + iss_space)
|
|
else:
|
|
logger.fdebug('character space not denoted.')
|
|
iss_space = ''
|
|
# if issexcept == 'INH':
|
|
# issue_except = '.INH'
|
|
if issexcept == 'NOW':
|
|
if '!' in issuenum: issuenum = re.sub('\!', '', issuenum)
|
|
# issue_except = '.NOW'
|
|
|
|
issue_except = iss_space + issexcept
|
|
logger.fdebug('issue_except denoted as : ' + issue_except)
|
|
issuenum = re.sub("[^0-9]", "", issuenum)
|
|
break
|
|
|
|
# if 'au' in issuenum.lower() and issuenum[:1].isdigit():
|
|
# issue_except = ' AU'
|
|
# elif 'ai' in issuenum.lower() and issuenum[:1].isdigit():
|
|
# issuenum = re.sub("[^0-9]", "", issuenum)
|
|
# issue_except = ' AI'
|
|
# elif 'inh' in issuenum.lower() and issuenum[:1].isdigit():
|
|
# issuenum = re.sub("[^0-9]", "", issuenum)
|
|
# issue_except = '.INH'
|
|
# elif 'now' in issuenum.lower() and issuenum[:1].isdigit():
|
|
# if '!' in issuenum: issuenum = re.sub('\!', '', issuenum)
|
|
# issuenum = re.sub("[^0-9]", "", issuenum)
|
|
# issue_except = '.NOW'
|
|
if '.' in issuenum:
|
|
iss_find = issuenum.find('.')
|
|
iss_b4dec = issuenum[:iss_find]
|
|
if iss_find == 0:
|
|
iss_b4dec = '0'
|
|
iss_decval = issuenum[iss_find +1:]
|
|
if iss_decval.endswith('.'):
|
|
iss_decval = iss_decval[:-1]
|
|
if int(iss_decval) == 0:
|
|
iss = iss_b4dec
|
|
issdec = int(iss_decval)
|
|
issueno = iss
|
|
else:
|
|
if len(iss_decval) == 1:
|
|
iss = iss_b4dec + "." + iss_decval
|
|
issdec = int(iss_decval) * 10
|
|
else:
|
|
iss = iss_b4dec + "." + iss_decval.rstrip('0')
|
|
issdec = int(iss_decval.rstrip('0')) * 10
|
|
issueno = iss_b4dec
|
|
else:
|
|
iss = issuenum
|
|
issueno = iss
|
|
# issue zero-suppression here
|
|
if mylar.CONFIG.ZERO_LEVEL == "0":
|
|
zeroadd = ""
|
|
else:
|
|
if mylar.CONFIG.ZERO_LEVEL_N == "none": zeroadd = ""
|
|
elif mylar.CONFIG.ZERO_LEVEL_N == "0x": zeroadd = "0"
|
|
elif mylar.CONFIG.ZERO_LEVEL_N == "00x": zeroadd = "00"
|
|
|
|
logger.fdebug('Zero Suppression set to : ' + str(mylar.CONFIG.ZERO_LEVEL_N))
|
|
prettycomiss = None
|
|
|
|
if issueno.isalpha():
|
|
logger.fdebug('issue detected as an alpha.')
|
|
prettycomiss = str(issueno)
|
|
else:
|
|
try:
|
|
x = float(issuenum)
|
|
#validity check
|
|
if x < 0:
|
|
logger.info('I\'ve encountered a negative issue #: %s. Trying to accomodate.' % issueno)
|
|
prettycomiss = '-' + str(zeroadd) + str(issueno[1:])
|
|
elif x == 9999999999:
|
|
logger.fdebug('Infinity issue found.')
|
|
issuenum = 'infinity'
|
|
elif x >= 0:
|
|
pass
|
|
else:
|
|
raise ValueError
|
|
except ValueError, e:
|
|
logger.warn('Unable to properly determine issue number [ %s] - you should probably log this on github for help.' % issueno)
|
|
return
|
|
|
|
if prettycomiss is None and len(str(issueno)) > 0:
|
|
#if int(issueno) < 0:
|
|
# self._log("issue detected is a negative")
|
|
# prettycomiss = '-' + str(zeroadd) + str(abs(issueno))
|
|
if int(issueno) < 10:
|
|
logger.fdebug('issue detected less than 10')
|
|
if '.' in iss:
|
|
if int(iss_decval) > 0:
|
|
issueno = str(iss)
|
|
prettycomiss = str(zeroadd) + str(iss)
|
|
else:
|
|
prettycomiss = str(zeroadd) + str(int(issueno))
|
|
else:
|
|
prettycomiss = str(zeroadd) + str(iss)
|
|
if issue_except != 'None':
|
|
prettycomiss = str(prettycomiss) + issue_except
|
|
logger.fdebug('Zero level supplement set to ' + str(mylar.CONFIG.ZERO_LEVEL_N) + '. Issue will be set as : ' + str(prettycomiss))
|
|
elif int(issueno) >= 10 and int(issueno) < 100:
|
|
logger.fdebug('issue detected greater than 10, but less than 100')
|
|
if mylar.CONFIG.ZERO_LEVEL_N == "none":
|
|
zeroadd = ""
|
|
else:
|
|
zeroadd = "0"
|
|
if '.' in iss:
|
|
if int(iss_decval) > 0:
|
|
issueno = str(iss)
|
|
prettycomiss = str(zeroadd) + str(iss)
|
|
else:
|
|
prettycomiss = str(zeroadd) + str(int(issueno))
|
|
else:
|
|
prettycomiss = str(zeroadd) + str(iss)
|
|
if issue_except != 'None':
|
|
prettycomiss = str(prettycomiss) + issue_except
|
|
logger.fdebug('Zero level supplement set to ' + str(mylar.CONFIG.ZERO_LEVEL_N) + '.Issue will be set as : ' + str(prettycomiss))
|
|
else:
|
|
logger.fdebug('issue detected greater than 100')
|
|
if issuenum == 'infinity':
|
|
prettycomiss = 'infinity'
|
|
else:
|
|
if '.' in iss:
|
|
if int(iss_decval) > 0:
|
|
issueno = str(iss)
|
|
prettycomiss = str(issueno)
|
|
if issue_except != 'None':
|
|
prettycomiss = str(prettycomiss) + issue_except
|
|
logger.fdebug('Zero level supplement set to ' + str(mylar.CONFIG.ZERO_LEVEL_N) + '. Issue will be set as : ' + str(prettycomiss))
|
|
elif len(str(issueno)) == 0:
|
|
prettycomiss = str(issueno)
|
|
logger.fdebug('issue length error - cannot determine length. Defaulting to None: ' + str(prettycomiss))
|
|
|
|
logger.fdebug('Pretty Comic Issue is : ' + str(prettycomiss))
|
|
if mylar.CONFIG.UNICODE_ISSUENUMBER:
|
|
logger.fdebug('Setting this to Unicode format as requested: %s' % prettycomiss)
|
|
prettycomiss = unicodeissue
|
|
|
|
issueyear = issuedate[:4]
|
|
month = issuedate[5:7].replace('-', '').strip()
|
|
month_name = fullmonth(month)
|
|
if month_name is None:
|
|
month_name = 'None'
|
|
logger.fdebug('Issue Year : ' + str(issueyear))
|
|
logger.fdebug('Publisher: ' + publisher)
|
|
logger.fdebug('Series: ' + series)
|
|
logger.fdebug('Year: ' + str(seriesyear))
|
|
logger.fdebug('Comic Location: ' + comlocation)
|
|
if comversion is None:
|
|
comversion = 'None'
|
|
#if comversion is None, remove it so it doesn't populate with 'None'
|
|
if comversion == 'None':
|
|
chunk_f_f = re.sub('\$VolumeN', '', mylar.CONFIG.FILE_FORMAT)
|
|
chunk_f = re.compile(r'\s+')
|
|
chunk_file_format = chunk_f.sub(' ', chunk_f_f)
|
|
logger.fdebug('No version # found for series, removing from filename')
|
|
logger.fdebug("new format: " + str(chunk_file_format))
|
|
else:
|
|
chunk_file_format = mylar.CONFIG.FILE_FORMAT
|
|
|
|
if annualize is None:
|
|
chunk_f_f = re.sub('\$Annual', '', chunk_file_format)
|
|
chunk_f = re.compile(r'\s+')
|
|
chunk_file_format = chunk_f.sub(' ', chunk_f_f)
|
|
logger.fdebug('not an annual - removing from filename paramaters')
|
|
logger.fdebug('new format: ' + str(chunk_file_format))
|
|
|
|
else:
|
|
logger.fdebug('chunk_file_format is: ' + str(chunk_file_format))
|
|
if mylar.CONFIG.ANNUALS_ON:
|
|
if 'annual' in series.lower():
|
|
if '$Annual' not in chunk_file_format: # and 'annual' not in ofilename.lower():
|
|
#if it's an annual, but $annual isn't specified in file_format, we need to
|
|
#force it in there, by default in the format of $Annual $Issue
|
|
#prettycomiss = "Annual " + str(prettycomiss)
|
|
logger.fdebug('[%s][ANNUALS-ON][ANNUAL IN SERIES][NO ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
else:
|
|
#because it exists within title, strip it then use formatting tag for placement of wording.
|
|
chunk_f_f = re.sub('\$Annual', '', chunk_file_format)
|
|
chunk_f = re.compile(r'\s+')
|
|
chunk_file_format = chunk_f.sub(' ', chunk_f_f)
|
|
logger.fdebug('[%s][ANNUALS-ON][ANNUAL IN SERIES][ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
else:
|
|
if '$Annual' not in chunk_file_format: # and 'annual' not in ofilename.lower():
|
|
#if it's an annual, but $annual isn't specified in file_format, we need to
|
|
#force it in there, by default in the format of $Annual $Issue
|
|
prettycomiss = "Annual %s" % prettycomiss
|
|
logger.fdebug('[%s][ANNUALS-ON][ANNUAL NOT IN SERIES][NO ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
else:
|
|
logger.fdebug('[%s][ANNUALS-ON][ANNUAL NOT IN SERIES][ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
|
|
else:
|
|
#if annuals aren't enabled, then annuals are being tracked as independent series.
|
|
#annualize will be true since it's an annual in the seriesname.
|
|
if 'annual' in series.lower():
|
|
if '$Annual' not in chunk_file_format: # and 'annual' not in ofilename.lower():
|
|
#if it's an annual, but $annual isn't specified in file_format, we need to
|
|
#force it in there, by default in the format of $Annual $Issue
|
|
#prettycomiss = "Annual " + str(prettycomiss)
|
|
logger.fdebug('[%s][ANNUALS-OFF][ANNUAL IN SERIES][NO ANNUAL FORMAT] prettycomiss: %s' (series, prettycomiss))
|
|
else:
|
|
#because it exists within title, strip it then use formatting tag for placement of wording.
|
|
chunk_f_f = re.sub('\$Annual', '', chunk_file_format)
|
|
chunk_f = re.compile(r'\s+')
|
|
chunk_file_format = chunk_f.sub(' ', chunk_f_f)
|
|
logger.fdebug('[%s][ANNUALS-OFF][ANNUAL IN SERIES][ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
else:
|
|
if '$Annual' not in chunk_file_format: # and 'annual' not in ofilename.lower():
|
|
#if it's an annual, but $annual isn't specified in file_format, we need to
|
|
#force it in there, by default in the format of $Annual $Issue
|
|
prettycomiss = "Annual %s" % prettycomiss
|
|
logger.fdebug('[%s][ANNUALS-OFF][ANNUAL NOT IN SERIES][NO ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
else:
|
|
logger.fdebug('[%s][ANNUALS-OFF][ANNUAL NOT IN SERIES][ANNUAL FORMAT] prettycomiss: %s' % (series, prettycomiss))
|
|
|
|
|
|
logger.fdebug('Annual detected within series title of ' + series + '. Not auto-correcting issue #')
|
|
|
|
seriesfilename = seriesfilename.encode('ascii', 'ignore').strip()
|
|
filebad = [':', ',', '/', '?', '!', '\'', '\"', '\*'] #in u_comicname or '/' in u_comicname or ',' in u_comicname or '?' in u_comicname:
|
|
for dbd in filebad:
|
|
if dbd in seriesfilename:
|
|
if any([dbd == '/', dbd == '*']):
|
|
repthechar = '-'
|
|
else:
|
|
repthechar = ''
|
|
seriesfilename = seriesfilename.replace(dbd, repthechar)
|
|
logger.fdebug('Altering series name due to filenaming restrictions: ' + seriesfilename)
|
|
|
|
publisher = re.sub('!', '', publisher)
|
|
|
|
file_values = {'$Series': seriesfilename,
|
|
'$Issue': prettycomiss,
|
|
'$Year': issueyear,
|
|
'$series': series.lower(),
|
|
'$Publisher': publisher,
|
|
'$publisher': publisher.lower(),
|
|
'$VolumeY': 'V' + str(seriesyear),
|
|
'$VolumeN': comversion,
|
|
'$monthname': month_name,
|
|
'$month': month,
|
|
'$Annual': 'Annual'
|
|
}
|
|
|
|
extensions = ('.cbr', '.cbz', '.cb7')
|
|
|
|
if ofilename.lower().endswith(extensions):
|
|
path, ext = os.path.splitext(ofilename)
|
|
|
|
if mylar.CONFIG.FILE_FORMAT == '':
|
|
logger.fdebug('Rename Files is not enabled - keeping original filename.')
|
|
#check if extension is in nzb_name - will screw up otherwise
|
|
if ofilename.lower().endswith(extensions):
|
|
nfilename = ofilename[:-4]
|
|
else:
|
|
nfilename = ofilename
|
|
else:
|
|
nfilename = replace_all(chunk_file_format, file_values)
|
|
if mylar.CONFIG.REPLACE_SPACES:
|
|
#mylar.CONFIG.REPLACE_CHAR ...determines what to replace spaces with underscore or dot
|
|
nfilename = nfilename.replace(' ', mylar.CONFIG.REPLACE_CHAR)
|
|
|
|
nfilename = re.sub('[\,\:]', '', nfilename) + ext.lower()
|
|
logger.fdebug('New Filename: ' + nfilename)
|
|
|
|
if mylar.CONFIG.LOWERCASE_FILENAMES:
|
|
nfilename = nfilename.lower()
|
|
dst = os.path.join(comlocation, nfilename)
|
|
else:
|
|
dst = os.path.join(comlocation, nfilename)
|
|
|
|
logger.fdebug('Source: ' + ofilename)
|
|
logger.fdebug('Destination: ' + dst)
|
|
|
|
rename_this = {"destination_dir": dst,
|
|
"nfilename": nfilename,
|
|
"issueid": issueid,
|
|
"comicid": comicid}
|
|
|
|
return rename_this
|
|
|
|
|
|
def apiremove(apistring, type):
|
|
if type == 'nzb':
|
|
value_regex = re.compile("(?<=apikey=)(?P<value>.*?)(?=$)")
|
|
#match = value_regex.search(apistring)
|
|
apiremoved = value_regex.sub("xUDONTNEEDTOKNOWTHISx", apistring)
|
|
else:
|
|
#type = $ to denote end of string
|
|
#type = & to denote up until next api variable
|
|
value_regex1 = re.compile("(?<=%26i=1%26r=)(?P<value>.*?)(?=" + str(type) +")")
|
|
#match = value_regex.search(apistring)
|
|
apiremoved1 = value_regex1.sub("xUDONTNEEDTOKNOWTHISx", apistring)
|
|
value_regex = re.compile("(?<=apikey=)(?P<value>.*?)(?=" + str(type) +")")
|
|
apiremoved = value_regex.sub("xUDONTNEEDTOKNOWTHISx", apiremoved1)
|
|
|
|
#need to remove the urlencoded-portions as well in future
|
|
return apiremoved
|
|
|
|
def remove_apikey(payd, key):
|
|
#payload = some dictionary with payload values
|
|
#key = the key to replace with REDACTED (normally apikey)
|
|
for k,v in payd.items():
|
|
payd[key] = 'REDACTED'
|
|
|
|
return payd
|
|
|
|
def ComicSort(comicorder=None, sequence=None, imported=None):
|
|
if sequence:
|
|
# if it's on startup, load the sql into a tuple for use to avoid record-locking
|
|
i = 0
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
comicsort = myDB.select("SELECT * FROM comics ORDER BY ComicSortName COLLATE NOCASE")
|
|
comicorderlist = []
|
|
comicorder = {}
|
|
comicidlist = []
|
|
if sequence == 'update':
|
|
mylar.COMICSORT['SortOrder'] = None
|
|
mylar.COMICSORT['LastOrderNo'] = None
|
|
mylar.COMICSORT['LastOrderID'] = None
|
|
for csort in comicsort:
|
|
if csort['ComicID'] is None: pass
|
|
if not csort['ComicID'] in comicidlist:
|
|
if sequence == 'startup':
|
|
comicorderlist.append({
|
|
'ComicID': csort['ComicID'],
|
|
'ComicOrder': i
|
|
})
|
|
elif sequence == 'update':
|
|
comicorderlist.append({
|
|
# mylar.COMICSORT['SortOrder'].append({
|
|
'ComicID': csort['ComicID'],
|
|
'ComicOrder': i
|
|
})
|
|
|
|
comicidlist.append(csort['ComicID'])
|
|
i+=1
|
|
if sequence == 'startup':
|
|
if i == 0:
|
|
comicorder['SortOrder'] = ({'ComicID': '99999', 'ComicOrder': 1})
|
|
comicorder['LastOrderNo'] = 1
|
|
comicorder['LastOrderID'] = 99999
|
|
else:
|
|
comicorder['SortOrder'] = comicorderlist
|
|
comicorder['LastOrderNo'] = i -1
|
|
comicorder['LastOrderID'] = comicorder['SortOrder'][i -1]['ComicID']
|
|
if i < 0: i == 0
|
|
logger.info('Sucessfully ordered ' + str(i -1) + ' series in your watchlist.')
|
|
return comicorder
|
|
elif sequence == 'update':
|
|
mylar.COMICSORT['SortOrder'] = comicorderlist
|
|
#print ("i:" + str(i))
|
|
if i == 0:
|
|
placemnt = 1
|
|
else:
|
|
placemnt = int(i -1)
|
|
mylar.COMICSORT['LastOrderNo'] = placemnt
|
|
mylar.COMICSORT['LastOrderID'] = mylar.COMICSORT['SortOrder'][placemnt]['ComicID']
|
|
return
|
|
else:
|
|
# for new series adds, we already know the comicid, so we set the sortorder to an abnormally high #
|
|
# we DO NOT write to the db to avoid record-locking.
|
|
# if we get 2 999's we're in trouble though.
|
|
sortedapp = []
|
|
if comicorder['LastOrderNo'] == '999':
|
|
lastorderval = int(comicorder['LastOrderNo']) + 1
|
|
else:
|
|
lastorderval = 999
|
|
sortedapp.append({
|
|
'ComicID': imported,
|
|
'ComicOrder': lastorderval
|
|
})
|
|
mylar.COMICSORT['SortOrder'] = sortedapp
|
|
mylar.COMICSORT['LastOrderNo'] = lastorderval
|
|
mylar.COMICSORT['LastOrderID'] = imported
|
|
return
|
|
|
|
def fullmonth(monthno):
|
|
#simple numerical to worded month conversion....
|
|
basmonths = {'1': 'January', '2': 'February', '3': 'March', '4': 'April', '5': 'May', '6': 'June', '7': 'July', '8': 'August', '9': 'September', '10': 'October', '11': 'November', '12': 'December'}
|
|
|
|
monthconv = None
|
|
|
|
for numbs in basmonths:
|
|
if int(numbs) == int(monthno):
|
|
monthconv = basmonths[numbs]
|
|
|
|
return monthconv
|
|
|
|
def updateComicLocation():
|
|
#in order for this to work, the ComicLocation MUST be left at the original location.
|
|
#in the config.ini - set LOCMOVE = 1 (to enable this to run on the NEXT startup)
|
|
# - set NEWCOMDIR = new ComicLocation
|
|
#after running, set ComicLocation to new location in Configuration GUI
|
|
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
if mylar.CONFIG.NEWCOM_DIR is not None:
|
|
logger.info('Performing a one-time mass update to Comic Location')
|
|
#create the root dir if it doesn't exist
|
|
checkdirectory = mylar.filechecker.validateAndCreateDirectory(mylar.CONFIG.NEWCOM_DIR, create=True)
|
|
if not checkdirectory:
|
|
logger.warn('Error trying to validate/create directory. Aborting this process at this time.')
|
|
return
|
|
dirlist = myDB.select("SELECT * FROM comics")
|
|
comloc = []
|
|
|
|
if dirlist is not None:
|
|
for dl in dirlist:
|
|
|
|
u_comicnm = dl['ComicName']
|
|
# let's remove the non-standard characters here that will break filenaming / searching.
|
|
comicname_folder = filesafe(u_comicnm)
|
|
|
|
publisher = re.sub('!', '', dl['ComicPublisher']) # thanks Boom!
|
|
year = dl['ComicYear']
|
|
|
|
if dl['Corrected_Type'] is not None:
|
|
booktype = dl['Corrected_Type']
|
|
else:
|
|
booktype = dl['Type']
|
|
if booktype == 'Print' or all([booktype != 'Print', mylar.CONFIG.FORMAT_BOOKTYPE is False]):
|
|
chunk_fb = re.sub('\$Type', '', mylar.CONFIG.FOLDER_FORMAT)
|
|
chunk_b = re.compile(r'\s+')
|
|
chunk_folder_format = chunk_b.sub(' ', chunk_fb)
|
|
else:
|
|
chunk_folder_format = mylar.CONFIG.FOLDER_FORMAT
|
|
|
|
comversion = dl['ComicVersion']
|
|
if comversion is None:
|
|
comversion = 'None'
|
|
#if comversion is None, remove it so it doesn't populate with 'None'
|
|
if comversion == 'None':
|
|
chunk_f_f = re.sub('\$VolumeN', '', mylar.CONFIG.FOLDER_FORMAT)
|
|
chunk_f = re.compile(r'\s+')
|
|
folderformat = chunk_f.sub(' ', chunk_f_f)
|
|
else:
|
|
folderformat = mylar.CONFIG.FOLDER_FORMAT
|
|
|
|
#do work to generate folder path
|
|
|
|
values = {'$Series': comicname_folder,
|
|
'$Publisher': publisher,
|
|
'$Year': year,
|
|
'$series': comicname_folder.lower(),
|
|
'$publisher': publisher.lower(),
|
|
'$VolumeY': 'V' + str(year),
|
|
'$VolumeN': comversion,
|
|
'$Annual': 'Annual',
|
|
'$Type': booktype
|
|
}
|
|
|
|
#set the paths here with the seperator removed allowing for cross-platform altering.
|
|
ccdir = re.sub(r'[\\|/]', '%&', mylar.CONFIG.NEWCOM_DIR)
|
|
ddir = re.sub(r'[\\|/]', '%&', mylar.CONFIG.DESTINATION_DIR)
|
|
dlc = re.sub(r'[\\|/]', '%&', dl['ComicLocation'])
|
|
|
|
if mylar.CONFIG.FFTONEWCOM_DIR:
|
|
#if this is enabled (1) it will apply the Folder_Format to all the new dirs
|
|
if mylar.CONFIG.FOLDER_FORMAT == '':
|
|
comlocation = re.sub(ddir, ccdir, dlc).strip()
|
|
else:
|
|
first = replace_all(folderformat, values)
|
|
if mylar.CONFIG.REPLACE_SPACES:
|
|
#mylar.CONFIG.REPLACE_CHAR ...determines what to replace spaces with underscore or dot
|
|
first = first.replace(' ', mylar.CONFIG.REPLACE_CHAR)
|
|
comlocation = os.path.join(mylar.CONFIG.NEWCOM_DIR, first).strip()
|
|
|
|
else:
|
|
#DESTINATION_DIR = /mnt/mediavg/Comics
|
|
#NEWCOM_DIR = /mnt/mediavg/Comics/Comics-1
|
|
#dl['ComicLocation'] = /mnt/mediavg/Comics/Batman-(2011)
|
|
comlocation = re.sub(ddir, ccdir, dlc).strip()
|
|
|
|
#regenerate the new path location so that it's os.dependent now.
|
|
com_done = re.sub('%&', os.sep.encode('unicode-escape'), comlocation).strip()
|
|
|
|
comloc.append({"comlocation": com_done,
|
|
"origlocation": dl['ComicLocation'],
|
|
"comicid": dl['ComicID']})
|
|
|
|
if len(comloc) > 0:
|
|
#give the information about what we're doing.
|
|
if mylar.CONFIG.FFTONEWCOM_DIR:
|
|
logger.info('FFTONEWCOM_DIR is enabled. Applying the existing folder format to ALL directories regardless of existing location paths')
|
|
else:
|
|
logger.info('FFTONEWCOM_DIR is not enabled. I will keep existing subdirectory paths, and will only change the actual Comic Location in the path.')
|
|
logger.fdebug(' (ie. /mnt/Comics/Marvel/Hush-(2012) to /mnt/mynewLocation/Marvel/Hush-(2012) ')
|
|
|
|
#do the deed.
|
|
for cl in comloc:
|
|
ctrlVal = {"ComicID": cl['comicid']}
|
|
newVal = {"ComicLocation": cl['comlocation']}
|
|
myDB.upsert("Comics", newVal, ctrlVal)
|
|
logger.fdebug('Updated : ' + cl['origlocation'] + ' .: TO :. ' + cl['comlocation'])
|
|
logger.info('Updated ' + str(len(comloc)) + ' series to a new Comic Location as specified in the config.ini')
|
|
else:
|
|
logger.fdebug('Failed in updating the Comic Locations. Check Folder Format string and/or log the issue.')
|
|
else:
|
|
logger.info('There are no series in your watchlist to Update the locations. Not updating anything at this time.')
|
|
#set the value to 0 here so we don't keep on doing this...
|
|
mylar.CONFIG.LOCMOVE = 0
|
|
#mylar.config_write()
|
|
else:
|
|
logger.info('No new ComicLocation path specified - not updating. Set NEWCOMD_DIR in config.ini')
|
|
#raise cherrypy.HTTPRedirect("config")
|
|
return
|
|
|
|
def cleanhtml(raw_html):
|
|
#cleanr = re.compile('<.*?>')
|
|
#cleantext = re.sub(cleanr, '', raw_html)
|
|
#return cleantext
|
|
from bs4 import BeautifulSoup
|
|
|
|
VALID_TAGS = ['div', 'p']
|
|
|
|
soup = BeautifulSoup(raw_html, "html.parser")
|
|
|
|
for tag in soup.findAll('p'):
|
|
if tag.name not in VALID_TAGS:
|
|
tag.replaceWith(tag.renderContents())
|
|
flipflop = soup.renderContents()
|
|
print flipflop
|
|
return flipflop
|
|
|
|
|
|
def issuedigits(issnum):
|
|
#import db
|
|
|
|
int_issnum = None
|
|
|
|
try:
|
|
tst = issnum.isdigit()
|
|
except:
|
|
try:
|
|
isstest = str(issnum)
|
|
tst = isstest.isdigit()
|
|
except:
|
|
return 9999999999
|
|
else:
|
|
issnum = str(issnum)
|
|
|
|
if issnum.isdigit():
|
|
int_issnum = int(issnum) * 1000
|
|
else:
|
|
#count = 0
|
|
#for char in issnum:
|
|
# if char.isalpha():
|
|
# count += 1
|
|
#if count > 5:
|
|
# logger.error('This is not an issue number - not enough numerics to parse')
|
|
# int_issnum = 999999999999999
|
|
# return int_issnum
|
|
try:
|
|
if 'au' in issnum.lower() and issnum[:1].isdigit():
|
|
int_issnum = (int(issnum[:-2]) * 1000) + ord('a') + ord('u')
|
|
elif 'ai' in issnum.lower() and issnum[:1].isdigit():
|
|
int_issnum = (int(issnum[:-2]) * 1000) + ord('a') + ord('i')
|
|
elif 'inh' in issnum.lower() or 'now' in issnum.lower():
|
|
remdec = issnum.find('.') #find the decimal position.
|
|
if remdec == -1:
|
|
#if no decimal, it's all one string
|
|
#remove the last 3 characters from the issue # (INH)
|
|
int_issnum = (int(issnum[:-3]) * 1000) + ord('i') + ord('n') + ord('h')
|
|
else:
|
|
int_issnum = (int(issnum[:-4]) * 1000) + ord('i') + ord('n') + ord('h')
|
|
elif 'now' in issnum.lower():
|
|
if '!' in issnum: issnum = re.sub('\!', '', issnum)
|
|
remdec = issnum.find('.') #find the decimal position.
|
|
if remdec == -1:
|
|
#if no decimal, it's all one string
|
|
#remove the last 3 characters from the issue # (NOW)
|
|
int_issnum = (int(issnum[:-3]) * 1000) + ord('n') + ord('o') + ord('w')
|
|
else:
|
|
int_issnum = (int(issnum[:-4]) * 1000) + ord('n') + ord('o') + ord('w')
|
|
elif 'mu' in issnum.lower():
|
|
remdec = issnum.find('.')
|
|
if remdec == -1:
|
|
int_issnum = (int(issnum[:-2]) * 1000) + ord('m') + ord('u')
|
|
else:
|
|
int_issnum = (int(issnum[:-3]) * 1000) + ord('m') + ord('u')
|
|
elif 'hu' in issnum.lower():
|
|
remdec = issnum.find('.') #find the decimal position.
|
|
if remdec == -1:
|
|
int_issnum = (int(issnum[:-2]) * 1000) + ord('h') + ord('u')
|
|
else:
|
|
int_issnum = (int(issnum[:-3]) * 1000) + ord('h') + ord('u')
|
|
|
|
except ValueError as e:
|
|
logger.error('[' + issnum + '] Unable to properly determine the issue number. Error: %s', e)
|
|
return 9999999999
|
|
|
|
if int_issnum is not None:
|
|
return int_issnum
|
|
|
|
#try:
|
|
# issnum.decode('ascii')
|
|
# logger.fdebug('ascii character.')
|
|
#except:
|
|
# logger.fdebug('Unicode character detected: ' + issnum)
|
|
#else: issnum.decode(mylar.SYS_ENCODING).decode('utf-8')
|
|
if type(issnum) == str:
|
|
try:
|
|
issnum = issnum.decode('utf-8')
|
|
except:
|
|
issnum = issnum.decode('windows-1252')
|
|
|
|
if type(issnum) == unicode:
|
|
vals = {u'\xbd':.5,u'\xbc':.25,u'\xbe':.75,u'\u221e':9999999999,u'\xe2':9999999999}
|
|
else:
|
|
vals = {'\xbd':.5,'\xbc':.25,'\xbe':.75,'\u221e':9999999999,'\xe2':9999999999}
|
|
|
|
x = [vals[key] for key in vals if key in issnum]
|
|
|
|
if x:
|
|
chk = re.sub('[^0-9]', '', issnum).strip()
|
|
if len(chk) == 0:
|
|
int_issnum = x[0] * 1000
|
|
else:
|
|
int_issnum = (int(re.sub('[^0-9]', '', issnum).strip()) + x[0]) * 1000
|
|
#logger.fdebug('int_issnum: ' + str(int_issnum))
|
|
else:
|
|
if any(['.' in issnum, ',' in issnum]):
|
|
#logger.fdebug('decimal detected.')
|
|
if ',' in issnum: issnum = re.sub(',', '.', issnum)
|
|
issst = str(issnum).find('.')
|
|
if issst == 0:
|
|
issb4dec = 0
|
|
else:
|
|
issb4dec = str(issnum)[:issst]
|
|
decis = str(issnum)[issst +1:]
|
|
if len(decis) == 1:
|
|
decisval = int(decis) * 10
|
|
issaftdec = str(decisval)
|
|
elif len(decis) == 2:
|
|
decisval = int(decis)
|
|
issaftdec = str(decisval)
|
|
else:
|
|
decisval = decis
|
|
issaftdec = str(decisval)
|
|
#if there's a trailing decimal (ie. 1.50.) and it's either intentional or not, blow it away.
|
|
if issaftdec[-1:] == '.':
|
|
issaftdec = issaftdec[:-1]
|
|
try:
|
|
int_issnum = (int(issb4dec) * 1000) + (int(issaftdec) * 10)
|
|
except ValueError:
|
|
#logger.fdebug('This has no issue # for me to get - Either a Graphic Novel or one-shot.')
|
|
int_issnum = 999999999999999
|
|
else:
|
|
try:
|
|
x = float(issnum)
|
|
#logger.info(x)
|
|
#validity check
|
|
if x < 0:
|
|
#logger.info("I've encountered a negative issue #: " + str(issnum) + ". Trying to accomodate.")
|
|
int_issnum = (int(x) *1000) - 1
|
|
elif bool(x):
|
|
logger.fdebug('Infinity issue found.')
|
|
int_issnum = 9999999999 * 1000
|
|
else: raise ValueError
|
|
except ValueError, e:
|
|
#this will account for any alpha in a issue#, so long as it doesn't have decimals.
|
|
x = 0
|
|
tstord = None
|
|
issno = None
|
|
invchk = "false"
|
|
if issnum.lower() != 'preview':
|
|
while (x < len(issnum)):
|
|
if issnum[x].isalpha():
|
|
#take first occurance of alpha in string and carry it through
|
|
tstord = issnum[x:].rstrip()
|
|
tstord = re.sub('[\-\,\.\+]', '', tstord).rstrip()
|
|
issno = issnum[:x].rstrip()
|
|
issno = re.sub('[\-\,\.\+]', '', issno).rstrip()
|
|
try:
|
|
isschk = float(issno)
|
|
except ValueError, e:
|
|
if len(issnum) == 1 and issnum.isalpha():
|
|
break
|
|
logger.fdebug('[' + issno + '] Invalid numeric for issue - cannot be found. Ignoring.')
|
|
issno = None
|
|
tstord = None
|
|
invchk = "true"
|
|
break
|
|
x+=1
|
|
if tstord is not None and issno is not None:
|
|
a = 0
|
|
ordtot = 0
|
|
if len(issnum) == 1 and issnum.isalpha():
|
|
int_issnum = ord(tstord.lower())
|
|
else:
|
|
while (a < len(tstord)):
|
|
ordtot += ord(tstord[a].lower()) #lower-case the letters for simplicty
|
|
a+=1
|
|
int_issnum = (int(issno) * 1000) + ordtot
|
|
elif invchk == "true":
|
|
if any([issnum.lower() == 'fall', issnum.lower() == 'spring', issnum.lower() == 'summer', issnum.lower() == 'winter']):
|
|
inu = 0
|
|
ordtot = 0
|
|
while (inu < len(issnum)):
|
|
ordtot += ord(issnum[inu].lower()) #lower-case the letters for simplicty
|
|
inu+=1
|
|
int_issnum = ordtot
|
|
else:
|
|
logger.fdebug('this does not have an issue # that I can parse properly.')
|
|
return 999999999999999
|
|
else:
|
|
if issnum == '9-5':
|
|
issnum = u'9\xbd'
|
|
logger.fdebug('issue: 9-5 is an invalid entry. Correcting to : ' + issnum)
|
|
int_issnum = (9 * 1000) + (.5 * 1000)
|
|
elif issnum == '112/113':
|
|
int_issnum = (112 * 1000) + (.5 * 1000)
|
|
elif issnum == '14-16':
|
|
int_issnum = (15 * 1000) + (.5 * 1000)
|
|
elif issnum.lower() == 'preview':
|
|
inu = 0
|
|
ordtot = 0
|
|
while (inu < len(issnum)):
|
|
ordtot += ord(issnum[inu].lower()) #lower-case the letters for simplicty
|
|
inu+=1
|
|
int_issnum = ordtot
|
|
else:
|
|
logger.error(issnum + ' this has an alpha-numeric in the issue # which I cannot account for.')
|
|
return 999999999999999
|
|
|
|
return int_issnum
|
|
|
|
|
|
def checkthepub(ComicID):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
publishers = ['marvel', 'dc', 'darkhorse']
|
|
pubchk = myDB.selectone("SELECT * FROM comics WHERE ComicID=?", [ComicID]).fetchone()
|
|
if pubchk is None:
|
|
logger.fdebug('No publisher information found to aid in determining series..defaulting to base check of 55 days.')
|
|
return mylar.CONFIG.BIGGIE_PUB
|
|
else:
|
|
for publish in publishers:
|
|
if publish in pubchk['ComicPublisher'].lower():
|
|
#logger.fdebug('Biggie publisher detected - ' + pubchk['ComicPublisher'])
|
|
return mylar.CONFIG.BIGGIE_PUB
|
|
|
|
#logger.fdebug('Indie publisher detected - ' + pubchk['ComicPublisher'])
|
|
return mylar.CONFIG.INDIE_PUB
|
|
|
|
def annual_update():
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
annuallist = myDB.select('SELECT * FROM annuals')
|
|
if annuallist is None:
|
|
logger.info('no annuals to update.')
|
|
return
|
|
|
|
cnames = []
|
|
#populate the ComicName field with the corresponding series name from the comics table.
|
|
for ann in annuallist:
|
|
coms = myDB.selectone('SELECT * FROM comics WHERE ComicID=?', [ann['ComicID']]).fetchone()
|
|
cnames.append({'ComicID': ann['ComicID'],
|
|
'ComicName': coms['ComicName']
|
|
})
|
|
|
|
#write in a seperate loop to avoid db locks
|
|
i=0
|
|
for cns in cnames:
|
|
ctrlVal = {"ComicID": cns['ComicID']}
|
|
newVal = {"ComicName": cns['ComicName']}
|
|
myDB.upsert("annuals", newVal, ctrlVal)
|
|
i+=1
|
|
|
|
logger.info(str(i) + ' series have been updated in the annuals table.')
|
|
return
|
|
|
|
def replacetheslash(data):
|
|
# this is necessary for the cache directory to display properly in IE/FF.
|
|
# os.path.join will pipe in the '\' in windows, which won't resolve
|
|
# when viewing through cherrypy - so convert it and viola.
|
|
if platform.system() == "Windows":
|
|
slashreplaced = data.replace('\\', '/')
|
|
else:
|
|
slashreplaced = data
|
|
return slashreplaced
|
|
|
|
def urlretrieve(urlfile, fpath):
|
|
chunk = 4096
|
|
f = open(fpath, "w")
|
|
while 1:
|
|
data = urlfile.read(chunk)
|
|
if not data:
|
|
print "done."
|
|
break
|
|
f.write(data)
|
|
print "Read %s bytes"%len(data)
|
|
|
|
def renamefile_readingorder(readorder):
|
|
logger.fdebug('readingorder#: ' + str(readorder))
|
|
if int(readorder) < 10: readord = "00" + str(readorder)
|
|
elif int(readorder) >= 10 and int(readorder) < 99: readord = "0" + str(readorder)
|
|
else: readord = str(readorder)
|
|
|
|
return readord
|
|
|
|
def latestdate_fix():
|
|
#import db
|
|
datefix = []
|
|
cnupdate = []
|
|
myDB = db.DBConnection()
|
|
comiclist = myDB.select('SELECT * FROM comics')
|
|
if comiclist is None:
|
|
logger.fdebug('No Series in watchlist to correct latest date')
|
|
return
|
|
for cl in comiclist:
|
|
if cl['ComicName_Filesafe'] is None:
|
|
cnupdate.append({"comicid": cl['ComicID'],
|
|
"comicname_filesafe": filesafe(cl['ComicName'])})
|
|
latestdate = cl['LatestDate']
|
|
#logger.fdebug("latestdate: " + str(latestdate))
|
|
try:
|
|
if latestdate[8:] == '':
|
|
#logger.fdebug("invalid date " + str(latestdate) + " appending 01 for day to avoid errors")
|
|
if len(latestdate) <= 7:
|
|
finddash = latestdate.find('-')
|
|
#logger.info('dash found at position ' + str(finddash))
|
|
if finddash != 4: #format of mm-yyyy
|
|
lat_month = latestdate[:finddash]
|
|
lat_year = latestdate[finddash +1:]
|
|
else: #format of yyyy-mm
|
|
lat_month = latestdate[finddash +1:]
|
|
lat_year = latestdate[:finddash]
|
|
|
|
latestdate = (lat_year) + '-' + str(lat_month) + '-01'
|
|
datefix.append({"comicid": cl['ComicID'],
|
|
"latestdate": latestdate})
|
|
#logger.info('latest date: ' + str(latestdate))
|
|
except:
|
|
datefix.append({"comicid": cl['ComicID'],
|
|
"latestdate": '0000-00-00'})
|
|
|
|
#now we fix.
|
|
if len(datefix) > 0:
|
|
logger.info('Preparing to correct/fix ' + str(len(datefix)) + ' series that have incorrect values given for the Latest Date field.')
|
|
for df in datefix:
|
|
newCtrl = {"ComicID": df['comicid']}
|
|
newVal = {"LatestDate": df['latestdate']}
|
|
myDB.upsert("comics", newVal, newCtrl)
|
|
if len(cnupdate) > 0:
|
|
logger.info('Preparing to update ' + str(len(cnupdate)) + ' series on your watchlist for use with non-ascii characters')
|
|
for cn in cnupdate:
|
|
newCtrl = {"ComicID": cn['comicid']}
|
|
newVal = {"ComicName_Filesafe": cn['comicname_filesafe']}
|
|
myDB.upsert("comics", newVal, newCtrl)
|
|
|
|
return
|
|
|
|
def upgrade_dynamic():
|
|
#import db
|
|
dynamic_comiclist = []
|
|
myDB = db.DBConnection()
|
|
#update the comicdb to include the Dynamic Names (and any futher changes as required)
|
|
clist = myDB.select('SELECT * FROM Comics')
|
|
for cl in clist:
|
|
cl_d = mylar.filechecker.FileChecker(watchcomic=cl['ComicName'])
|
|
cl_dyninfo = cl_d.dynamic_replace(cl['ComicName'])
|
|
dynamic_comiclist.append({'DynamicComicName': re.sub('[\|\s]','', cl_dyninfo['mod_seriesname'].lower()).strip(),
|
|
'ComicID': cl['ComicID']})
|
|
|
|
if len(dynamic_comiclist) > 0:
|
|
for dl in dynamic_comiclist:
|
|
CtrlVal = {"ComicID": dl['ComicID']}
|
|
newVal = {"DynamicComicName": dl['DynamicComicName']}
|
|
myDB.upsert("Comics", newVal, CtrlVal)
|
|
|
|
#update the storyarcsdb to include the Dynamic Names (and any futher changes as required)
|
|
dynamic_storylist = []
|
|
rlist = myDB.select('SELECT * FROM storyarcs WHERE StoryArcID is not NULL')
|
|
for rl in rlist:
|
|
rl_d = mylar.filechecker.FileChecker(watchcomic=rl['ComicName'])
|
|
rl_dyninfo = cl_d.dynamic_replace(rl['ComicName'])
|
|
dynamic_storylist.append({'DynamicComicName': re.sub('[\|\s]','', rl_dyninfo['mod_seriesname'].lower()).strip(),
|
|
'IssueArcID': rl['IssueArcID']})
|
|
|
|
if len(dynamic_storylist) > 0:
|
|
for ds in dynamic_storylist:
|
|
CtrlVal = {"IssueArcID": ds['IssueArcID']}
|
|
newVal = {"DynamicComicName": ds['DynamicComicName']}
|
|
myDB.upsert("storyarcs", newVal, CtrlVal)
|
|
|
|
logger.info('Finished updating ' + str(len(dynamic_comiclist)) + ' / ' + str(len(dynamic_storylist)) + ' entries within the db.')
|
|
mylar.CONFIG.DYNAMIC_UPDATE = 4
|
|
mylar.CONFIG.writeconfig()
|
|
return
|
|
|
|
def checkFolder(folderpath=None):
|
|
from mylar import PostProcessor
|
|
|
|
queue = Queue.Queue()
|
|
#monitor a selected folder for 'snatched' files that haven't been processed
|
|
if folderpath is None:
|
|
logger.info('Checking folder ' + mylar.CONFIG.CHECK_FOLDER + ' for newly snatched downloads')
|
|
path = mylar.CONFIG.CHECK_FOLDER
|
|
else:
|
|
logger.info('Submitted folder ' + folderpath + ' for direct folder post-processing')
|
|
path = folderpath
|
|
|
|
PostProcess = PostProcessor.PostProcessor('Manual Run', path, queue=queue)
|
|
vals = PostProcess.Process()
|
|
return
|
|
|
|
def LoadAlternateSearchNames(seriesname_alt, comicid):
|
|
#seriesname_alt = db.comics['AlternateSearch']
|
|
AS_Alt = []
|
|
Alternate_Names = {}
|
|
alt_count = 0
|
|
|
|
#logger.fdebug('seriesname_alt:' + str(seriesname_alt))
|
|
if seriesname_alt is None or seriesname_alt == 'None':
|
|
return "no results"
|
|
else:
|
|
chkthealt = seriesname_alt.split('##')
|
|
if chkthealt == 0:
|
|
AS_Alternate = seriesname_alt
|
|
AS_Alt.append(seriesname_alt)
|
|
for calt in chkthealt:
|
|
AS_Alter = re.sub('##', '', calt)
|
|
u_altsearchcomic = AS_Alter.encode('ascii', 'ignore').strip()
|
|
AS_formatrem_seriesname = re.sub('\s+', ' ', u_altsearchcomic)
|
|
if AS_formatrem_seriesname[:1] == ' ': AS_formatrem_seriesname = AS_formatrem_seriesname[1:]
|
|
|
|
AS_Alt.append({"AlternateName": AS_formatrem_seriesname})
|
|
alt_count+=1
|
|
|
|
Alternate_Names['AlternateName'] = AS_Alt
|
|
Alternate_Names['ComicID'] = comicid
|
|
Alternate_Names['Count'] = alt_count
|
|
logger.info('AlternateNames returned:' + str(Alternate_Names))
|
|
|
|
return Alternate_Names
|
|
|
|
def havetotals(refreshit=None):
|
|
#import db
|
|
|
|
comics = []
|
|
myDB = db.DBConnection()
|
|
|
|
if refreshit is None:
|
|
if mylar.CONFIG.ANNUALS_ON:
|
|
comiclist = myDB.select('SELECT comics.*, COUNT(totalAnnuals.IssueID) AS TotalAnnuals FROM comics LEFT JOIN annuals as totalAnnuals on totalAnnuals.ComicID = comics.ComicID GROUP BY comics.ComicID order by comics.ComicSortName COLLATE NOCASE')
|
|
else:
|
|
comiclist = myDB.select('SELECT * FROM comics GROUP BY ComicID order by ComicSortName COLLATE NOCASE')
|
|
else:
|
|
comiclist = []
|
|
comicref = myDB.selectone('SELECT comics.ComicID AS ComicID, comics.Have AS Have, comics.Total as Total, COUNT(totalAnnuals.IssueID) AS TotalAnnuals FROM comics LEFT JOIN annuals as totalAnnuals on totalAnnuals.ComicID = comics.ComicID WHERE comics.ComicID=? GROUP BY comics.ComicID', [refreshit]).fetchone()
|
|
#refreshit is the ComicID passed from the Refresh Series to force/check numerical have totals
|
|
comiclist.append({"ComicID": comicref['ComicID'],
|
|
"Have": comicref['Have'],
|
|
"Total": comicref['Total'],
|
|
"TotalAnnuals": comicref['TotalAnnuals']})
|
|
|
|
for comic in comiclist:
|
|
#--not sure about this part
|
|
#if comic['Total'] is None:
|
|
# if refreshit is not None:
|
|
# logger.fdebug(str(comic['ComicID']) + ' has no issuedata available. Forcing complete Refresh/Rescan')
|
|
# return True
|
|
# else:
|
|
# continue
|
|
try:
|
|
totalissues = comic['Total']
|
|
# if mylar.CONFIG.ANNUALS_ON:
|
|
# totalissues += comic['TotalAnnuals']
|
|
haveissues = comic['Have']
|
|
except TypeError:
|
|
logger.warning('[Warning] ComicID: ' + str(comic['ComicID']) + ' is incomplete - Removing from DB. You should try to re-add the series.')
|
|
myDB.action("DELETE from COMICS WHERE ComicID=? AND ComicName LIKE 'Comic ID%'", [comic['ComicID']])
|
|
myDB.action("DELETE from ISSUES WHERE ComicID=? AND ComicName LIKE 'Comic ID%'", [comic['ComicID']])
|
|
continue
|
|
|
|
if not haveissues:
|
|
havetracks = 0
|
|
|
|
if refreshit is not None:
|
|
if haveissues > totalissues:
|
|
return True # if it's 5/4, send back to updater and don't restore previous status'
|
|
else:
|
|
return False # if it's 5/5 or 4/5, send back to updater and restore previous status'
|
|
|
|
try:
|
|
percent = (haveissues *100.0) /totalissues
|
|
if percent > 100:
|
|
percent = 101
|
|
except (ZeroDivisionError, TypeError):
|
|
percent = 0
|
|
totalissues = '?'
|
|
|
|
if comic['LatestDate'] is None:
|
|
logger.warn(comic['ComicName'] + ' has not finished loading. Nulling some values so things display properly until they can populate.')
|
|
recentstatus = 'Loading'
|
|
elif comic['ComicPublished'] is None or comic['ComicPublished'] == '' or comic['LatestDate'] is None:
|
|
recentstatus = 'Unknown'
|
|
elif comic['ForceContinuing'] == 1:
|
|
recentstatus = 'Continuing'
|
|
elif 'present' in comic['ComicPublished'].lower() or (today()[:4] in comic['LatestDate']):
|
|
latestdate = comic['LatestDate']
|
|
#pull-list f'd up the date by putting '15' instead of '2015' causing 500 server errors
|
|
if '-' in latestdate[:3]:
|
|
st_date = latestdate.find('-')
|
|
st_remainder = latestdate[st_date+1:]
|
|
st_year = latestdate[:st_date]
|
|
year = '20' + st_year
|
|
latestdate = str(year) + '-' + str(st_remainder)
|
|
#logger.fdebug('year set to: ' + latestdate)
|
|
c_date = datetime.date(int(latestdate[:4]), int(latestdate[5:7]), 1)
|
|
n_date = datetime.date.today()
|
|
recentchk = (n_date - c_date).days
|
|
if comic['NewPublish'] is True:
|
|
recentstatus = 'Continuing'
|
|
else:
|
|
#do this just incase and as an extra measure of accuracy hopefully.
|
|
if recentchk < 55:
|
|
recentstatus = 'Continuing'
|
|
else:
|
|
recentstatus = 'Ended'
|
|
else:
|
|
recentstatus = 'Ended'
|
|
|
|
if recentstatus == 'Loading':
|
|
cpub = comic['ComicPublished']
|
|
else:
|
|
try:
|
|
cpub = re.sub('(N)', '', comic['ComicPublished']).strip()
|
|
except Exception as e:
|
|
logger.warn('[Error: %s] No Publisher found for %s - you probably want to Refresh the series when you get a chance.' % (e, comic['ComicName']))
|
|
cpub = None
|
|
|
|
comics.append({"ComicID": comic['ComicID'],
|
|
"ComicName": comic['ComicName'],
|
|
"ComicSortName": comic['ComicSortName'],
|
|
"ComicPublisher": comic['ComicPublisher'],
|
|
"ComicYear": comic['ComicYear'],
|
|
"ComicImage": comic['ComicImage'],
|
|
"LatestIssue": comic['LatestIssue'],
|
|
"LatestDate": comic['LatestDate'],
|
|
"ComicPublished": cpub,
|
|
"Status": comic['Status'],
|
|
"recentstatus": recentstatus,
|
|
"percent": percent,
|
|
"totalissues": totalissues,
|
|
"haveissues": haveissues,
|
|
"DateAdded": comic['LastUpdated'],
|
|
"Type": comic['Type'],
|
|
"Corrected_Type": comic['Corrected_Type']})
|
|
|
|
return comics
|
|
|
|
def filesafe(comic):
|
|
import unicodedata
|
|
if u'\u2014' in comic:
|
|
comic = re.sub(u'\u2014', ' - ', comic)
|
|
try:
|
|
u_comic = unicodedata.normalize('NFKD', comic).encode('ASCII', 'ignore').strip()
|
|
except TypeError:
|
|
u_comic = comic.encode('ASCII', 'ignore').strip()
|
|
|
|
comicname_filesafe = re.sub('[\:\'\"\,\?\!\\\]', '', u_comic)
|
|
comicname_filesafe = re.sub('[\/\*]', '-', comicname_filesafe)
|
|
|
|
return comicname_filesafe
|
|
|
|
def IssueDetails(filelocation, IssueID=None, justinfo=False):
|
|
import zipfile
|
|
from xml.dom.minidom import parseString
|
|
|
|
issuedetails = []
|
|
issuetag = None
|
|
|
|
if justinfo is False:
|
|
dstlocation = os.path.join(mylar.CONFIG.CACHE_DIR, 'temp.zip')
|
|
|
|
|
|
if filelocation.endswith('.cbz'):
|
|
logger.fdebug('CBZ file detected. Checking for .xml within file')
|
|
shutil.copy(filelocation, dstlocation)
|
|
else:
|
|
logger.fdebug('filename is not a cbz : ' + filelocation)
|
|
return
|
|
|
|
cover = "notfound"
|
|
pic_extensions = ('.jpg','.png','.webp')
|
|
modtime = os.path.getmtime(dstlocation)
|
|
low_infile = 999999
|
|
|
|
try:
|
|
with zipfile.ZipFile(dstlocation, 'r') as inzipfile:
|
|
for infile in sorted(inzipfile.namelist()):
|
|
tmp_infile = re.sub("[^0-9]","", infile).strip()
|
|
if tmp_infile == '':
|
|
pass
|
|
elif int(tmp_infile) < int(low_infile):
|
|
low_infile = tmp_infile
|
|
low_infile_name = infile
|
|
if infile == 'ComicInfo.xml':
|
|
logger.fdebug('Extracting ComicInfo.xml to display.')
|
|
dst = os.path.join(mylar.CONFIG.CACHE_DIR, 'ComicInfo.xml')
|
|
data = inzipfile.read(infile)
|
|
#print str(data)
|
|
issuetag = 'xml'
|
|
#looks for the first page and assumes it's the cover. (Alternate covers handled later on)
|
|
elif any(['000.' in infile, '00.' in infile]) and infile.endswith(pic_extensions) and cover == "notfound":
|
|
logger.fdebug('Extracting primary image ' + infile + ' as coverfile for display.')
|
|
local_file = open(os.path.join(mylar.CONFIG.CACHE_DIR, 'temp.jpg'), "wb")
|
|
local_file.write(inzipfile.read(infile))
|
|
local_file.close
|
|
cover = "found"
|
|
elif any(['00a' in infile, '00b' in infile, '00c' in infile, '00d' in infile, '00e' in infile]) and infile.endswith(pic_extensions) and cover == "notfound":
|
|
logger.fdebug('Found Alternate cover - ' + infile + ' . Extracting.')
|
|
altlist = ('00a', '00b', '00c', '00d', '00e')
|
|
for alt in altlist:
|
|
if alt in infile:
|
|
local_file = open(os.path.join(mylar.CONFIG.CACHE_DIR, 'temp.jpg'), "wb")
|
|
local_file.write(inzipfile.read(infile))
|
|
local_file.close
|
|
cover = "found"
|
|
break
|
|
|
|
elif (any(['001.jpg' in infile, '001.png' in infile, '001.webp' in infile, '01.jpg' in infile, '01.png' in infile, '01.webp' in infile]) or all(['0001' in infile, infile.endswith(pic_extensions)]) or all(['01' in infile, infile.endswith(pic_extensions)])) and cover == "notfound":
|
|
logger.fdebug('Extracting primary image ' + infile + ' as coverfile for display.')
|
|
local_file = open(os.path.join(mylar.CONFIG.CACHE_DIR, 'temp.jpg'), "wb")
|
|
local_file.write(inzipfile.read(infile))
|
|
local_file.close
|
|
cover = "found"
|
|
|
|
if cover != "found":
|
|
logger.fdebug('Invalid naming sequence for jpgs discovered. Attempting to find the lowest sequence and will use as cover (it might not work). Currently : ' + str(low_infile))
|
|
local_file = open(os.path.join(mylar.CONFIG.CACHE_DIR, 'temp.jpg'), "wb")
|
|
logger.fdebug('infile_name used for displaying: %s' % low_infile_name)
|
|
local_file.write(inzipfile.read(low_infile_name))
|
|
local_file.close
|
|
cover = "found"
|
|
|
|
except:
|
|
logger.info('ERROR. Unable to properly retrieve the cover for displaying. It\'s probably best to re-tag this file.')
|
|
return
|
|
|
|
ComicImage = os.path.join('cache', 'temp.jpg?' +str(modtime))
|
|
IssueImage = replacetheslash(ComicImage)
|
|
|
|
else:
|
|
IssueImage = "None"
|
|
try:
|
|
with zipfile.ZipFile(filelocation, 'r') as inzipfile:
|
|
for infile in sorted(inzipfile.namelist()):
|
|
if infile == 'ComicInfo.xml':
|
|
logger.fdebug('Found ComicInfo.xml - now retrieving information.')
|
|
data = inzipfile.read(infile)
|
|
issuetag = 'xml'
|
|
break
|
|
except:
|
|
logger.info('ERROR. Unable to properly retrieve the cover for displaying. It\'s probably best to re-tag this file.')
|
|
return
|
|
|
|
|
|
if issuetag is None:
|
|
data = None
|
|
try:
|
|
dz = zipfile.ZipFile(filelocation, 'r')
|
|
data = dz.comment
|
|
except:
|
|
logger.warn('Unable to extract comment field from zipfile.')
|
|
return
|
|
else:
|
|
if data:
|
|
issuetag = 'comment'
|
|
else:
|
|
logger.warn('No metadata available in zipfile comment field.')
|
|
return
|
|
|
|
logger.info('Tag returned as being: ' + str(issuetag))
|
|
|
|
#logger.info('data:' + str(data))
|
|
|
|
if issuetag == 'xml':
|
|
#import easy to use xml parser called minidom:
|
|
dom = parseString(data)
|
|
|
|
results = dom.getElementsByTagName('ComicInfo')
|
|
for result in results:
|
|
try:
|
|
issue_title = result.getElementsByTagName('Title')[0].firstChild.wholeText
|
|
except:
|
|
issue_title = "None"
|
|
try:
|
|
series_title = result.getElementsByTagName('Series')[0].firstChild.wholeText
|
|
except:
|
|
series_title = "None"
|
|
try:
|
|
series_volume = result.getElementsByTagName('Volume')[0].firstChild.wholeText
|
|
except:
|
|
series_volume = "None"
|
|
try:
|
|
issue_number = result.getElementsByTagName('Number')[0].firstChild.wholeText
|
|
except:
|
|
issue_number = "None"
|
|
try:
|
|
summary = result.getElementsByTagName('Summary')[0].firstChild.wholeText
|
|
except:
|
|
summary = "None"
|
|
|
|
if '*List' in summary:
|
|
summary_cut = summary.find('*List')
|
|
summary = summary[:summary_cut]
|
|
#check here to see if Covers exist as they will probably be misnamed when trying to determine the actual cover
|
|
# (ie. 00a.jpg / 00d.jpg - when there's a Cover A or a Cover D listed)
|
|
try:
|
|
notes = result.getElementsByTagName('Notes')[0].firstChild.wholeText #IssueID is in here
|
|
except:
|
|
notes = "None"
|
|
try:
|
|
year = result.getElementsByTagName('Year')[0].firstChild.wholeText
|
|
except:
|
|
year = "None"
|
|
try:
|
|
month = result.getElementsByTagName('Month')[0].firstChild.wholeText
|
|
except:
|
|
month = "None"
|
|
try:
|
|
day = result.getElementsByTagName('Day')[0].firstChild.wholeText
|
|
except:
|
|
day = "None"
|
|
try:
|
|
writer = result.getElementsByTagName('Writer')[0].firstChild.wholeText
|
|
except:
|
|
writer = "None"
|
|
try:
|
|
penciller = result.getElementsByTagName('Penciller')[0].firstChild.wholeText
|
|
except:
|
|
penciller = "None"
|
|
try:
|
|
inker = result.getElementsByTagName('Inker')[0].firstChild.wholeText
|
|
except:
|
|
inker = "None"
|
|
try:
|
|
colorist = result.getElementsByTagName('Colorist')[0].firstChild.wholeText
|
|
except:
|
|
colorist = "None"
|
|
try:
|
|
letterer = result.getElementsByTagName('Letterer')[0].firstChild.wholeText
|
|
except:
|
|
letterer = "None"
|
|
try:
|
|
cover_artist = result.getElementsByTagName('CoverArtist')[0].firstChild.wholeText
|
|
except:
|
|
cover_artist = "None"
|
|
try:
|
|
editor = result.getElementsByTagName('Editor')[0].firstChild.wholeText
|
|
except:
|
|
editor = "None"
|
|
try:
|
|
publisher = result.getElementsByTagName('Publisher')[0].firstChild.wholeText
|
|
except:
|
|
publisher = "None"
|
|
try:
|
|
webpage = result.getElementsByTagName('Web')[0].firstChild.wholeText
|
|
except:
|
|
webpage = "None"
|
|
try:
|
|
pagecount = result.getElementsByTagName('PageCount')[0].firstChild.wholeText
|
|
except:
|
|
pagecount = 0
|
|
|
|
#not used atm.
|
|
#to validate a front cover if it's tagged as one within the zip (some do this)
|
|
#i = 0
|
|
#try:
|
|
# pageinfo = result.getElementsByTagName('Page')[0].attributes
|
|
# if pageinfo: pageinfo_test == True
|
|
#except:
|
|
# pageinfo_test = False
|
|
|
|
#if pageinfo_test:
|
|
# while (i < int(pagecount)):
|
|
# pageinfo = result.getElementsByTagName('Page')[i].attributes
|
|
# attrib = pageinfo.getNamedItem('Image')
|
|
# #logger.fdebug('Frontcover validated as being image #: ' + str(attrib.value))
|
|
# att = pageinfo.getNamedItem('Type')
|
|
# #logger.fdebug('pageinfo: ' + str(pageinfo))
|
|
# if att.value == 'FrontCover':
|
|
# #logger.fdebug('FrontCover detected. Extracting.')
|
|
# break
|
|
# i+=1
|
|
|
|
elif issuetag == 'comment':
|
|
logger.info('CBL Tagging.')
|
|
stripline = 'Archive: ' + filelocation
|
|
data = re.sub(stripline, '', data.encode("utf-8")).strip()
|
|
if data is None or data == '':
|
|
return
|
|
import ast
|
|
ast_data = ast.literal_eval(str(data))
|
|
lastmodified = ast_data['lastModified']
|
|
|
|
dt = ast_data['ComicBookInfo/1.0']
|
|
try:
|
|
publisher = dt['publisher']
|
|
except:
|
|
publisher = None
|
|
try:
|
|
year = dt['publicationYear']
|
|
except:
|
|
year = None
|
|
try:
|
|
month = dt['publicationMonth']
|
|
except:
|
|
month = None
|
|
try:
|
|
day = dt['publicationDay']
|
|
except:
|
|
day = None
|
|
try:
|
|
issue_title = dt['title']
|
|
except:
|
|
issue_title = None
|
|
try:
|
|
series_title = dt['series']
|
|
except:
|
|
series_title = None
|
|
try:
|
|
issue_number = dt['issue']
|
|
except:
|
|
issue_number = None
|
|
try:
|
|
summary = dt['comments']
|
|
except:
|
|
summary = "None"
|
|
|
|
editor = "None"
|
|
colorist = "None"
|
|
artist = "None"
|
|
writer = "None"
|
|
letterer = "None"
|
|
cover_artist = "None"
|
|
penciller = "None"
|
|
inker = "None"
|
|
|
|
try:
|
|
series_volume = dt['volume']
|
|
except:
|
|
series_volume = None
|
|
|
|
try:
|
|
t = dt['credits']
|
|
except:
|
|
editor = None
|
|
colorist = None
|
|
artist = None
|
|
writer = None
|
|
letterer = None
|
|
cover_artist = None
|
|
penciller = None
|
|
inker = None
|
|
|
|
else:
|
|
for cl in dt['credits']:
|
|
if cl['role'] == 'Editor':
|
|
if editor == "None": editor = cl['person']
|
|
else: editor += ', ' + cl['person']
|
|
elif cl['role'] == 'Colorist':
|
|
if colorist == "None": colorist = cl['person']
|
|
else: colorist += ', ' + cl['person']
|
|
elif cl['role'] == 'Artist':
|
|
if artist == "None": artist = cl['person']
|
|
else: artist += ', ' + cl['person']
|
|
elif cl['role'] == 'Writer':
|
|
if writer == "None": writer = cl['person']
|
|
else: writer += ', ' + cl['person']
|
|
elif cl['role'] == 'Letterer':
|
|
if letterer == "None": letterer = cl['person']
|
|
else: letterer += ', ' + cl['person']
|
|
elif cl['role'] == 'Cover':
|
|
if cover_artist == "None": cover_artist = cl['person']
|
|
else: cover_artist += ', ' + cl['person']
|
|
elif cl['role'] == 'Penciller':
|
|
if penciller == "None": penciller = cl['person']
|
|
else: penciller += ', ' + cl['person']
|
|
elif cl['role'] == 'Inker':
|
|
if inker == "None": inker = cl['person']
|
|
else: inker += ', ' + cl['person']
|
|
|
|
try:
|
|
notes = dt['notes']
|
|
except:
|
|
notes = "None"
|
|
try:
|
|
webpage = dt['web']
|
|
except:
|
|
webpage = "None"
|
|
try:
|
|
pagecount = dt['pagecount']
|
|
except:
|
|
pagecount = "None"
|
|
|
|
else:
|
|
logger.warn('Unable to locate any metadata within cbz file. Tag this file and try again if necessary.')
|
|
return
|
|
|
|
issuedetails.append({"title": issue_title,
|
|
"series": series_title,
|
|
"volume": series_volume,
|
|
"issue_number": issue_number,
|
|
"summary": summary,
|
|
"notes": notes,
|
|
"year": year,
|
|
"month": month,
|
|
"day": day,
|
|
"writer": writer,
|
|
"penciller": penciller,
|
|
"inker": inker,
|
|
"colorist": colorist,
|
|
"letterer": letterer,
|
|
"cover_artist": cover_artist,
|
|
"editor": editor,
|
|
"publisher": publisher,
|
|
"webpage": webpage,
|
|
"pagecount": pagecount,
|
|
"IssueImage": IssueImage})
|
|
|
|
return issuedetails
|
|
|
|
def get_issue_title(IssueID=None, ComicID=None, IssueNumber=None, IssueArcID=None):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
if IssueID:
|
|
issue = myDB.selectone('SELECT * FROM issues WHERE IssueID=?', [IssueID]).fetchone()
|
|
if issue is None:
|
|
issue = myDB.selectone('SELECT * FROM annuals WHERE IssueID=?', [IssueID]).fetchone()
|
|
if issue is None:
|
|
logger.fdebug('Unable to locate given IssueID within the db. Assuming Issue Title is None.')
|
|
return None
|
|
else:
|
|
issue = myDB.selectone('SELECT * FROM issues WHERE ComicID=? AND Int_IssueNumber=?', [ComicID, issuedigits(IssueNumber)]).fetchone()
|
|
if issue is None:
|
|
issue = myDB.selectone('SELECT * FROM annuals WHERE IssueID=?', [IssueID]).fetchone()
|
|
if issue is None:
|
|
if IssueArcID:
|
|
issue = myDB.selectone('SELECT * FROM readlist WHERE IssueArcID=?', [IssueArcID]).fetchone()
|
|
if issue is None:
|
|
logger.fdebug('Unable to locate given IssueID within the db. Assuming Issue Title is None.')
|
|
return None
|
|
else:
|
|
logger.fdebug('Unable to locate given IssueID within the db. Assuming Issue Title is None.')
|
|
return None
|
|
|
|
return issue['IssueName']
|
|
|
|
def int_num(s):
|
|
try:
|
|
return int(s)
|
|
except ValueError:
|
|
return float(s)
|
|
|
|
def listPull(weeknumber, year):
|
|
#import db
|
|
library = {}
|
|
myDB = db.DBConnection()
|
|
# Get individual comics
|
|
list = myDB.select("SELECT ComicID FROM Weekly WHERE weeknumber=? AND year=?", [weeknumber,year])
|
|
for row in list:
|
|
library[row['ComicID']] = row['ComicID']
|
|
return library
|
|
|
|
def listLibrary(comicid=None):
|
|
#import db
|
|
library = {}
|
|
myDB = db.DBConnection()
|
|
if comicid is None:
|
|
if mylar.CONFIG.ANNUALS_ON is True:
|
|
list = myDB.select("SELECT a.comicid, b.releasecomicid, a.status FROM Comics AS a LEFT JOIN annuals AS b on a.comicid=b.comicid group by a.comicid")
|
|
else:
|
|
list = myDB.select("SELECT comicid, status FROM Comics group by comicid")
|
|
else:
|
|
if mylar.CONFIG.ANNUALS_ON is True:
|
|
list = myDB.select("SELECT a.comicid, b.releasecomicid, a.status FROM Comics AS a LEFT JOIN annuals AS b on a.comicid=b.comicid WHERE a.comicid=? group by a.comicid", [re.sub('4050-', '', comicid).strip()])
|
|
else:
|
|
list = myDB.select("SELECT comicid, status FROM Comics WHERE comicid=? group by comicid", [re.sub('4050-', '', comicid).strip()])
|
|
|
|
for row in list:
|
|
library[row['ComicID']] = {'comicid': row['ComicID'],
|
|
'status': row['Status']}
|
|
try:
|
|
if row['ReleaseComicID'] is not None:
|
|
library[row['ReleaseComicID']] = {'comicid': row['ComicID'],
|
|
'status': row['Status']}
|
|
except:
|
|
pass
|
|
|
|
return library
|
|
|
|
def listStoryArcs():
|
|
#import db
|
|
library = {}
|
|
myDB = db.DBConnection()
|
|
# Get Distinct Arc IDs
|
|
#list = myDB.select("SELECT DISTINCT(StoryArcID) FROM storyarcs");
|
|
#for row in list:
|
|
# library[row['StoryArcID']] = row['StoryArcID']
|
|
# Get Distinct CV Arc IDs
|
|
list = myDB.select("SELECT DISTINCT(CV_ArcID) FROM storyarcs");
|
|
for row in list:
|
|
library[row['CV_ArcID']] = {'comicid': row['CV_ArcID']}
|
|
return library
|
|
|
|
def listoneoffs(weeknumber, year):
|
|
#import db
|
|
library = []
|
|
myDB = db.DBConnection()
|
|
# Get Distinct one-off issues from the pullist that have already been downloaded / snatched
|
|
list = myDB.select("SELECT DISTINCT(IssueID), Status, ComicID, ComicName, Status, IssueNumber FROM oneoffhistory WHERE weeknumber=? and year=? AND Status='Downloaded' OR Status='Snatched'", [weeknumber, year])
|
|
for row in list:
|
|
library.append({'IssueID': row['IssueID'],
|
|
'ComicID': row['ComicID'],
|
|
'ComicName': row['ComicName'],
|
|
'IssueNumber': row['IssueNumber'],
|
|
'Status': row['Status'],
|
|
'weeknumber': weeknumber,
|
|
'year': year})
|
|
return library
|
|
|
|
def manualArc(issueid, reading_order, storyarcid):
|
|
#import db
|
|
if issueid.startswith('4000-'):
|
|
issueid = issueid[5:]
|
|
|
|
myDB = db.DBConnection()
|
|
|
|
arc_chk = myDB.select("SELECT * FROM storyarcs WHERE StoryArcID=? AND NOT Manual is 'deleted'", [storyarcid])
|
|
storyarcname = arc_chk[0]['StoryArc']
|
|
storyarcissues = arc_chk[0]['TotalIssues']
|
|
|
|
iss_arcids = []
|
|
for issarc in arc_chk:
|
|
iss_arcids.append({"IssueArcID": issarc['IssueArcID'],
|
|
"IssueID": issarc['IssueID'],
|
|
"Manual": issarc['Manual'],
|
|
"ReadingOrder": issarc['ReadingOrder']})
|
|
|
|
|
|
arc_results = mylar.cv.getComic(comicid=None, type='issue', issueid=None, arcid=storyarcid, arclist='M' + str(issueid))
|
|
arcval = arc_results['issuechoice'][0]
|
|
comicname = arcval['ComicName']
|
|
st_d = mylar.filechecker.FileChecker(watchcomic=comicname)
|
|
st_dyninfo = st_d.dynamic_replace(comicname)
|
|
dynamic_name = re.sub('[\|\s]','', st_dyninfo['mod_seriesname'].lower()).strip()
|
|
issname = arcval['Issue_Name']
|
|
issid = str(arcval['IssueID'])
|
|
comicid = str(arcval['ComicID'])
|
|
cidlist = str(comicid)
|
|
st_issueid = None
|
|
manual_mod = 'added'
|
|
new_readorder = []
|
|
for aid in iss_arcids:
|
|
if aid['IssueID'] == issid:
|
|
logger.info('Issue already exists for storyarc [IssueArcID:' + aid['IssueArcID'] + '][Manual:' + aid['Manual'])
|
|
st_issueid = aid['IssueArcID']
|
|
manual_mod = aid['Manual']
|
|
|
|
if reading_order is None:
|
|
#if no reading order is given, drop in the last spot.
|
|
reading_order = len(iss_arcids) + 1
|
|
if int(aid['ReadingOrder']) >= int(reading_order):
|
|
reading_seq = int(aid['ReadingOrder']) + 1
|
|
else:
|
|
reading_seq = int(aid['ReadingOrder'])
|
|
|
|
new_readorder.append({'IssueArcID': aid['IssueArcID'],
|
|
'IssueID': aid['IssueID'],
|
|
'ReadingOrder': reading_seq})
|
|
|
|
import random
|
|
if st_issueid is None:
|
|
st_issueid = str(storyarcid) + "_" + str(random.randint(1000,9999))
|
|
issnum = arcval['Issue_Number']
|
|
issdate = str(arcval['Issue_Date'])
|
|
storedate = str(arcval['Store_Date'])
|
|
int_issnum = issuedigits(issnum)
|
|
|
|
comicid_results = mylar.cv.getComic(comicid=None, type='comicyears', comicidlist=cidlist)
|
|
seriesYear = 'None'
|
|
issuePublisher = 'None'
|
|
seriesVolume = 'None'
|
|
|
|
if issname is None:
|
|
IssueName = 'None'
|
|
else:
|
|
IssueName = issname[:70]
|
|
|
|
for cid in comicid_results:
|
|
if cid['ComicID'] == comicid:
|
|
seriesYear = cid['SeriesYear']
|
|
issuePublisher = cid['Publisher']
|
|
seriesVolume = cid['Volume']
|
|
#assume that the arc is the same
|
|
storyarcpublisher = issuePublisher
|
|
break
|
|
|
|
|
|
newCtrl = {"IssueID": issid,
|
|
"StoryArcID": storyarcid}
|
|
newVals = {"ComicID": comicid,
|
|
"IssueArcID": st_issueid,
|
|
"StoryArc": storyarcname,
|
|
"ComicName": comicname,
|
|
"Volume": seriesVolume,
|
|
"DynamicComicName": dynamic_name,
|
|
"IssueName": IssueName,
|
|
"IssueNumber": issnum,
|
|
"Publisher": storyarcpublisher,
|
|
"TotalIssues": str(int(storyarcissues) +1),
|
|
"ReadingOrder": int(reading_order), #arbitrarily set it to the last reading order sequence # just to see if it works.
|
|
"IssueDate": issdate,
|
|
"ReleaseDate": storedate,
|
|
"SeriesYear": seriesYear,
|
|
"IssuePublisher": issuePublisher,
|
|
"CV_ArcID": storyarcid,
|
|
"Int_IssueNumber": int_issnum,
|
|
"Manual": manual_mod}
|
|
|
|
myDB.upsert("storyarcs", newVals, newCtrl)
|
|
|
|
#now we resequence the reading-order to accomdate the change.
|
|
logger.info('Adding the new issue into the reading order & resequencing the order to make sure there are no sequence drops...')
|
|
new_readorder.append({'IssueArcID': st_issueid,
|
|
'IssueID': issid,
|
|
'ReadingOrder': int(reading_order)})
|
|
|
|
newrl = 0
|
|
for rl in sorted(new_readorder, key=itemgetter('ReadingOrder'), reverse=False):
|
|
if rl['ReadingOrder'] - 1 != newrl:
|
|
rorder = newrl + 1
|
|
logger.fdebug(rl['IssueID'] + ' - changing reading order seq to : ' + str(rorder))
|
|
else:
|
|
rorder = rl['ReadingOrder']
|
|
logger.fdebug(rl['IssueID'] + ' - setting reading order seq to : ' + str(rorder))
|
|
|
|
rl_ctrl = {"IssueID": rl['IssueID'],
|
|
"IssueArcID": rl['IssueArcID'],
|
|
"StoryArcID": storyarcid}
|
|
r1_new = {"ReadingOrder": rorder}
|
|
newrl = rorder
|
|
|
|
myDB.upsert("storyarcs", r1_new, rl_ctrl)
|
|
|
|
#check to see if the issue exists already so we can set the status right away.
|
|
iss_chk = myDB.selectone('SELECT * FROM issues where issueid = ?', [issueid]).fetchone()
|
|
if iss_chk is None:
|
|
logger.info('Issue is not currently in your watchlist. Setting status to Skipped')
|
|
status_change = 'Skipped'
|
|
else:
|
|
status_change = iss_chk['Status']
|
|
logger.info('Issue currently exists in your watchlist. Setting status to ' + status_change)
|
|
myDB.upsert("storyarcs", {'Status': status_change}, newCtrl)
|
|
|
|
return
|
|
|
|
def listIssues(weeknumber, year):
|
|
#import db
|
|
library = []
|
|
myDB = db.DBConnection()
|
|
# Get individual issues
|
|
list = myDB.select("SELECT issues.Status, issues.ComicID, issues.IssueID, issues.ComicName, issues.IssueDate, issues.ReleaseDate, weekly.publisher, issues.Issue_Number from weekly, issues where weekly.IssueID = issues.IssueID and weeknumber = ? and year = ?", [int(weeknumber), year])
|
|
for row in list:
|
|
if row['ReleaseDate'] is None:
|
|
tmpdate = row['IssueDate']
|
|
else:
|
|
tmpdate = row['ReleaseDate']
|
|
library.append({'ComicID': row['ComicID'],
|
|
'Status': row['Status'],
|
|
'IssueID': row['IssueID'],
|
|
'ComicName': row['ComicName'],
|
|
'Publisher': row['publisher'],
|
|
'Issue_Number': row['Issue_Number'],
|
|
'IssueYear': tmpdate})
|
|
|
|
# Add the annuals
|
|
if mylar.CONFIG.ANNUALS_ON:
|
|
list = myDB.select("SELECT annuals.Status, annuals.ComicID, annuals.ReleaseComicID, annuals.IssueID, annuals.ComicName, annuals.ReleaseDate, annuals.IssueDate, weekly.publisher, annuals.Issue_Number from weekly, annuals where weekly.IssueID = annuals.IssueID and weeknumber = ? and year = ?", [int(weeknumber), year])
|
|
for row in list:
|
|
if row['ReleaseDate'] is None:
|
|
tmpdate = row['IssueDate']
|
|
else:
|
|
tmpdate = row['ReleaseDate']
|
|
library.append({'ComicID': row['ComicID'],
|
|
'Status': row['Status'],
|
|
'IssueID': row['IssueID'],
|
|
'ComicName': row['ComicName'],
|
|
'Publisher': row['publisher'],
|
|
'Issue_Number': row['Issue_Number'],
|
|
'IssueYear': tmpdate})
|
|
|
|
#tmplist = library
|
|
#librarylist = []
|
|
#for liblist in tmplist:
|
|
# lb = myDB.select('SELECT ComicVersion, Type, ComicYear, ComicID from comics WHERE ComicID=?', [liblist['ComicID']])
|
|
# librarylist.append(liblist)
|
|
# librarylist.update({'Comic_Volume': lb['ComicVersion'],
|
|
# 'ComicYear': lb['ComicYear'],
|
|
# 'ComicType': lb['Type']})
|
|
return library
|
|
|
|
def incr_snatched(ComicID):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
incr_count = myDB.selectone("SELECT Have FROM Comics WHERE ComicID=?", [ComicID]).fetchone()
|
|
logger.fdebug('Incrementing HAVE count total to : ' + str(incr_count['Have'] + 1))
|
|
newCtrl = {"ComicID": ComicID}
|
|
newVal = {"Have": incr_count['Have'] + 1}
|
|
myDB.upsert("comics", newVal, newCtrl)
|
|
return
|
|
|
|
def duplicate_filecheck(filename, ComicID=None, IssueID=None, StoryArcID=None, rtnval=None):
|
|
#filename = the filename in question that's being checked against
|
|
#comicid = the comicid of the series that's being checked for duplication
|
|
#issueid = the issueid of the issue that's being checked for duplication
|
|
#storyarcid = the storyarcid of the issue that's being checked for duplication.
|
|
#rtnval = the return value of a previous duplicate_filecheck that's re-running against new values
|
|
#
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
|
|
logger.info('[DUPECHECK] Duplicate check for ' + filename)
|
|
try:
|
|
filesz = os.path.getsize(filename)
|
|
except OSError as e:
|
|
logger.warn('[DUPECHECK] File cannot be located in location specified. Something has moved or altered the name.')
|
|
logger.warn('[DUPECHECK] Make sure if you are using ComicRN, you do not have Completed Download Handling enabled (or vice-versa). Aborting')
|
|
return
|
|
|
|
if IssueID:
|
|
dupchk = myDB.selectone("SELECT * FROM issues WHERE IssueID=?", [IssueID]).fetchone()
|
|
if dupchk is None:
|
|
dupchk = myDB.selectone("SELECT * FROM annuals WHERE IssueID=?", [IssueID]).fetchone()
|
|
if dupchk is None:
|
|
logger.info('[DUPECHECK] Unable to find corresponding Issue within the DB. Do you still have the series on your watchlist?')
|
|
return
|
|
|
|
series = myDB.selectone("SELECT * FROM comics WHERE ComicID=?", [dupchk['ComicID']]).fetchone()
|
|
|
|
#if it's a retry and the file was already snatched, the status is Snatched and won't hit the dupecheck.
|
|
#rtnval will be one of 3:
|
|
#'write' - write new file
|
|
#'dupe_file' - do not write new file as existing file is better quality
|
|
#'dupe_src' - write new file, as existing file is a lesser quality (dupe)
|
|
|
|
if dupchk['Status'] == 'Downloaded' or dupchk['Status'] == 'Archived':
|
|
try:
|
|
dupsize = dupchk['ComicSize']
|
|
except:
|
|
logger.info('[DUPECHECK] Duplication detection returned no hits as this is a new Snatch. This is not a duplicate.')
|
|
rtnval = {'action': "write"}
|
|
|
|
logger.info('[DUPECHECK] Existing Status already set to ' + dupchk['Status'])
|
|
cid = []
|
|
if dupsize is None:
|
|
logger.info('[DUPECHECK] Existing filesize is 0 bytes as I cannot locate the orginal entry - it is probably archived.')
|
|
logger.fdebug('[DUPECHECK] Checking series for unrefreshed series syndrome (USS).')
|
|
havechk = myDB.selectone('SELECT * FROM comics WHERE ComicID=?', [ComicID]).fetchone()
|
|
if havechk:
|
|
if havechk['Have'] > havechk['Total']:
|
|
logger.info('[DUPECHECK] Series has invalid issue totals [' + str(havechk['Have']) + '/' + str(havechk['Total']) + '] Attempting to Refresh & continue post-processing this issue.')
|
|
cid.append(ComicID)
|
|
logger.fdebug('[DUPECHECK] ComicID: ' + str(ComicID))
|
|
mylar.updater.dbUpdate(ComicIDList=cid, calledfrom='dupechk')
|
|
return duplicate_filecheck(filename, ComicID, IssueID, StoryArcID)
|
|
else:
|
|
if rtnval is not None:
|
|
if rtnval['action'] == 'dont_dupe':
|
|
logger.fdebug('[DUPECHECK] File is Archived but no file can be located within the db at the specified location. Assuming this was a manual archival and will not post-process this issue.')
|
|
return rtnval
|
|
else:
|
|
rtnval = {'action': "dont_dupe"}
|
|
#file is Archived, but no entry exists in the db for the location. Assume Archived, and don't post-process.
|
|
#quick rescan of files in dir, then rerun the dup check again...
|
|
mylar.updater.forceRescan(ComicID)
|
|
chk1 = duplicate_filecheck(filename, ComicID, IssueID, StoryArcID, rtnval)
|
|
rtnval = chk1
|
|
else:
|
|
rtnval = {'action': "dupe_file",
|
|
'to_dupe': os.path.join(series['ComicLocation'], dupchk['Location'])}
|
|
else:
|
|
logger.info('[DUPECHECK] Existing file within db :' + dupchk['Location'] + ' has a filesize of : ' + str(dupsize) + ' bytes.')
|
|
|
|
#keywords to force keep / delete
|
|
#this will be eventually user-controlled via the GUI once the options are enabled.
|
|
|
|
if int(dupsize) == 0:
|
|
logger.info('[DUPECHECK] Existing filesize is 0 as I cannot locate the original entry.')
|
|
if dupchk['Status'] == 'Archived':
|
|
logger.info('[DUPECHECK] Assuming issue is Archived.')
|
|
rtnval = {'action': "dupe_file",
|
|
'to_dupe': filename}
|
|
return rtnval
|
|
else:
|
|
logger.info('[DUPECHECK] Assuming 0-byte file - this one is gonna get hammered.')
|
|
|
|
logger.fdebug('[DUPECHECK] Based on duplication preferences I will retain based on : ' + mylar.CONFIG.DUPECONSTRAINT)
|
|
|
|
tmp_dupeconstraint = mylar.CONFIG.DUPECONSTRAINT
|
|
|
|
if any(['cbr' in mylar.CONFIG.DUPECONSTRAINT, 'cbz' in mylar.CONFIG.DUPECONSTRAINT]):
|
|
if 'cbr' in mylar.CONFIG.DUPECONSTRAINT:
|
|
if filename.endswith('.cbr'):
|
|
#this has to be configured in config - either retain cbr or cbz.
|
|
if dupchk['Location'].endswith('.cbr'):
|
|
logger.info('[DUPECHECK-CBR PRIORITY] [#' + dupchk['Issue_Number'] + '] BOTH files are in cbr format. Retaining the larger filesize of the two.')
|
|
tmp_dupeconstraint = 'filesize'
|
|
else:
|
|
#keep filename
|
|
logger.info('[DUPECHECK-CBR PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining newly scanned in file : ' + filename)
|
|
rtnval = {'action': "dupe_src",
|
|
'to_dupe': os.path.join(series['ComicLocation'], dupchk['Location'])}
|
|
else:
|
|
if dupchk['Location'].endswith('.cbz'):
|
|
logger.info('[DUPECHECK-CBR PRIORITY] [#' + dupchk['Issue_Number'] + '] BOTH files are in cbz format. Retaining the larger filesize of the two.')
|
|
tmp_dupeconstraint = 'filesize'
|
|
else:
|
|
#keep filename
|
|
logger.info('[DUPECHECK-CBR PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining newly scanned in file : ' + dupchk['Location'])
|
|
rtnval = {'action': "dupe_file",
|
|
'to_dupe': filename}
|
|
|
|
elif 'cbz' in mylar.CONFIG.DUPECONSTRAINT:
|
|
if filename.endswith('.cbr'):
|
|
if dupchk['Location'].endswith('.cbr'):
|
|
logger.info('[DUPECHECK-CBZ PRIORITY] [#' + dupchk['Issue_Number'] + '] BOTH files are in cbr format. Retaining the larger filesize of the two.')
|
|
tmp_dupeconstraint = 'filesize'
|
|
else:
|
|
#keep filename
|
|
logger.info('[DUPECHECK-CBZ PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining currently scanned in filename : ' + dupchk['Location'])
|
|
rtnval = {'action': "dupe_file",
|
|
'to_dupe': filename}
|
|
else:
|
|
if dupchk['Location'].endswith('.cbz'):
|
|
logger.info('[DUPECHECK-CBZ PRIORITY] [#' + dupchk['Issue_Number'] + '] BOTH files are in cbz format. Retaining the larger filesize of the two.')
|
|
tmp_dupeconstraint = 'filesize'
|
|
else:
|
|
#keep filename
|
|
logger.info('[DUPECHECK-CBZ PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining newly scanned in filename : ' + filename)
|
|
rtnval = {'action': "dupe_src",
|
|
'to_dupe': os.path.join(series['ComicLocation'], dupchk['Location'])}
|
|
|
|
if mylar.CONFIG.DUPECONSTRAINT == 'filesize' or tmp_dupeconstraint == 'filesize':
|
|
if filesz <= int(dupsize) and int(dupsize) != 0:
|
|
logger.info('[DUPECHECK-FILESIZE PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining currently scanned in filename : ' + dupchk['Location'])
|
|
rtnval = {'action': "dupe_file",
|
|
'to_dupe': filename}
|
|
else:
|
|
logger.info('[DUPECHECK-FILESIZE PRIORITY] [#' + dupchk['Issue_Number'] + '] Retaining newly scanned in filename : ' + filename)
|
|
rtnval = {'action': "dupe_src",
|
|
'to_dupe': os.path.join(series['ComicLocation'], dupchk['Location'])}
|
|
|
|
else:
|
|
logger.info('[DUPECHECK] Duplication detection returned no hits. This is not a duplicate of anything that I have scanned in as of yet.')
|
|
rtnval = {'action': "write"}
|
|
return rtnval
|
|
|
|
def create_https_certificates(ssl_cert, ssl_key):
|
|
"""
|
|
Create a pair of self-signed HTTPS certificares and store in them in
|
|
'ssl_cert' and 'ssl_key'. Method assumes pyOpenSSL is installed.
|
|
|
|
This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard).
|
|
"""
|
|
|
|
from OpenSSL import crypto
|
|
from certgen import createKeyPair, createCertRequest, createCertificate, \
|
|
TYPE_RSA, serial
|
|
|
|
# Create the CA Certificate
|
|
cakey = createKeyPair(TYPE_RSA, 2048)
|
|
careq = createCertRequest(cakey, CN="Certificate Authority")
|
|
cacert = createCertificate(careq, (careq, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years
|
|
|
|
pkey = createKeyPair(TYPE_RSA, 2048)
|
|
req = createCertRequest(pkey, CN="Mylar")
|
|
cert = createCertificate(req, (cacert, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years
|
|
|
|
# Save the key and certificate to disk
|
|
try:
|
|
with open(ssl_key, "w") as fp:
|
|
fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
|
with open(ssl_cert, "w") as fp:
|
|
fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
|
except IOError as e:
|
|
logger.error("Error creating SSL key and certificate: %s", e)
|
|
return False
|
|
|
|
return True
|
|
|
|
def torrent_create(site, linkid, alt=None):
|
|
if any([site == '32P', site == 'TOR']):
|
|
pass
|
|
#elif site == 'TPSE':
|
|
# if alt is None:
|
|
# url = mylar.TPSEURL + 'torrent/' + str(linkid) + '.torrent'
|
|
# else:
|
|
# url = mylar.TPSEURL + 'torrent/' + str(linkid) + '.torrent'
|
|
elif site == 'DEM':
|
|
url = mylar.DEMURL + 'files/download/' + str(linkid) + '/'
|
|
elif site == 'WWT':
|
|
url = mylar.WWTURL + 'download.php'
|
|
|
|
return url
|
|
|
|
def parse_32pfeed(rssfeedline):
|
|
KEYS_32P = {}
|
|
if mylar.CONFIG.ENABLE_32P and len(rssfeedline) > 1:
|
|
userid_st = rssfeedline.find('&user')
|
|
userid_en = rssfeedline.find('&', userid_st +1)
|
|
if userid_en == -1:
|
|
USERID_32P = rssfeedline[userid_st +6:]
|
|
else:
|
|
USERID_32P = rssfeedline[userid_st +6:userid_en]
|
|
|
|
auth_st = rssfeedline.find('&auth')
|
|
auth_en = rssfeedline.find('&', auth_st +1)
|
|
if auth_en == -1:
|
|
AUTH_32P = rssfeedline[auth_st +6:]
|
|
else:
|
|
AUTH_32P = rssfeedline[auth_st +6:auth_en]
|
|
|
|
authkey_st = rssfeedline.find('&authkey')
|
|
authkey_en = rssfeedline.find('&', authkey_st +1)
|
|
if authkey_en == -1:
|
|
AUTHKEY_32P = rssfeedline[authkey_st +9:]
|
|
else:
|
|
AUTHKEY_32P = rssfeedline[authkey_st +9:authkey_en]
|
|
|
|
KEYS_32P = {"user": USERID_32P,
|
|
"auth": AUTH_32P,
|
|
"authkey": AUTHKEY_32P,
|
|
"passkey": mylar.CONFIG.PASSKEY_32P}
|
|
|
|
return KEYS_32P
|
|
|
|
def humanize_time(amount, units = 'seconds'):
|
|
|
|
def process_time(amount, units):
|
|
|
|
INTERVALS = [ 1, 60,
|
|
60*60,
|
|
60*60*24,
|
|
60*60*24*7,
|
|
60*60*24*7*4,
|
|
60*60*24*7*4*12,
|
|
60*60*24*7*4*12*100,
|
|
60*60*24*7*4*12*100*10]
|
|
NAMES = [('second', 'seconds'),
|
|
('minute', 'minutes'),
|
|
('hour', 'hours'),
|
|
('day', 'days'),
|
|
('week', 'weeks'),
|
|
('month', 'months'),
|
|
('year', 'years'),
|
|
('century', 'centuries'),
|
|
('millennium', 'millennia')]
|
|
|
|
result = []
|
|
|
|
unit = map(lambda a: a[1], NAMES).index(units)
|
|
# Convert to seconds
|
|
amount = amount * INTERVALS[unit]
|
|
|
|
for i in range(len(NAMES)-1, -1, -1):
|
|
a = amount // INTERVALS[i]
|
|
if a > 0:
|
|
result.append( (a, NAMES[i][1 % a]) )
|
|
amount -= a * INTERVALS[i]
|
|
|
|
return result
|
|
|
|
rd = process_time(int(amount), units)
|
|
cont = 0
|
|
for u in rd:
|
|
if u[0] > 0:
|
|
cont += 1
|
|
|
|
buf = ''
|
|
i = 0
|
|
for u in rd:
|
|
if u[0] > 0:
|
|
buf += "%d %s" % (u[0], u[1])
|
|
cont -= 1
|
|
|
|
if i < (len(rd)-1):
|
|
if cont > 1:
|
|
buf += ", "
|
|
else:
|
|
buf += " and "
|
|
|
|
i += 1
|
|
|
|
return buf
|
|
|
|
def issue_status(IssueID):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
|
|
IssueID = str(IssueID)
|
|
|
|
logger.fdebug('[ISSUE-STATUS] Issue Status Check for %s' % IssueID)
|
|
|
|
isschk = myDB.selectone("SELECT * FROM issues WHERE IssueID=?", [IssueID]).fetchone()
|
|
if isschk is None:
|
|
isschk = myDB.selectone("SELECT * FROM annuals WHERE IssueID=?", [IssueID]).fetchone()
|
|
if isschk is None:
|
|
isschk = myDB.selectone("SELECT * FROM storyarcs WHERE IssueArcID=?", [IssueID]).fetchone()
|
|
if isschk is None:
|
|
logger.warn('Unable to retrieve IssueID from db. This is a problem. Aborting.')
|
|
return False
|
|
|
|
if any([isschk['Status'] == 'Downloaded', isschk['Status'] == 'Snatched']):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def crc(filename):
|
|
#memory in lieu of speed (line by line)
|
|
#prev = 0
|
|
#for eachLine in open(filename,"rb"):
|
|
# prev = zlib.crc32(eachLine, prev)
|
|
#return "%X"%(prev & 0xFFFFFFFF)
|
|
|
|
#speed in lieu of memory (file into memory entirely)
|
|
#return "%X" % (zlib.crc32(open(filename, "rb").read()) & 0xFFFFFFFF)
|
|
filename = filename.encode(mylar.SYS_ENCODING)
|
|
return hashlib.md5(filename).hexdigest()
|
|
|
|
def issue_find_ids(ComicName, ComicID, pack, IssueNumber):
|
|
#import db
|
|
|
|
myDB = db.DBConnection()
|
|
|
|
issuelist = myDB.select("SELECT * FROM issues WHERE ComicID=?", [ComicID])
|
|
|
|
if 'Annual' not in pack:
|
|
packlist = [x.strip() for x in pack.split(',')]
|
|
plist = []
|
|
pack_issues = []
|
|
for pl in packlist:
|
|
if '-' in pl:
|
|
plist.append(range(int(pl[:pl.find('-')]),int(pl[pl.find('-')+1:])+1))
|
|
else:
|
|
plist.append(int(pl))
|
|
|
|
for pi in plist:
|
|
if type(pi) == list:
|
|
for x in pi:
|
|
pack_issues.append(x)
|
|
else:
|
|
pack_issues.append(pi)
|
|
|
|
pack_issues.sort()
|
|
annualize = False
|
|
else:
|
|
#remove the annuals wording
|
|
tmp_annuals = pack[pack.find('Annual'):]
|
|
tmp_ann = re.sub('[annual/annuals/+]', '', tmp_annuals.lower()).strip()
|
|
tmp_pack = re.sub('[annual/annuals/+]', '', pack.lower()).strip()
|
|
pack_issues_numbers = re.findall(r'\d+', tmp_pack)
|
|
pack_issues = range(int(pack_issues_numbers[0]),int(pack_issues_numbers[1])+1)
|
|
annualize = True
|
|
|
|
issues = {}
|
|
issueinfo = []
|
|
|
|
Int_IssueNumber = issuedigits(IssueNumber)
|
|
valid = False
|
|
|
|
for iss in pack_issues:
|
|
int_iss = issuedigits(iss)
|
|
for xb in issuelist:
|
|
if xb['Status'] != 'Downloaded':
|
|
if xb['Int_IssueNumber'] == int_iss:
|
|
issueinfo.append({'issueid': xb['IssueID'],
|
|
'int_iss': int_iss,
|
|
'issuenumber': xb['Issue_Number']})
|
|
break
|
|
|
|
for x in issueinfo:
|
|
if Int_IssueNumber == x['int_iss']:
|
|
valid = True
|
|
break
|
|
|
|
issues['issues'] = issueinfo
|
|
|
|
if len(issues['issues']) == len(pack_issues):
|
|
logger.info('Complete issue count of ' + str(len(pack_issues)) + ' issues are available within this pack for ' + ComicName)
|
|
else:
|
|
logger.info('Issue counts are not complete (not a COMPLETE pack) for ' + ComicName)
|
|
|
|
issues['issue_range'] = pack_issues
|
|
issues['valid'] = valid
|
|
return issues
|
|
|
|
def conversion(value):
|
|
if type(value) == str:
|
|
try:
|
|
value = value.decode('utf-8')
|
|
except:
|
|
value = value.decode('windows-1252')
|
|
return value
|
|
|
|
def clean_url(url):
|
|
leading = len(url) - len(url.lstrip(' '))
|
|
ending = len(url) - len(url.rstrip(' '))
|
|
if leading >= 1:
|
|
url = url[leading:]
|
|
if ending >=1:
|
|
url = url[:-ending]
|
|
return url
|
|
|
|
def chunker(seq, size):
|
|
#returns a list from a large group of tuples by size (ie. for group in chunker(seq, 3))
|
|
return [seq[pos:pos + size] for pos in xrange(0, len(seq), size)]
|
|
|
|
def cleanHost(host, protocol = True, ssl = False, username = None, password = None):
|
|
""" Return a cleaned up host with given url options set
|
|
taken verbatim from CouchPotato
|
|
Changes protocol to https if ssl is set to True and http if ssl is set to false.
|
|
>>> cleanHost("localhost:80", ssl=True)
|
|
'https://localhost:80/'
|
|
>>> cleanHost("localhost:80", ssl=False)
|
|
'http://localhost:80/'
|
|
|
|
Username and password is managed with the username and password variables
|
|
>>> cleanHost("localhost:80", username="user", password="passwd")
|
|
'http://user:passwd@localhost:80/'
|
|
|
|
Output without scheme (protocol) can be forced with protocol=False
|
|
>>> cleanHost("localhost:80", protocol=False)
|
|
'localhost:80'
|
|
"""
|
|
|
|
if not '://' in host and protocol:
|
|
host = ('https://' if ssl else 'http://') + host
|
|
|
|
if not protocol:
|
|
host = host.split('://', 1)[-1]
|
|
|
|
if protocol and username and password:
|
|
try:
|
|
auth = re.findall('^(?:.+?//)(.+?):(.+?)@(?:.+)$', host)
|
|
if auth:
|
|
log.error('Cleanhost error: auth already defined in url: %s, please remove BasicAuth from url.', host)
|
|
else:
|
|
host = host.replace('://', '://%s:%s@' % (username, password), 1)
|
|
except:
|
|
pass
|
|
|
|
host = host.rstrip('/ ')
|
|
if protocol:
|
|
host += '/'
|
|
|
|
return host
|
|
|
|
def checkthe_id(comicid=None, up_vals=None):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
if not up_vals:
|
|
chk = myDB.selectone("SELECT * from ref32p WHERE ComicID=?", [comicid]).fetchone()
|
|
if chk is None:
|
|
return None
|
|
else:
|
|
#if updated time hasn't been set or it's > 24 hrs, blank the entry so we can make sure we pull an updated groupid from 32p
|
|
if chk['Updated'] is None:
|
|
logger.fdebug('Reference found for 32p - but the id has never been verified after populating. Verifying it is still the right id before proceeding.')
|
|
return None
|
|
else:
|
|
c_obj_date = datetime.datetime.strptime(chk['Updated'], "%Y-%m-%d %H:%M:%S")
|
|
n_date = datetime.datetime.now()
|
|
absdiff = abs(n_date - c_obj_date)
|
|
hours = (absdiff.days * 24 * 60 * 60 + absdiff.seconds) / 3600.0
|
|
if hours >= 24:
|
|
logger.fdebug('Reference found for 32p - but older than 24hours since last checked. Verifying it is still the right id before proceeding.')
|
|
return None
|
|
else:
|
|
return {'id': chk['ID'],
|
|
'series': chk['Series']}
|
|
|
|
else:
|
|
ctrlVal = {'ComicID': comicid}
|
|
newVal = {'Series': up_vals[0]['series'],
|
|
'ID': up_vals[0]['id'],
|
|
'Updated': now()}
|
|
myDB.upsert("ref32p", newVal, ctrlVal)
|
|
|
|
def updatearc_locs(storyarcid, issues):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
issuelist = []
|
|
for x in issues:
|
|
issuelist.append(x['IssueID'])
|
|
tmpsql = "SELECT a.comicid, a.comiclocation, b.comicid, b.status, b.issueid, b.location FROM comics as a INNER JOIN issues as b ON a.comicid = b.comicid WHERE b.issueid in ({seq})".format(seq=','.join(['?'] *(len(issuelist))))
|
|
chkthis = myDB.select(tmpsql, issuelist)
|
|
update_iss = []
|
|
if chkthis is None:
|
|
return
|
|
else:
|
|
for chk in chkthis:
|
|
if chk['Status'] == 'Downloaded':
|
|
pathsrc = os.path.join(chk['ComicLocation'], chk['Location'])
|
|
if not os.path.exists(pathsrc):
|
|
try:
|
|
if all([mylar.CONFIG.MULTIPLE_DEST_DIRS is not None, mylar.CONFIG.MULTIPLE_DEST_DIRS != 'None', os.path.join(mylar.CONFIG.MULTIPLE_DEST_DIRS, os.path.basename(chk['ComicLocation'])) != chk['ComicLocation'], os.path.exists(os.path.join(mylar.CONFIG.MULTIPLE_DEST_DIRS, os.path.basename(chk['ComicLocation'])))]):
|
|
pathsrc = os.path.join(mylar.CONFIG.MULTIPLE_DEST_DIRS, os.path.basename(chk['ComicLocation']), chk['Location'])
|
|
else:
|
|
logger.fdebug(module + ' file does not exist in location: ' + pathdir + '. Cannot valid location - some options will not be available for this item.')
|
|
continue
|
|
except:
|
|
continue
|
|
|
|
# update_iss.append({'IssueID': chk['IssueID'],
|
|
# 'Location': pathdir})
|
|
arcinfo = None
|
|
for la in issues:
|
|
if la['IssueID'] == chk['IssueID']:
|
|
arcinfo = la
|
|
break
|
|
|
|
if arcinfo is None:
|
|
continue
|
|
|
|
if arcinfo['Publisher'] is None:
|
|
arcpub = arcinfo['IssuePublisher']
|
|
else:
|
|
arcpub = arcinfo['Publisher']
|
|
|
|
grdst = arcformat(arcinfo['StoryArc'], spantheyears(arcinfo['StoryArcID']), arcpub)
|
|
if grdst is not None:
|
|
logger.info('grdst:' + grdst)
|
|
#send to renamer here if valid.
|
|
dfilename = chk['Location']
|
|
if mylar.CONFIG.RENAME_FILES:
|
|
renamed_file = rename_param(arcinfo['ComicID'], arcinfo['ComicName'], arcinfo['IssueNumber'], chk['Location'], issueid=arcinfo['IssueID'], arc=arcinfo['StoryArc'])
|
|
if renamed_file:
|
|
dfilename = renamed_file['nfilename']
|
|
|
|
if mylar.CONFIG.READ2FILENAME:
|
|
#logger.fdebug('readingorder#: ' + str(arcinfo['ReadingOrder']))
|
|
#if int(arcinfo['ReadingOrder']) < 10: readord = "00" + str(arcinfo['ReadingOrder'])
|
|
#elif int(arcinfo['ReadingOrder']) >= 10 and int(arcinfo['ReadingOrder']) <= 99: readord = "0" + str(arcinfo['ReadingOrder'])
|
|
#else: readord = str(arcinfo['ReadingOrder'])
|
|
readord = renamefile_readingorder(arcinfo['ReadingOrder'])
|
|
dfilename = str(readord) + "-" + dfilename
|
|
|
|
pathdst = os.path.join(grdst, dfilename)
|
|
|
|
logger.fdebug('Destination Path : ' + pathdst)
|
|
logger.fdebug('Source Path : ' + pathsrc)
|
|
if not os.path.isdir(grdst):
|
|
logger.fdebug('[ARC-DIRECTORY] Arc directory doesn\'t exist. Creating: %s' % grdst)
|
|
mylar.filechecker.validateAndCreateDirectory(grdst, create=True)
|
|
|
|
if not os.path.isfile(pathdst):
|
|
logger.info('[' + mylar.CONFIG.ARC_FILEOPS.upper() + '] ' + pathsrc + ' into directory : ' + pathdst)
|
|
|
|
try:
|
|
#need to ensure that src is pointing to the series in order to do a soft/hard-link properly
|
|
fileoperation = file_ops(pathsrc, pathdst, arc=True)
|
|
if not fileoperation:
|
|
raise OSError
|
|
except (OSError, IOError):
|
|
logger.fdebug('[' + mylar.CONFIG.ARC_FILEOPS.upper() + '] Failure ' + pathsrc + ' - check directories and manually re-run.')
|
|
continue
|
|
updateloc = pathdst
|
|
else:
|
|
updateloc = pathsrc
|
|
|
|
update_iss.append({'IssueID': chk['IssueID'],
|
|
'Location': updateloc})
|
|
|
|
for ui in update_iss:
|
|
logger.info(ui['IssueID'] + ' to update location to: ' + ui['Location'])
|
|
myDB.upsert("storyarcs", {'Location': ui['Location']}, {'IssueID': ui['IssueID'], 'StoryArcID': storyarcid})
|
|
|
|
|
|
def spantheyears(storyarcid):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
|
|
totalcnt = myDB.select("SELECT * FROM storyarcs WHERE StoryArcID=?", [storyarcid])
|
|
lowyear = 9999
|
|
maxyear = 0
|
|
for la in totalcnt:
|
|
if la['IssueDate'] is None or la['IssueDate'] == '0000-00-00':
|
|
continue
|
|
else:
|
|
if int(la['IssueDate'][:4]) > maxyear:
|
|
maxyear = int(la['IssueDate'][:4])
|
|
if int(la['IssueDate'][:4]) < lowyear:
|
|
lowyear = int(la['IssueDate'][:4])
|
|
|
|
if maxyear == 0:
|
|
spanyears = la['SeriesYear']
|
|
elif lowyear == maxyear:
|
|
spanyears = str(maxyear)
|
|
else:
|
|
spanyears = str(lowyear) + ' - ' + str(maxyear) #la['SeriesYear'] + ' - ' + str(maxyear)
|
|
return spanyears
|
|
|
|
def arcformat(arc, spanyears, publisher):
|
|
arcdir = filesafe(arc)
|
|
if publisher is None:
|
|
publisher = 'None'
|
|
|
|
values = {'$arc': arcdir,
|
|
'$spanyears': spanyears,
|
|
'$publisher': publisher}
|
|
|
|
tmp_folderformat = mylar.CONFIG.ARC_FOLDERFORMAT
|
|
|
|
if publisher == 'None':
|
|
chunk_f_f = re.sub('\$publisher', '', tmp_folderformat)
|
|
chunk_f = re.compile(r'\s+')
|
|
tmp_folderformat = chunk_f.sub(' ', chunk_f_f)
|
|
|
|
|
|
if any([tmp_folderformat == '', tmp_folderformat is None]):
|
|
arcpath = arcdir
|
|
else:
|
|
arcpath = replace_all(tmp_folderformat, values)
|
|
|
|
if mylar.CONFIG.REPLACE_SPACES:
|
|
arcpath = arcpath.replace(' ', mylar.CONFIG.REPLACE_CHAR)
|
|
|
|
if arcpath.startswith('/'):
|
|
arcpath = arcpath[1:]
|
|
elif arcpath.startswith('//'):
|
|
arcpath = arcpath[2:]
|
|
|
|
if mylar.CONFIG.STORYARCDIR is True:
|
|
dstloc = os.path.join(mylar.CONFIG.DESTINATION_DIR, 'StoryArcs', arcpath)
|
|
elif mylar.CONFIG.COPY2ARCDIR is True:
|
|
logger.warn('Story arc directory is not configured. Defaulting to grabbag directory: ' + mylar.CONFIG.GRABBAG_DIR)
|
|
dstloc = os.path.join(mylar.CONFIG.GRABBAG_DIR, arcpath)
|
|
else:
|
|
dstloc = None
|
|
|
|
return dstloc
|
|
|
|
def torrentinfo(issueid=None, torrent_hash=None, download=False, monitor=False):
|
|
#import db
|
|
from base64 import b16encode, b32decode
|
|
|
|
#check the status of the issueid to make sure it's in Snatched status and was grabbed via torrent.
|
|
if issueid:
|
|
myDB = db.DBConnection()
|
|
cinfo = myDB.selectone('SELECT a.Issue_Number, a.ComicName, a.Status, b.Hash from issues as a inner join snatched as b ON a.IssueID=b.IssueID WHERE a.IssueID=?', [issueid]).fetchone()
|
|
if cinfo is None:
|
|
logger.warn('Unable to locate IssueID of : ' + issueid)
|
|
snatch_status = 'ERROR'
|
|
|
|
if cinfo['Status'] != 'Snatched' or cinfo['Hash'] is None:
|
|
logger.warn(cinfo['ComicName'] + ' #' + cinfo['Issue_Number'] + ' is currently in a ' + cinfo['Status'] + ' Status.')
|
|
snatch_status = 'ERROR'
|
|
|
|
torrent_hash = cinfo['Hash']
|
|
|
|
logger.fdebug("Working on torrent: " + torrent_hash)
|
|
if len(torrent_hash) == 32:
|
|
torrent_hash = b16encode(b32decode(torrent_hash))
|
|
|
|
if not len(torrent_hash) == 40:
|
|
logger.error("Torrent hash is missing, or an invalid hash value has been passed")
|
|
snatch_status = 'ERROR'
|
|
else:
|
|
if mylar.USE_RTORRENT:
|
|
import test
|
|
rp = test.RTorrent()
|
|
torrent_info = rp.main(torrent_hash, check=True)
|
|
elif mylar.USE_DELUGE:
|
|
#need to set the connect here as well....
|
|
import torrent.clients.deluge as delu
|
|
dp = delu.TorrentClient()
|
|
if not dp.connect(mylar.CONFIG.DELUGE_HOST, mylar.CONFIG.DELUGE_USERNAME, mylar.CONFIG.DELUGE_PASSWORD):
|
|
logger.warn('Not connected to Deluge!')
|
|
|
|
torrent_info = dp.get_torrent(torrent_hash)
|
|
else:
|
|
snatch_status = 'ERROR'
|
|
return
|
|
|
|
logger.info('torrent_info: %s' % torrent_info)
|
|
|
|
if torrent_info is False or len(torrent_info) == 0:
|
|
logger.warn('torrent returned no information. Check logs - aborting auto-snatch at this time.')
|
|
snatch_status = 'ERROR'
|
|
else:
|
|
if mylar.USE_DELUGE:
|
|
torrent_status = torrent_info['is_finished']
|
|
torrent_files = torrent_info['num_files']
|
|
torrent_folder = torrent_info['save_path']
|
|
torrent_info['total_filesize'] = torrent_info['total_size']
|
|
torrent_info['upload_total'] = torrent_info['total_uploaded']
|
|
torrent_info['download_total'] = torrent_info['total_payload_download']
|
|
torrent_info['time_started'] = torrent_info['time_added']
|
|
|
|
elif mylar.USE_RTORRENT:
|
|
torrent_status = torrent_info['completed']
|
|
torrent_files = len(torrent_info['files'])
|
|
torrent_folder = torrent_info['folder']
|
|
|
|
if all([torrent_status is True, download is True]):
|
|
if not issueid:
|
|
torrent_info['snatch_status'] = 'STARTING...'
|
|
#yield torrent_info
|
|
|
|
import shlex, subprocess
|
|
logger.info('Torrent is completed and status is currently Snatched. Attempting to auto-retrieve.')
|
|
with open(mylar.CONFIG.AUTO_SNATCH_SCRIPT, 'r') as f:
|
|
first_line = f.readline()
|
|
|
|
if mylar.CONFIG.AUTO_SNATCH_SCRIPT.endswith('.sh'):
|
|
shell_cmd = re.sub('#!', '', first_line)
|
|
if shell_cmd == '' or shell_cmd is None:
|
|
shell_cmd = '/bin/bash'
|
|
else:
|
|
shell_cmd = sys.executable
|
|
|
|
curScriptName = shell_cmd + ' ' + str(mylar.CONFIG.AUTO_SNATCH_SCRIPT).decode("string_escape")
|
|
if torrent_files > 1:
|
|
downlocation = torrent_folder.encode('utf-8')
|
|
else:
|
|
if mylar.USE_DELUGE:
|
|
downlocation = os.path.join(torrent_folder.encode('utf-8'), torrent_info['files'][0]['path'])
|
|
else:
|
|
downlocation = torrent_info['files'][0].encode('utf-8')
|
|
|
|
autosnatch_env = os.environ.copy()
|
|
autosnatch_env['downlocation'] = re.sub("'", "\\'",downlocation)
|
|
|
|
#these are pulled from the config and are the ssh values to use to retrieve the data
|
|
autosnatch_env['host'] = mylar.CONFIG.PP_SSHHOST
|
|
autosnatch_env['port'] = mylar.CONFIG.PP_SSHPORT
|
|
autosnatch_env['user'] = mylar.CONFIG.PP_SSHUSER
|
|
autosnatch_env['localcd'] = mylar.CONFIG.PP_SSHLOCALCD
|
|
#bash won't accept None, so send check and send empty strings for the 2 possible None values if needed
|
|
if mylar.CONFIG.PP_SSHKEYFILE is not None:
|
|
autosnatch_env['keyfile'] = mylar.CONFIG.PP_SSHKEYFILE
|
|
else:
|
|
autosnatch_env['keyfile'] = ''
|
|
if mylar.CONFIG.PP_SSHPASSWD is not None:
|
|
autosnatch_env['passwd'] = mylar.CONFIG.PP_SSHPASSWD
|
|
else:
|
|
autosnatch_env['passwd'] = ''
|
|
|
|
|
|
#downlocation = re.sub("\'", "\\'", downlocation)
|
|
#downlocation = re.sub("&", "\&", downlocation)
|
|
|
|
script_cmd = shlex.split(curScriptName, posix=False) # + [downlocation]
|
|
logger.fdebug(u"Executing command " +str(script_cmd))
|
|
try:
|
|
p = subprocess.Popen(script_cmd, env=dict(autosnatch_env), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=mylar.PROG_DIR)
|
|
out, err = p.communicate()
|
|
logger.fdebug(u"Script result: " + out)
|
|
except OSError, e:
|
|
logger.warn(u"Unable to run extra_script: " + e)
|
|
snatch_status = 'ERROR'
|
|
else:
|
|
if 'Access failed: No such file' in out:
|
|
logger.fdebug('Not located in location it is supposed to be in - probably has been moved by some script and I got the wrong location due to timing. Trying again...')
|
|
snatch_status = 'IN PROGRESS'
|
|
else:
|
|
snatch_status = 'COMPLETED'
|
|
torrent_info['completed'] = torrent_status
|
|
torrent_info['files'] = torrent_files
|
|
torrent_info['folder'] = torrent_folder
|
|
|
|
else:
|
|
if download is True:
|
|
snatch_status = 'IN PROGRESS'
|
|
elif monitor is True:
|
|
#pause the torrent, copy it to the cache folder, unpause the torrent and return the complete path to the cache location
|
|
if mylar.USE_DELUGE:
|
|
pauseit = dp.stop_torrent(torrent_hash)
|
|
if pauseit is False:
|
|
logger.warn('Unable to pause torrent - cannot run post-process on item at this time.')
|
|
snatch_status = 'MONITOR FAIL'
|
|
else:
|
|
try:
|
|
new_filepath = os.path.join(torrent_path, '.copy')
|
|
logger.fdebug('New_Filepath: %s' % new_filepath)
|
|
shutil.copy(torrent_path, new_filepath)
|
|
torrent_info['copied_filepath'] = new_filepath
|
|
except:
|
|
logger.warn('Unexpected Error: %s' % sys.exc_info()[0])
|
|
logger.warn('Unable to create temporary directory to perform meta-tagging. Processing cannot continue with given item at this time.')
|
|
torrent_info['copied_filepath'] = torrent_path
|
|
SNATCH_STATUS = 'MONITOR FAIL'
|
|
else:
|
|
startit = dp.start_torrent(torrent_hash)
|
|
SNATCH_STATUS = 'MONITOR COMPLETE'
|
|
else:
|
|
snatch_status = 'NOT SNATCHED'
|
|
|
|
torrent_info['snatch_status'] = snatch_status
|
|
return torrent_info
|
|
|
|
def weekly_info(week=None, year=None, current=None):
|
|
#find the current week and save it as a reference point.
|
|
todaydate = datetime.datetime.today()
|
|
current_weeknumber = todaydate.strftime("%U")
|
|
if current is not None:
|
|
c_weeknumber = int(current[:current.find('-')])
|
|
c_weekyear = int(current[current.find('-')+1:])
|
|
else:
|
|
c_weeknumber = week
|
|
c_weekyear = year
|
|
|
|
if week:
|
|
weeknumber = int(week)
|
|
year = int(year)
|
|
|
|
#monkey patch for 2018/2019 - week 52/week 0
|
|
if all([weeknumber == 52, c_weeknumber == 51, c_weekyear == 2018]):
|
|
weeknumber = 0
|
|
year = 2019
|
|
elif all([weeknumber == 52, c_weeknumber == 0, c_weekyear == 2019]):
|
|
weeknumber = 51
|
|
year = 2018
|
|
|
|
#monkey patch for 2019/2020 - week 52/week 0
|
|
if all([weeknumber == 52, c_weeknumber == 51, c_weekyear == 2019]):
|
|
weeknumber = 0
|
|
year = 2020
|
|
elif all([weeknumber == 52, c_weeknumber == 0, c_weekyear == 2020]):
|
|
weeknumber = 51
|
|
year = 2019
|
|
|
|
#view specific week (prev_week, next_week)
|
|
startofyear = date(year,1,1)
|
|
week0 = startofyear - timedelta(days=startofyear.isoweekday())
|
|
stweek = datetime.datetime.strptime(week0.strftime('%Y-%m-%d'), '%Y-%m-%d')
|
|
startweek = stweek + timedelta(weeks = weeknumber)
|
|
midweek = startweek + timedelta(days = 3)
|
|
endweek = startweek + timedelta(days = 6)
|
|
else:
|
|
#find the given week number for the current day
|
|
weeknumber = current_weeknumber
|
|
year = todaydate.strftime("%Y")
|
|
|
|
#monkey patch for 2018/2019 - week 52/week 0
|
|
if all([weeknumber == 52, c_weeknumber == 51, c_weekyear == 2018]):
|
|
weeknumber = 0
|
|
year = 2019
|
|
elif all([weeknumber == 52, c_weeknumber == 0, c_weekyear == 2019]):
|
|
weeknumber = 51
|
|
year = 2018
|
|
|
|
#monkey patch for 2019/2020 - week 52/week 0
|
|
if all([weeknumber == 52, c_weeknumber == 51, c_weekyear == 2019]) or all([weeknumber == '52', year == '2019']):
|
|
weeknumber = 0
|
|
year = 2020
|
|
elif all([weeknumber == 52, c_weeknumber == 0, c_weekyear == 2020]):
|
|
weeknumber = 51
|
|
year = 2019
|
|
|
|
stweek = datetime.datetime.strptime(todaydate.strftime('%Y-%m-%d'), '%Y-%m-%d')
|
|
startweek = stweek - timedelta(days = (stweek.weekday() + 1) % 7)
|
|
midweek = startweek + timedelta(days = 3)
|
|
endweek = startweek + timedelta(days = 6)
|
|
|
|
prev_week = int(weeknumber) - 1
|
|
prev_year = year
|
|
if prev_week < 0:
|
|
prev_week = 52
|
|
prev_year = int(year) - 1
|
|
|
|
next_week = int(weeknumber) + 1
|
|
next_year = year
|
|
if next_week > 52:
|
|
next_year = int(year) + 1
|
|
next_week = datetime.date(int(next_year),1,1).strftime("%U")
|
|
|
|
date_fmt = "%B %d, %Y"
|
|
try:
|
|
con_startweek = u"" + startweek.strftime(date_fmt).decode('utf-8')
|
|
con_endweek = u"" + endweek.strftime(date_fmt).decode('utf-8')
|
|
except:
|
|
con_startweek = u"" + startweek.strftime(date_fmt).decode('cp1252')
|
|
con_endweek = u"" + endweek.strftime(date_fmt).decode('cp1252')
|
|
|
|
if mylar.CONFIG.WEEKFOLDER_LOC is not None:
|
|
weekdst = mylar.CONFIG.WEEKFOLDER_LOC
|
|
else:
|
|
weekdst = mylar.CONFIG.DESTINATION_DIR
|
|
|
|
if mylar.SCHED_WEEKLY_LAST is not None:
|
|
weekly_stamp = datetime.datetime.fromtimestamp(mylar.SCHED_WEEKLY_LAST)
|
|
weekly_last = weekly_stamp.replace(microsecond=0)
|
|
else:
|
|
weekly_last = 'None'
|
|
|
|
weekinfo = {'weeknumber': weeknumber,
|
|
'startweek': con_startweek,
|
|
'midweek': midweek.strftime('%Y-%m-%d'),
|
|
'endweek': con_endweek,
|
|
'year': year,
|
|
'prev_weeknumber': prev_week,
|
|
'prev_year': prev_year,
|
|
'next_weeknumber': next_week,
|
|
'next_year': next_year,
|
|
'current_weeknumber': current_weeknumber,
|
|
'last_update': weekly_last}
|
|
|
|
if weekdst is not None:
|
|
if mylar.CONFIG.WEEKFOLDER_FORMAT == 0:
|
|
weekn = weeknumber
|
|
if len(str(weekn)) == 1:
|
|
weekn = '%s%s' % ('0', str(weekn))
|
|
weekfold = os.path.join(weekdst, '%s-%s' % (weekinfo['year'], weekn))
|
|
else:
|
|
weekfold = os.path.join(weekdst, str( str(weekinfo['midweek']) ))
|
|
else:
|
|
weekfold = None
|
|
|
|
weekinfo['week_folder'] = weekfold
|
|
|
|
return weekinfo
|
|
|
|
def latestdate_update():
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
ccheck = myDB.select('SELECT a.ComicID, b.IssueID, a.LatestDate, b.ReleaseDate, b.Issue_Number from comics as a left join issues as b on a.comicid=b.comicid where a.LatestDate < b.ReleaseDate or a.LatestDate like "%Unknown%" group by a.ComicID')
|
|
if ccheck is None or len(ccheck) == 0:
|
|
return
|
|
logger.info('Now preparing to update ' + str(len(ccheck)) + ' series that have out-of-date latest date information.')
|
|
ablist = []
|
|
for cc in ccheck:
|
|
ablist.append({'ComicID': cc['ComicID'],
|
|
'LatestDate': cc['ReleaseDate'],
|
|
'LatestIssue': cc['Issue_Number']})
|
|
|
|
#forcibly set the latest date and issue number to the most recent.
|
|
for a in ablist:
|
|
logger.info(a)
|
|
newVal = {'LatestDate': a['LatestDate'],
|
|
'LatestIssue': a['LatestIssue']}
|
|
ctrlVal = {'ComicID': a['ComicID']}
|
|
logger.info('updating latest date for : ' + a['ComicID'] + ' to ' + a['LatestDate'] + ' #' + a['LatestIssue'])
|
|
myDB.upsert("comics", newVal, ctrlVal)
|
|
|
|
def ddl_downloader(queue):
|
|
myDB = db.DBConnection()
|
|
while True:
|
|
if mylar.DDL_LOCK is True:
|
|
time.sleep(5)
|
|
|
|
elif mylar.DDL_LOCK is False and queue.qsize() >= 1:
|
|
item = queue.get(True)
|
|
if item == 'exit':
|
|
logger.info('Cleaning up workers for shutdown')
|
|
break
|
|
logger.info('Now loading request from DDL queue: %s' % item['series'])
|
|
|
|
#write this to the table so we have a record of what's going on.
|
|
ctrlval = {'id': item['id']}
|
|
val = {'status': 'Downloading',
|
|
'updated_date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}
|
|
myDB.upsert('ddl_info', val, ctrlval)
|
|
|
|
ddz = getcomics.GC()
|
|
ddzstat = ddz.downloadit(item['id'], item['link'], item['mainlink'], item['resume'])
|
|
|
|
if ddzstat['success'] is True:
|
|
tdnow = datetime.datetime.now()
|
|
nval = {'status': 'Completed',
|
|
'updated_date': tdnow.strftime('%Y-%m-%d %H:%M')}
|
|
myDB.upsert('ddl_info', nval, ctrlval)
|
|
|
|
if all([ddzstat['success'] is True, mylar.CONFIG.POST_PROCESSING is True]):
|
|
try:
|
|
if ddzstat['filename'] is None:
|
|
logger.info('%s successfully downloaded - now initiating post-processing.' % (os.path.basename(ddzstat['path'])))
|
|
mylar.PP_QUEUE.put({'nzb_name': os.path.basename(ddzstat['path']),
|
|
'nzb_folder': ddzstat['path'],
|
|
'failed': False,
|
|
'issueid': None,
|
|
'comicid': item['comicid'],
|
|
'apicall': True,
|
|
'ddl': True})
|
|
else:
|
|
logger.info('%s successfully downloaded - now initiating post-processing.' % (ddzstat['filename']))
|
|
mylar.PP_QUEUE.put({'nzb_name': ddzstat['filename'],
|
|
'nzb_folder': ddzstat['path'],
|
|
'failed': False,
|
|
'issueid': item['issueid'],
|
|
'comicid': item['comicid'],
|
|
'apicall': True,
|
|
'ddl': True})
|
|
except Exception as e:
|
|
logger.info('process error: %s [%s]' %(e, ddzstat))
|
|
elif all([ddzstat['success'] is True, mylar.CONFIG.POST_PROCESSING is False]):
|
|
logger.info('File successfully downloaded. Post Processing is not enabled - item retained here: %s' % os.path.join(ddzstat['path'],ddzstat['filename']))
|
|
else:
|
|
logger.info('[Status: %s] Failed to download: %s ' % (ddzstat['success'], ddzstat))
|
|
nval = {'status': 'Failed',
|
|
'updated_date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}
|
|
myDB.upsert('ddl_info', nval, ctrlval)
|
|
else:
|
|
time.sleep(5)
|
|
|
|
def postprocess_main(queue):
|
|
while True:
|
|
if mylar.APILOCK is True:
|
|
time.sleep(5)
|
|
|
|
elif mylar.APILOCK is False and queue.qsize() >= 1: #len(queue) > 1:
|
|
pp = None
|
|
item = queue.get(True)
|
|
logger.info('Now loading from post-processing queue: %s' % item)
|
|
if item == 'exit':
|
|
logger.info('Cleaning up workers for shutdown')
|
|
break
|
|
|
|
if mylar.APILOCK is False:
|
|
try:
|
|
pprocess = process.Process(item['nzb_name'], item['nzb_folder'], item['failed'], item['issueid'], item['comicid'], item['apicall'], item['ddl'])
|
|
except:
|
|
pprocess = process.Process(item['nzb_name'], item['nzb_folder'], item['failed'], item['issueid'], item['comicid'], item['apicall'])
|
|
pp = pprocess.post_process()
|
|
time.sleep(5) #arbitrary sleep to let the process attempt to finish pp'ing
|
|
|
|
if pp is not None:
|
|
if pp['mode'] == 'stop':
|
|
#reset the lock so any subsequent items can pp and not keep the queue locked up.
|
|
mylar.APILOCK = False
|
|
|
|
if mylar.APILOCK is True:
|
|
logger.info('Another item is post-processing still...')
|
|
time.sleep(15)
|
|
#mylar.PP_QUEUE.put(item)
|
|
else:
|
|
time.sleep(5)
|
|
|
|
def search_queue(queue):
|
|
while True:
|
|
if mylar.SEARCHLOCK is True:
|
|
time.sleep(5)
|
|
|
|
elif mylar.SEARCHLOCK is False and queue.qsize() >= 1: #len(queue) > 1:
|
|
item = queue.get(True)
|
|
if item == 'exit':
|
|
logger.info('[SEARCH-QUEUE] Cleaning up workers for shutdown')
|
|
break
|
|
|
|
logger.info('[SEARCH-QUEUE] Now loading item from search queue: %s' % item)
|
|
if mylar.SEARCHLOCK is False:
|
|
ss_queue = mylar.search.searchforissue(item['issueid'])
|
|
time.sleep(5) #arbitrary sleep to let the process attempt to finish pp'ing
|
|
|
|
if mylar.SEARCHLOCK is True:
|
|
logger.fdebug('[SEARCH-QUEUE] Another item is currently being searched....')
|
|
time.sleep(15)
|
|
else:
|
|
time.sleep(5)
|
|
|
|
|
|
def worker_main(queue):
|
|
while True:
|
|
if queue.qsize() >= 1:
|
|
item = queue.get(True)
|
|
logger.info('Now loading from queue: ' + item)
|
|
if item == 'exit':
|
|
logger.info('Cleaning up workers for shutdown')
|
|
break
|
|
snstat = torrentinfo(torrent_hash=item, download=True)
|
|
if snstat['snatch_status'] == 'IN PROGRESS':
|
|
logger.info('Still downloading in client....let us try again momentarily.')
|
|
time.sleep(30)
|
|
mylar.SNATCHED_QUEUE.put(item)
|
|
elif any([snstat['snatch_status'] == 'MONITOR FAIL', snstat['snatch_status'] == 'MONITOR COMPLETE']):
|
|
logger.info('File copied for post-processing - submitting as a direct pp.')
|
|
threading.Thread(target=self.checkFolder, args=[os.path.abspath(os.path.join(snstat['copied_filepath'], os.pardir))]).start()
|
|
else:
|
|
time.sleep(15)
|
|
|
|
def nzb_monitor(queue):
|
|
while True:
|
|
if queue.qsize() >= 1:
|
|
item = queue.get(True)
|
|
if item == 'exit':
|
|
logger.info('Cleaning up workers for shutdown')
|
|
break
|
|
logger.info('Now loading from queue: %s' % item)
|
|
if all([mylar.USE_SABNZBD is True, mylar.CONFIG.SAB_CLIENT_POST_PROCESSING is True]):
|
|
nz = sabnzbd.SABnzbd(item)
|
|
nzstat = nz.processor()
|
|
elif all([mylar.USE_NZBGET is True, mylar.CONFIG.NZBGET_CLIENT_POST_PROCESSING is True]):
|
|
nz = nzbget.NZBGet()
|
|
nzstat = nz.processor(item)
|
|
else:
|
|
logger.warn('There are no NZB Completed Download handlers enabled. Not sending item to completed download handling...')
|
|
break
|
|
|
|
if any([nzstat['status'] == 'file not found', nzstat['status'] == 'double-pp']):
|
|
logger.warn('Unable to complete post-processing call due to not finding file in the location provided. [%s]' % item)
|
|
elif nzstat['status'] is False:
|
|
logger.info('Could not find NZBID %s in the downloader\'s queue. I will requeue this item for post-processing...' % item['NZBID'])
|
|
time.sleep(5)
|
|
mylar.NZB_QUEUE.put(item)
|
|
elif nzstat['status'] is True:
|
|
if nzstat['failed'] is False:
|
|
logger.info('File successfully downloaded - now initiating completed downloading handling.')
|
|
else:
|
|
logger.info('File failed either due to being corrupt or incomplete - now initiating completed failed downloading handling.')
|
|
try:
|
|
mylar.PP_QUEUE.put({'nzb_name': nzstat['name'],
|
|
'nzb_folder': nzstat['location'],
|
|
'failed': nzstat['failed'],
|
|
'issueid': nzstat['issueid'],
|
|
'comicid': nzstat['comicid'],
|
|
'apicall': nzstat['apicall'],
|
|
'ddl': False})
|
|
#cc = process.Process(nzstat['name'], nzstat['location'], failed=nzstat['failed'])
|
|
#nzpp = cc.post_process()
|
|
except Exception as e:
|
|
logger.info('process error: %s' % e)
|
|
else:
|
|
time.sleep(5)
|
|
|
|
def script_env(mode, vars):
|
|
#mode = on-snatch, pre-postprocess, post-postprocess
|
|
#var = dictionary containing variables to pass
|
|
mylar_env = os.environ.copy()
|
|
if mode == 'on-snatch':
|
|
runscript = mylar.CONFIG.SNATCH_SCRIPT
|
|
if 'torrentinfo' in vars:
|
|
if 'hash' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_hash'] = vars['torrentinfo']['hash']
|
|
if 'torrent_filename' in vars['torrentinfo']:
|
|
mylar_env['mylar_torrent_filename'] = vars['torrentinfo']['torrent_filename']
|
|
if 'name' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_name'] = vars['torrentinfo']['name']
|
|
if 'folder' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_folder'] = vars['torrentinfo']['folder']
|
|
if 'label' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_label'] = vars['torrentinfo']['label']
|
|
if 'total_filesize' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_filesize'] = str(vars['torrentinfo']['total_filesize'])
|
|
if 'time_started' in vars['torrentinfo']:
|
|
mylar_env['mylar_release_start'] = str(vars['torrentinfo']['time_started'])
|
|
if 'filepath' in vars['torrentinfo']:
|
|
mylar_env['mylar_torrent_file'] = str(vars['torrentinfo']['filepath'])
|
|
else:
|
|
try:
|
|
mylar_env['mylar_release_files'] = '|'.join(vars['torrentinfo']['files'])
|
|
except TypeError:
|
|
mylar_env['mylar_release_files'] = '|'.join(json.dumps(vars['torrentinfo']['files']))
|
|
elif 'nzbinfo' in vars:
|
|
mylar_env['mylar_release_id'] = vars['nzbinfo']['id']
|
|
if 'client_id' in vars['nzbinfo']:
|
|
mylar_env['mylar_client_id'] = vars['nzbinfo']['client_id']
|
|
mylar_env['mylar_release_nzbname'] = vars['nzbinfo']['nzbname']
|
|
mylar_env['mylar_release_link'] = vars['nzbinfo']['link']
|
|
mylar_env['mylar_release_nzbpath'] = vars['nzbinfo']['nzbpath']
|
|
if 'blackhole' in vars['nzbinfo']:
|
|
mylar_env['mylar_release_blackhole'] = vars['nzbinfo']['blackhole']
|
|
mylar_env['mylar_release_provider'] = vars['provider']
|
|
if 'comicinfo' in vars:
|
|
try:
|
|
if vars['comicinfo']['comicid'] is not None:
|
|
mylar_env['mylar_comicid'] = vars['comicinfo']['comicid'] #comicid/issueid are unknown for one-offs (should be fixable tho)
|
|
else:
|
|
mylar_env['mylar_comicid'] = 'None'
|
|
except:
|
|
pass
|
|
try:
|
|
if vars['comicinfo']['issueid'] is not None:
|
|
mylar_env['mylar_issueid'] = vars['comicinfo']['issueid']
|
|
else:
|
|
mylar_env['mylar_issueid'] = 'None'
|
|
except:
|
|
pass
|
|
try:
|
|
if vars['comicinfo']['issuearcid'] is not None:
|
|
mylar_env['mylar_issuearcid'] = vars['comicinfo']['issuearcid']
|
|
else:
|
|
mylar_env['mylar_issuearcid'] = 'None'
|
|
except:
|
|
pass
|
|
mylar_env['mylar_comicname'] = vars['comicinfo']['comicname']
|
|
mylar_env['mylar_issuenumber'] = str(vars['comicinfo']['issuenumber'])
|
|
try:
|
|
mylar_env['mylar_comicvolume'] = str(vars['comicinfo']['volume'])
|
|
except:
|
|
pass
|
|
try:
|
|
mylar_env['mylar_seriesyear'] = str(vars['comicinfo']['seriesyear'])
|
|
except:
|
|
pass
|
|
try:
|
|
mylar_env['mylar_issuedate'] = str(vars['comicinfo']['issuedate'])
|
|
except:
|
|
pass
|
|
|
|
mylar_env['mylar_release_pack'] = str(vars['pack'])
|
|
if vars['pack'] is True:
|
|
if vars['pack_numbers'] is not None:
|
|
mylar_env['mylar_release_pack_numbers'] = vars['pack_numbers']
|
|
if vars['pack_issuelist'] is not None:
|
|
mylar_env['mylar_release_pack_issuelist'] = vars['pack_issuelist']
|
|
mylar_env['mylar_method'] = vars['method']
|
|
mylar_env['mylar_client'] = vars['clientmode']
|
|
|
|
elif mode == 'post-process':
|
|
#to-do
|
|
runscript = mylar.CONFIG.EXTRA_SCRIPTS
|
|
elif mode == 'pre-process':
|
|
#to-do
|
|
runscript = mylar.CONFIG.PRE_SCRIPTS
|
|
|
|
logger.fdebug('Initiating ' + mode + ' script detection.')
|
|
with open(runscript, 'r') as f:
|
|
first_line = f.readline()
|
|
|
|
if runscript.endswith('.sh'):
|
|
shell_cmd = re.sub('#!', '', first_line)
|
|
if shell_cmd == '' or shell_cmd is None:
|
|
shell_cmd = '/bin/bash'
|
|
else:
|
|
shell_cmd = sys.executable
|
|
|
|
curScriptName = shell_cmd + ' ' + runscript.decode("string_escape")
|
|
logger.fdebug("snatch script detected...enabling: " + str(curScriptName))
|
|
|
|
script_cmd = shlex.split(curScriptName)
|
|
logger.fdebug(u"Executing command " +str(script_cmd))
|
|
try:
|
|
subprocess.call(script_cmd, env=dict(mylar_env))
|
|
except OSError, e:
|
|
logger.warn(u"Unable to run extra_script: " + str(script_cmd))
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def get_the_hash(filepath):
|
|
import bencode
|
|
# Open torrent file
|
|
torrent_file = open(filepath, "rb")
|
|
metainfo = bencode.decode(torrent_file.read())
|
|
info = metainfo['info']
|
|
thehash = hashlib.sha1(bencode.encode(info)).hexdigest().upper()
|
|
logger.info('Hash of file : ' + thehash)
|
|
return {'hash': thehash}
|
|
|
|
def disable_provider(site, newznab=False):
|
|
logger.info('Temporarily disabling %s due to not responding' % site)
|
|
if newznab is True:
|
|
tmplist = []
|
|
for ti in mylar.CONFIG.EXTRA_NEWZNABS:
|
|
tmpnewz = list(ti)
|
|
if tmpnewz[0] == site:
|
|
tmpnewz[5] = '0'
|
|
tmplist.append(tuple(tmpnewz))
|
|
mylar.CONFIG.EXTRA_NEWZNABS = tmplist
|
|
else:
|
|
if site == 'nzbsu':
|
|
mylar.CONFIG.NZBSU = False
|
|
elif site == 'dognzb':
|
|
mylar.CONFIG.DOGNZB = False
|
|
elif site == 'experimental':
|
|
mylar.CONFIG.EXPERIMENTAL = False
|
|
elif site == '32P':
|
|
mylar.CONFIG.ENABLE_32P = False
|
|
|
|
def date_conversion(originaldate):
|
|
c_obj_date = datetime.datetime.strptime(originaldate, "%Y-%m-%d %H:%M:%S")
|
|
n_date = datetime.datetime.now()
|
|
absdiff = abs(n_date - c_obj_date)
|
|
hours = (absdiff.days * 24 * 60 * 60 + absdiff.seconds) / 3600.0
|
|
return hours
|
|
|
|
def job_management(write=False, job=None, last_run_completed=None, current_run=None, status=None):
|
|
jobresults = []
|
|
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
|
|
if job is None:
|
|
dbupdate_newstatus = 'Waiting'
|
|
dbupdate_nextrun = None
|
|
if mylar.CONFIG.ENABLE_RSS is True:
|
|
rss_newstatus = 'Waiting'
|
|
else:
|
|
rss_newstatus = 'Paused'
|
|
rss_nextrun = None
|
|
weekly_newstatus = 'Waiting'
|
|
weekly_nextrun = None
|
|
search_newstatus = 'Waiting'
|
|
search_nextrun = None
|
|
version_newstatus = 'Waiting'
|
|
version_nextrun = None
|
|
if mylar.CONFIG.ENABLE_CHECK_FOLDER is True:
|
|
monitor_newstatus = 'Waiting'
|
|
else:
|
|
monitor_newstatus = 'Paused'
|
|
monitor_nextrun = None
|
|
|
|
job_info = myDB.select('SELECT DISTINCT * FROM jobhistory')
|
|
#set default values if nothing has been ran yet
|
|
for ji in job_info:
|
|
if 'update' in ji['JobName'].lower():
|
|
if mylar.SCHED_DBUPDATE_LAST is None:
|
|
mylar.SCHED_DBUPDATE_LAST = ji['prev_run_timestamp']
|
|
dbupdate_newstatus = ji['status']
|
|
mylar.UPDATER_STATUS = dbupdate_newstatus
|
|
dbupdate_nextrun = ji['next_run_timestamp']
|
|
elif 'search' in ji['JobName'].lower():
|
|
if mylar.SCHED_SEARCH_LAST is None:
|
|
mylar.SCHED_SEARCH_LAST = ji['prev_run_timestamp']
|
|
search_newstatus = ji['status']
|
|
mylar.SEARCH_STATUS = search_newstatus
|
|
search_nextrun = ji['next_run_timestamp']
|
|
elif 'rss' in ji['JobName'].lower():
|
|
if mylar.SCHED_RSS_LAST is None:
|
|
mylar.SCHED_RSS_LAST = ji['prev_run_timestamp']
|
|
rss_newstatus = ji['status']
|
|
mylar.RSS_STATUS = rss_newstatus
|
|
rss_nextrun = ji['next_run_timestamp']
|
|
elif 'weekly' in ji['JobName'].lower():
|
|
if mylar.SCHED_WEEKLY_LAST is None:
|
|
mylar.SCHED_WEEKLY_LAST = ji['prev_run_timestamp']
|
|
weekly_newstatus = ji['status']
|
|
mylar.WEEKLY_STATUS = weekly_newstatus
|
|
weekly_nextrun = ji['next_run_timestamp']
|
|
elif 'version' in ji['JobName'].lower():
|
|
if mylar.SCHED_VERSION_LAST is None:
|
|
mylar.SCHED_VERSION_LAST = ji['prev_run_timestamp']
|
|
version_newstatus = ji['status']
|
|
mylar.VERSION_STATUS = version_newstatus
|
|
version_nextrun = ji['next_run_timestamp']
|
|
elif 'monitor' in ji['JobName'].lower():
|
|
if mylar.SCHED_MONITOR_LAST is None:
|
|
mylar.SCHED_MONITOR_LAST = ji['prev_run_timestamp']
|
|
monitor_newstatus = ji['status']
|
|
mylar.MONITOR_STATUS = monitor_newstatus
|
|
monitor_nextrun = ji['next_run_timestamp']
|
|
|
|
monitors = {'weekly': mylar.SCHED_WEEKLY_LAST,
|
|
'monitor': mylar.SCHED_MONITOR_LAST,
|
|
'search': mylar.SCHED_SEARCH_LAST,
|
|
'dbupdater': mylar.SCHED_DBUPDATE_LAST,
|
|
'version': mylar.SCHED_VERSION_LAST,
|
|
'rss': mylar.SCHED_RSS_LAST}
|
|
|
|
#this is for initial startup
|
|
for jb in mylar.SCHED.get_jobs():
|
|
#logger.fdebug('jb: %s' % jb)
|
|
jobinfo = str(jb)
|
|
if 'Status Updater' in jobinfo.lower():
|
|
continue
|
|
elif 'update' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_DBUPDATE_LAST
|
|
newstatus = dbupdate_newstatus
|
|
mylar.UPDATER_STATUS = newstatus
|
|
elif 'search' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_SEARCH_LAST
|
|
newstatus = search_newstatus
|
|
mylar.SEARCH_STATUS = newstatus
|
|
elif 'rss' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_RSS_LAST
|
|
newstatus = rss_newstatus
|
|
mylar.RSS_STATUS = newstatus
|
|
elif 'weekly' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_WEEKLY_LAST
|
|
newstatus = weekly_newstatus
|
|
mylar.WEEKLY_STATUS = newstatus
|
|
elif 'version' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_VERSION_LAST
|
|
newstatus = version_newstatus
|
|
mylar.VERSION_STATUS = newstatus
|
|
elif 'monitor' in jobinfo.lower():
|
|
prev_run_timestamp = mylar.SCHED_MONITOR_LAST
|
|
newstatus = monitor_newstatus
|
|
mylar.MONITOR_STATUS = newstatus
|
|
|
|
jobname = jobinfo[:jobinfo.find('(')-1].strip()
|
|
#logger.fdebug('jobinfo: %s' % jobinfo)
|
|
try:
|
|
jobtimetmp = jobinfo.split('at: ')[1].split('.')[0].strip()
|
|
except:
|
|
continue
|
|
#logger.fdebug('jobtimetmp: %s' % jobtimetmp)
|
|
jobtime = float(calendar.timegm(datetime.datetime.strptime(jobtimetmp[:-1], '%Y-%m-%d %H:%M:%S %Z').timetuple()))
|
|
#logger.fdebug('jobtime: %s' % jobtime)
|
|
|
|
if prev_run_timestamp is not None:
|
|
prev_run_time_utc = datetime.datetime.utcfromtimestamp(float(prev_run_timestamp))
|
|
prev_run_time_utc = prev_run_time_utc.replace(microsecond=0)
|
|
else:
|
|
prev_run_time_utc = None
|
|
#logger.fdebug('prev_run_time: %s' % prev_run_timestamp)
|
|
#logger.fdebug('prev_run_time type: %s' % type(prev_run_timestamp))
|
|
jobresults.append({'jobname': jobname,
|
|
'next_run_datetime': datetime.datetime.utcfromtimestamp(jobtime),
|
|
'prev_run_datetime': prev_run_time_utc,
|
|
'next_run_timestamp': jobtime,
|
|
'prev_run_timestamp': prev_run_timestamp,
|
|
'status': newstatus})
|
|
|
|
if not write:
|
|
if len(jobresults) == 0:
|
|
return monitors
|
|
else:
|
|
return jobresults
|
|
else:
|
|
if job is None:
|
|
for x in jobresults:
|
|
updateCtrl = {'JobName': x['jobname']}
|
|
updateVals = {'next_run_timestamp': x['next_run_timestamp'],
|
|
'prev_run_timestamp': x['prev_run_timestamp'],
|
|
'next_run_datetime': x['next_run_datetime'],
|
|
'prev_run_datetime': x['prev_run_datetime'],
|
|
'status': x['status']}
|
|
|
|
myDB.upsert('jobhistory', updateVals, updateCtrl)
|
|
else:
|
|
#logger.fdebug('Updating info - job: %s' % job)
|
|
#logger.fdebug('Updating info - last run: %s' % last_run_completed)
|
|
#logger.fdebug('Updating info - status: %s' % status)
|
|
updateCtrl = {'JobName': job}
|
|
if current_run is not None:
|
|
pr_datetime = datetime.datetime.utcfromtimestamp(current_run)
|
|
pr_datetime = pr_datetime.replace(microsecond=0)
|
|
updateVals = {'prev_run_timestamp': current_run,
|
|
'prev_run_datetime': pr_datetime,
|
|
'status': status}
|
|
#logger.info('updateVals: %s' % updateVals)
|
|
elif last_run_completed is not None:
|
|
if any([job == 'DB Updater', job == 'Auto-Search', job == 'RSS Feeds', job == 'Weekly Pullist', job == 'Check Version', job == 'Folder Monitor']):
|
|
jobstore = None
|
|
for jbst in mylar.SCHED.get_jobs():
|
|
jb = str(jbst)
|
|
if 'Status Updater' in jb.lower():
|
|
continue
|
|
elif job == 'DB Updater' and 'update' in jb.lower():
|
|
nextrun_stamp = utctimestamp() + (int(mylar.DBUPDATE_INTERVAL) * 60)
|
|
jobstore = jbst
|
|
break
|
|
elif job == 'Auto-Search' and 'search' in jb.lower():
|
|
nextrun_stamp = utctimestamp() + (mylar.CONFIG.SEARCH_INTERVAL * 60)
|
|
jobstore = jbst
|
|
break
|
|
elif job == 'RSS Feeds' and 'rss' in jb.lower():
|
|
nextrun_stamp = utctimestamp() + (int(mylar.CONFIG.RSS_CHECKINTERVAL) * 60)
|
|
mylar.SCHED_RSS_LAST = last_run_completed
|
|
jobstore = jbst
|
|
break
|
|
elif job == 'Weekly Pullist' and 'weekly' in jb.lower():
|
|
if mylar.CONFIG.ALT_PULL == 2:
|
|
wkt = 4
|
|
else:
|
|
wkt = 24
|
|
nextrun_stamp = utctimestamp() + (wkt * 60 * 60)
|
|
mylar.SCHED_WEEKLY_LAST = last_run_completed
|
|
jobstore = jbst
|
|
break
|
|
elif job == 'Check Version' and 'version' in jb.lower():
|
|
nextrun_stamp = utctimestamp() + (mylar.CONFIG.CHECK_GITHUB_INTERVAL * 60)
|
|
jobstore = jbst
|
|
break
|
|
elif job == 'Folder Monitor' and 'monitor' in jb.lower():
|
|
nextrun_stamp = utctimestamp() + (int(mylar.CONFIG.DOWNLOAD_SCAN_INTERVAL) * 60)
|
|
jobstore = jbst
|
|
break
|
|
|
|
if jobstore is not None:
|
|
nextrun_date = datetime.datetime.utcfromtimestamp(nextrun_stamp)
|
|
jobstore.modify(next_run_time=nextrun_date)
|
|
nextrun_date = nextrun_date.replace(microsecond=0)
|
|
else:
|
|
# if the rss is enabled after startup, we have to re-set it up...
|
|
nextrun_stamp = utctimestamp() + (int(mylar.CONFIG.RSS_CHECKINTERVAL) * 60)
|
|
nextrun_date = datetime.datetime.utcfromtimestamp(nextrun_stamp)
|
|
mylar.SCHED_RSS_LAST = last_run_completed
|
|
|
|
logger.fdebug('ReScheduled job: %s to %s' % (job, nextrun_date))
|
|
lastrun_comp = datetime.datetime.utcfromtimestamp(last_run_completed)
|
|
lastrun_comp = lastrun_comp.replace(microsecond=0)
|
|
#if it's completed, then update the last run time to the ending time of the job
|
|
updateVals = {'prev_run_timestamp': last_run_completed,
|
|
'prev_run_datetime': lastrun_comp,
|
|
'last_run_completed': 'True',
|
|
'next_run_timestamp': nextrun_stamp,
|
|
'next_run_datetime': nextrun_date,
|
|
'status': status}
|
|
|
|
#logger.fdebug('Job update for %s: %s' % (updateCtrl, updateVals))
|
|
myDB.upsert('jobhistory', updateVals, updateCtrl)
|
|
|
|
|
|
def stupidchk():
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
CCOMICS = myDB.select("SELECT COUNT(*) FROM comics WHERE Status='Active'")
|
|
ens = myDB.select("SELECT COUNT(*) FROM comics WHERE Status='Loading' OR Status='Paused'")
|
|
mylar.COUNT_COMICS = CCOMICS[0][0]
|
|
mylar.EN_OOMICS = ens[0][0]
|
|
|
|
def newznab_test(name, host, ssl, apikey):
|
|
from xml.dom.minidom import parseString, Element
|
|
params = {'t': 'search',
|
|
'apikey': apikey,
|
|
'o': 'xml'}
|
|
|
|
if host[:-1] == '/':
|
|
host = host + 'api'
|
|
else:
|
|
host = host + '/api'
|
|
headers = {'User-Agent': str(mylar.USER_AGENT)}
|
|
logger.info('host: %s' % host)
|
|
try:
|
|
r = requests.get(host, params=params, headers=headers, verify=bool(ssl))
|
|
except Exception as e:
|
|
logger.warn('Unable to connect: %s' % e)
|
|
return
|
|
else:
|
|
try:
|
|
data = parseString(r.content)
|
|
except Exception as e:
|
|
logger.warn('[WARNING] Error attempting to test: %s' % e)
|
|
|
|
try:
|
|
error_code = data.getElementsByTagName('error')[0].attributes['code'].value
|
|
except Exception as e:
|
|
logger.info('Connected - Status code returned: %s' % r.status_code)
|
|
if r.status_code == 200:
|
|
return True
|
|
else:
|
|
logger.warn('Received response - Status code returned: %s' % r.status_code)
|
|
return False
|
|
|
|
code = error_code
|
|
description = data.getElementsByTagName('error')[0].attributes['description'].value
|
|
logger.info('[ERROR:%s] - %s' % (code, description))
|
|
return False
|
|
|
|
def get_free_space(folder):
|
|
min_threshold = 100000000 #threshold for minimum amount of freespace available (#100mb)
|
|
if platform.system() == "Windows":
|
|
free_bytes = ctypes.c_ulonglong(0)
|
|
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
|
|
dst_freesize = free_bytes.value
|
|
else:
|
|
st = os.statvfs(folder)
|
|
dst_freesize = st.f_bavail * st.f_frsize
|
|
logger.fdebug('[FREESPACE-CHECK] %s has %s free' % (folder, sizeof_fmt(dst_freesize)))
|
|
if min_threshold > dst_freesize:
|
|
logger.warn('[FREESPACE-CHECK] There is only %s space left on %s' % (dst_freesize, folder))
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def sizeof_fmt(num, suffix='B'):
|
|
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
|
|
if abs(num) < 1024.0:
|
|
return "%3.1f%s%s" % (num, unit, suffix)
|
|
num /= 1024.0
|
|
return "%.1f%s%s" % (num, 'Yi', suffix)
|
|
|
|
def getImage(comicid, url, issueid=None):
|
|
|
|
if os.path.exists(mylar.CONFIG.CACHE_DIR):
|
|
pass
|
|
else:
|
|
#let's make the dir.
|
|
try:
|
|
os.makedirs(str(mylar.CONFIG.CACHE_DIR))
|
|
logger.info('Cache Directory successfully created at: %s' % mylar.CONFIG.CACHE_DIR)
|
|
|
|
except OSError:
|
|
logger.error('Could not create cache dir. Check permissions of cache dir: %s' % mylar.CONFIG.CACHE_DIR)
|
|
|
|
coverfile = os.path.join(mylar.CONFIG.CACHE_DIR, str(comicid) + '.jpg')
|
|
|
|
#if cover has '+' in url it's malformed, we need to replace '+' with '%20' to retreive properly.
|
|
|
|
#new CV API restriction - one api request / second.(probably unecessary here, but it doesn't hurt)
|
|
if mylar.CONFIG.CVAPI_RATE is None or mylar.CONFIG.CVAPI_RATE < 2:
|
|
time.sleep(2)
|
|
else:
|
|
time.sleep(mylar.CONFIG.CVAPI_RATE)
|
|
|
|
logger.info('Attempting to retrieve the comic image for series')
|
|
try:
|
|
r = requests.get(url, params=None, stream=True, verify=mylar.CONFIG.CV_VERIFY, headers=mylar.CV_HEADERS)
|
|
except Exception as e:
|
|
logger.warn('[ERROR: %s] Unable to download image from CV URL link: %s' % (e, url))
|
|
coversize = 0
|
|
statuscode = '400'
|
|
else:
|
|
statuscode = str(r.status_code)
|
|
logger.fdebug('comic image retrieval status code: %s' % statuscode)
|
|
|
|
if statuscode != '200':
|
|
logger.warn('Unable to download image from CV URL link: %s [Status Code returned: %s]' % (url, statuscode))
|
|
coversize = 0
|
|
else:
|
|
if r.headers.get('Content-Encoding') == 'gzip':
|
|
buf = StringIO(r.content)
|
|
f = gzip.GzipFile(fileobj=buf)
|
|
|
|
with open(coverfile, 'wb') as f:
|
|
for chunk in r.iter_content(chunk_size=1024):
|
|
if chunk: # filter out keep-alive new chunks
|
|
f.write(chunk)
|
|
f.flush()
|
|
|
|
|
|
statinfo = os.stat(coverfile)
|
|
coversize = statinfo.st_size
|
|
|
|
if any([int(coversize) < 10000, statuscode != '200']):
|
|
try:
|
|
if statuscode != '200':
|
|
logger.info('Trying to grab an alternate cover due to problems trying to retrieve the main cover image.')
|
|
else:
|
|
logger.info('Image size invalid [%s bytes] - trying to get alternate cover image.' % coversize)
|
|
except Exception as e:
|
|
logger.info('Image size invalid [%s bytes] - trying to get alternate cover image.' % coversize)
|
|
|
|
logger.fdebug('invalid image link is here: %s' % url)
|
|
|
|
if os.path.exists(coverfile):
|
|
os.remove(coverfile)
|
|
|
|
return 'retry'
|
|
|
|
def publisherImages(publisher):
|
|
if publisher == 'DC Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-dccomics.png',
|
|
'publisher_image_alt': 'DC',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Marvel':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-marvel.jpg',
|
|
'publisher_image_alt': 'Marvel',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Image':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-imagecomics.png',
|
|
'publisher_image_alt': 'Image',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Dark Horse Comics' or publisher == 'Dark Horse':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-darkhorse.png',
|
|
'publisher_image_alt': 'DarkHorse',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'IDW Publishing':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-idwpublish.png',
|
|
'publisher_image_alt': 'IDW',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Icon':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-iconcomics.png',
|
|
'publisher_image_alt': 'Icon',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Red5':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-red5comics.png',
|
|
'publisher_image_alt': 'Red5',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Vertigo':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-vertigo.png',
|
|
'publisher_image_alt': 'Vertigo',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Shadowline':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-shadowline.png',
|
|
'publisher_image_alt': 'Shadowline',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '150'}
|
|
elif publisher == 'Archie Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-archiecomics.jpg',
|
|
'publisher_image_alt': 'Archie',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Oni Press':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-onipress.jpg',
|
|
'publisher_image_alt': 'Oni Press',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Tokyopop':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-tokyopop.jpg',
|
|
'publisher_image_alt': 'Tokyopop',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Midtown Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-midtowncomics.jpg',
|
|
'publisher_image_alt': 'Midtown',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Boom! Studios':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-boom.jpg',
|
|
'publisher_image_alt': 'Boom!',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Skybound':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-skybound.jpg',
|
|
'publisher_image_alt': 'Skybound',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Dynamite Entertainment':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-dynamite.png',
|
|
'publisher_image_alt': 'Dynamite',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '125'}
|
|
elif publisher == 'Top Cow':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-topcow.gif',
|
|
'publisher_image_alt': 'Top Cow',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Cartoon Books':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-cartoonbooks.jpg',
|
|
'publisher_image_alt': 'Cartoon Books',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '90'}
|
|
elif publisher == 'Valiant':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-valiant.png',
|
|
'publisher_image_alt': 'Valiant',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Action Lab':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-actionlabs.png',
|
|
'publisher_image_alt': 'Action Lab',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Zenescope Entertainment':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-zenescope.png',
|
|
'publisher_image_alt': 'Zenescope',
|
|
'publisher_imageH': '125',
|
|
'publisher_imageW': '125'}
|
|
elif publisher == '2000 ad':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-2000ad.jpg',
|
|
'publisher_image_alt': '2000 AD',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Aardvark':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-aardvark.png',
|
|
'publisher_image_alt': 'Aardvark',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Abstract Studio':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-abstract.jpg',
|
|
'publisher_image_alt': 'Abstract Studio',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Aftershock Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-aftershock.jpg',
|
|
'publisher_image_alt': 'Aftershock',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Avatar Press':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-avatarpress.jpg',
|
|
'publisher_image_alt': 'Avatar Press',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Benitez Productions':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-benitez.png',
|
|
'publisher_image_alt': 'Benitez',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '125'}
|
|
elif publisher == 'Boundless Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-boundless.png',
|
|
'publisher_image_alt': 'Boundless',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Darby Pop':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-darbypop.png',
|
|
'publisher_image_alt': 'Darby Pop',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '125'}
|
|
elif publisher == 'Devil\'s Due':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-devilsdue.png',
|
|
'publisher_image_alt': 'Devil\'s Due',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Joe Books':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-joebooks.png',
|
|
'publisher_image_alt': 'Joe Books',
|
|
'publisher_imageH': '100',
|
|
'publisher_imageW': '100'}
|
|
elif publisher == 'Titan Comics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-titan.png',
|
|
'publisher_image_alt': 'Titan',
|
|
'publisher_imageH': '75',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Viz':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-viz.png',
|
|
'publisher_image_alt': 'Viz',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '50'}
|
|
elif publisher == 'Warp Graphics':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-warpgraphics.png',
|
|
'publisher_image_alt': 'Warp Graphics',
|
|
'publisher_imageH': '125',
|
|
'publisher_imageW': '75'}
|
|
elif publisher == 'Wildstorm':
|
|
comicpublisher = {'publisher_image': 'interfaces/default/images/publisherlogos/logo-wildstorm.png',
|
|
'publisher_image_alt': 'Wildstorm',
|
|
'publisher_imageH': '50',
|
|
'publisher_imageW': '100'}
|
|
else:
|
|
comicpublisher = {'publisher_image': None,
|
|
'publisher_image_alt': 'Nope',
|
|
'publisher_imageH': '0',
|
|
'publisher_imageW': '0'}
|
|
|
|
return comicpublisher
|
|
|
|
def lookupthebitches(filelist, folder, nzbname, nzbid, prov, hash, pulldate):
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
watchlist = listLibrary()
|
|
matchlist = []
|
|
#get the weeknumber/year for the pulldate
|
|
dt = datetime.datetime.strptime(pulldate, '%Y-%m-%d')
|
|
weeknumber = dt.strftime("%U")
|
|
year = dt.strftime("%Y")
|
|
for f in filelist:
|
|
file = re.sub(folder, '', f).strip()
|
|
pp = mylar.filechecker.FileChecker(justparse=True, file=file)
|
|
parsedinfo = pp.listFiles()
|
|
if parsedinfo['parse_status'] == 'success':
|
|
dyncheck = re.sub('[\|\s]', '', parsedinfo['dynamic_name'].lower()).strip()
|
|
check = myDB.selectone('SELECT * FROM weekly WHERE DynamicName=? AND weeknumber=? AND year=? AND STATUS<>"Downloaded"', [dyncheck, weeknumber, year]).fetchone()
|
|
if check is not None:
|
|
logger.fdebug('[%s] found match: %s #%s' % (file, check['COMIC'], check['ISSUE']))
|
|
matchlist.append({'comicname': check['COMIC'],
|
|
'issue': check['ISSUE'],
|
|
'comicid': check['ComicID'],
|
|
'issueid': check['IssueID'],
|
|
'dynamicname': check['DynamicName']})
|
|
else:
|
|
logger.fdebug('[%s] unable to match to the pull: %s' % (file, parsedinfo))
|
|
|
|
if len(matchlist) > 0:
|
|
for x in matchlist:
|
|
if all([x['comicid'] not in watchlist, mylar.CONFIG.PACK_0DAY_WATCHLIST_ONLY is False]):
|
|
oneoff = True
|
|
mode = 'pullwant'
|
|
elif all([x['comicid'] not in watchlist, mylar.CONFIG.PACK_0DAY_WATCHLIST_ONLY is True]):
|
|
continue
|
|
else:
|
|
oneoff = False
|
|
mode = 'want'
|
|
mylar.updater.nzblog(x['issueid'], nzbname, x['comicname'], id=nzbid, prov=prov, oneoff=oneoff)
|
|
mylar.updater.foundsearch(x['comicid'], x['issueid'], mode=mode, provider=prov, hash=hash)
|
|
|
|
|
|
def DateAddedFix():
|
|
#import db
|
|
myDB = db.DBConnection()
|
|
DA_A = datetime.datetime.today()
|
|
DateAdded = DA_A.strftime('%Y-%m-%d')
|
|
issues = myDB.select("SELECT IssueID FROM issues WHERE Status='Wanted' and DateAdded is NULL")
|
|
for da in issues:
|
|
myDB.upsert("issues", {'DateAdded': DateAdded}, {'IssueID': da[0]})
|
|
annuals = myDB.select("SELECT IssueID FROM annuals WHERE Status='Wanted' and DateAdded is NULL")
|
|
for an in annuals:
|
|
myDB.upsert("annuals", {'DateAdded': DateAdded}, {'IssueID': an[0]})
|
|
|
|
def file_ops(path,dst,arc=False,one_off=False):
|
|
# # path = source path + filename
|
|
# # dst = destination path + filename
|
|
# # arc = to denote if the file_operation is being performed as part of a story arc or not where the series exists on the watchlist already
|
|
# # one-off = if the file_operation is being performed where it is either going into the grabbab_dst or story arc folder
|
|
|
|
# #get the crc of the file prior to the operation and then compare after to ensure it's complete.
|
|
# crc_check = mylar.filechecker.crc(path)
|
|
# #will be either copy / move
|
|
|
|
if any([one_off, arc]):
|
|
action_op = mylar.CONFIG.ARC_FILEOPS
|
|
else:
|
|
action_op = mylar.CONFIG.FILE_OPTS
|
|
|
|
if action_op == 'copy' or (arc is True and any([action_op == 'copy', action_op == 'move'])):
|
|
try:
|
|
shutil.copy( path , dst )
|
|
# if crc_check == mylar.filechecker.crc(dst):
|
|
except Exception as e:
|
|
logger.error('[%s] error : %s' % (action_op, e))
|
|
return False
|
|
return True
|
|
|
|
elif action_op == 'move':
|
|
try:
|
|
shutil.move( path , dst )
|
|
# if crc_check == mylar.filechecker.crc(dst):
|
|
except Exception as e:
|
|
logger.error('[MOVE] error : %s' % e)
|
|
return False
|
|
return True
|
|
|
|
elif any([action_op == 'hardlink', action_op == 'softlink']):
|
|
if 'windows' not in mylar.OS_DETECT.lower():
|
|
# if it's an arc, then in needs to go reverse since we want to keep the src files (in the series directory)
|
|
if action_op == 'hardlink':
|
|
import sys
|
|
|
|
# Open a file
|
|
try:
|
|
fd = os.open( path, os.O_RDWR|os.O_CREAT )
|
|
os.close( fd )
|
|
|
|
# Now create another copy of the above file.
|
|
os.link( path, dst )
|
|
logger.info('Created hard link successfully!!')
|
|
except OSError, e:
|
|
if e.errno == errno.EXDEV:
|
|
logger.warn('[' + str(e) + '] Hardlinking failure. Could not create hardlink - dropping down to copy mode so that this operation can complete. Intervention is required if you wish to continue using hardlinks.')
|
|
try:
|
|
shutil.copy( path, dst )
|
|
logger.fdebug('Successfully copied file to : ' + dst)
|
|
return True
|
|
except Exception as e:
|
|
logger.error('[COPY] error : %s' % e)
|
|
return False
|
|
else:
|
|
logger.warn('[' + str(e) + '] Hardlinking failure. Could not create hardlink - Intervention is required if you wish to continue using hardlinks.')
|
|
return False
|
|
|
|
hardlinks = os.lstat( dst ).st_nlink
|
|
if hardlinks > 1:
|
|
logger.info('Created hard link [' + str(hardlinks) + '] successfully!! (' + dst + ')')
|
|
else:
|
|
logger.warn('Hardlink cannot be verified. You should probably verify that it is created properly.')
|
|
|
|
return True
|
|
|
|
elif action_op == 'softlink':
|
|
try:
|
|
#first we need to copy the file to the new location, then create the symlink pointing from new -> original
|
|
if not arc:
|
|
shutil.move( path, dst )
|
|
if os.path.lexists( path ):
|
|
os.remove( path )
|
|
os.symlink( dst, path )
|
|
logger.fdebug('Successfully created softlink [' + dst + ' --> ' + path + ']')
|
|
else:
|
|
os.symlink ( path, dst )
|
|
logger.fdebug('Successfully created softlink [' + path + ' --> ' + dst + ']')
|
|
except OSError, e:
|
|
#if e.errno == errno.EEXIST:
|
|
# os.remove(dst)
|
|
# os.symlink( path, dst )
|
|
#else:
|
|
logger.warn('[' + str(e) + '] Unable to create symlink. Dropping down to copy mode so that this operation can continue.')
|
|
try:
|
|
shutil.copy( dst, path )
|
|
logger.fdebug('Successfully copied file [' + dst + ' --> ' + path + ']')
|
|
except Exception as e:
|
|
logger.error('[COPY] error : %s' % e)
|
|
return False
|
|
|
|
return True
|
|
|
|
else:
|
|
#Not ready just yet.
|
|
pass
|
|
|
|
#softlinks = shortcut (normally junctions are called softlinks, but for this it's being called a softlink)
|
|
#hardlinks = MUST reside on the same drive as the original
|
|
#junctions = not used (for directories across same machine only but different drives)
|
|
|
|
#option 1
|
|
#this one needs to get tested
|
|
#import ctypes
|
|
#kdll = ctypes.windll.LoadLibrary("kernel32.dll")
|
|
#kdll.CreateSymbolicLinkW(path, dst, 0)
|
|
|
|
#option 2
|
|
import lib.winlink as winlink
|
|
if mylar.CONFIG.FILE_OPTS == 'hardlink':
|
|
try:
|
|
os.system(r'mklink /H dst path')
|
|
logger.fdebug('Successfully hardlinked file [' + dst + ' --> ' + path + ']')
|
|
except OSError, e:
|
|
logger.warn('[' + e + '] Unable to create symlink. Dropping down to copy mode so that this operation can continue.')
|
|
try:
|
|
shutil.copy( dst, path )
|
|
logger.fdebug('Successfully copied file [' + dst + ' --> ' + path + ']')
|
|
except:
|
|
return False
|
|
|
|
elif mylar.CONFIG.FILE_OPTS == 'softlink': #ie. shortcut.
|
|
try:
|
|
shutil.move( path, dst )
|
|
if os.path.lexists( path ):
|
|
os.remove( path )
|
|
os.system(r'mklink dst path')
|
|
logger.fdebug('Successfully created symlink [' + dst + ' --> ' + path + ']')
|
|
except OSError, e:
|
|
raise e
|
|
logger.warn('[' + e + '] Unable to create softlink. Dropping down to copy mode so that this operation can continue.')
|
|
try:
|
|
shutil.copy( dst, path )
|
|
logger.fdebug('Successfully copied file [' + dst + ' --> ' + path + ']')
|
|
except:
|
|
return False
|
|
|
|
|
|
else:
|
|
return False
|
|
|
|
from threading import Thread
|
|
|
|
class ThreadWithReturnValue(Thread):
|
|
def __init__(self, group=None, target=None, name=None,
|
|
args=(), kwargs={}, Verbose=None):
|
|
Thread.__init__(self, group, target, name, args, kwargs, Verbose)
|
|
self._return = None
|
|
def run(self):
|
|
if self._Thread__target is not None:
|
|
self._return = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
|
|
|
|
def join(self):
|
|
Thread.join(self)
|
|
return self._return
|
|
|