Merge branch 'main' into dev

This commit is contained in:
JackDandy 2024-08-10 12:56:58 +01:00
commit c6bfc9c3d1
37 changed files with 211 additions and 16 deletions

75
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,75 @@
test 3.8:
image: python:3.8
stage: test
before_script:
- pip install responses
- pip install -r requirements.txt
- pip install coveralls
script:
- cd ./tests
- coverage run --source=.. --omit=../lib/* ./all_tests.py
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "dev"
test 3.9:
image: python:3.9
stage: test
before_script:
- pip install responses
- pip install -r requirements.txt
- pip install coveralls
script:
- cd ./tests
- coverage run --source=.. --omit=../lib/* ./all_tests.py
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "dev"
test 3.10:
image: python:3.10
stage: test
before_script:
- pip install responses
- pip install -r requirements.txt
- pip install coveralls
script:
- cd ./tests
- coverage run --source=.. --omit=../lib/* ./all_tests.py
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "dev"
test 3.11:
image: python:3.11
stage: test
before_script:
- pip install responses
- pip install -r requirements.txt
- pip install coveralls
script:
- cd ./tests
- coverage run --source=.. --omit=../lib/* ./all_tests.py
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "dev"
test 3.12:
image: python:3.12
stage: test
before_script:
- pip install responses
- pip install -r requirements.txt
- pip install coveralls
script:
- cd ./tests
- coverage run --source=.. --omit=../lib/* ./all_tests.py
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "dev"

View file

@ -5,6 +5,16 @@
* Update urllib3 2.2.1 (54d6edf) to 2.2.2 (27e2a5c) * Update urllib3 2.2.1 (54d6edf) to 2.2.2 (27e2a5c)
### 3.32.4 (2024-08-10 11:40:00 UTC)
* Change person cards to not display "eps" when number of eps is unknown
* Fix wrong added death dates
* Fix parsing changes to IMDb bio
* Change improve efficiency when saving config.ini
* Change improve efficiency when saving viewshow glide
* Change allow Python 3.12.5
### 3.32.3 (2024-06-26 18:10:00 UTC) ### 3.32.3 (2024-06-26 18:10:00 UTC)
* Fix votes on shows/IMDb cards * Fix votes on shows/IMDb cards

View file

@ -4,6 +4,7 @@ Libs with customisations...
/lib/backports/configparser /lib/backports/configparser
/lib/browser_ua /lib/browser_ua
/lib/bs4 /lib/bs4
/lib/configobj/__init__.py
/lib/dateutil/zoneinfo/__init__.py /lib/dateutil/zoneinfo/__init__.py
/lib/dateutil/tz/tz.py /lib/dateutil/tz/tz.py
/lib/enzyme /lib/enzyme

View file

@ -595,7 +595,7 @@ $(function() {
#if $this_show.get('p_chars') #if $this_show.get('p_chars')
<p style='overflow-y:auto;max-height:152px'> <p style='overflow-y:auto;max-height:152px'>
#for $char in $this_show['p_chars'] #for $char in $this_show['p_chars']
<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#393'>as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]/$char[3] eps)#end if#</span> <span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#393'>as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]#if $char[3]#/$char[3] eps#end if#)#end if#</span>
#end for #end for
</p> </p>
#end if #end if

View file

@ -245,7 +245,9 @@ $(document).ready(function() {
if (saveTime){ if (saveTime){
params.slidetime = $.SickGear.config.glideSlideTime; params.slidetime = $.SickGear.config.glideSlideTime;
} }
$.get($.SickGear.Root + '/home/set-display-show-glide', params); if (!$.isEmptyObject(params)){
$.get($.SickGear.Root + '/home/set-display-show-glide', params);
}
} }
} }

View file

@ -219,7 +219,7 @@ class IMDbIndexer(TVInfoBase):
if not bio: if not bio:
return return
with BS4Parser(bio) as bio_item: with BS4Parser(bio) as bio_item:
bv = bio_item.find(string='Mini Bio', recursive=True).find_next('p') bv = bio_item.find('div', attrs={'data-testid': re.compile('mini_bio$')}, recursive=True)
for a in bv.findAll('a'): for a in bv.findAll('a'):
a.replaceWithChildren() a.replaceWithChildren()
for b in bv.findAll('br'): for b in bv.findAll('br'):

View file

@ -2097,6 +2097,7 @@ class ConfigObj(Section):
else: else:
with open(self.filename, 'wb') as h: with open(self.filename, 'wb') as h:
h.write(output_bytes) h.write(output_bytes)
h.flush()
def validate(self, validator, preserve_errors=False, copy=False, def validate(self, validator, preserve_errors=False, copy=False,
section=None): section=None):

View file

@ -38,7 +38,7 @@ warnings.filterwarnings('ignore', message='.*deprecated in cryptography.*')
versions = [((3, 8, 2), (3, 8, 19)), versions = [((3, 8, 2), (3, 8, 19)),
((3, 9, 0), (3, 9, 2)), ((3, 9, 4), (3, 9, 19)), ((3, 9, 0), (3, 9, 2)), ((3, 9, 4), (3, 9, 19)),
((3, 10, 0), (3, 12, 4))] # inclusive version ranges ((3, 10, 0), (3, 12, 5))] # inclusive version ranges
if not any(list(map(lambda v: v[0] <= sys.version_info[:3] <= v[1], versions))) and not int(os.environ.get('PYT', 0)): if not any(list(map(lambda v: v[0] <= sys.version_info[:3] <= v[1], versions))) and not int(os.environ.get('PYT', 0)):
major, minor, micro = sys.version_info[:3] major, minor, micro = sys.version_info[:3]
print('Python %s.%s.%s detected.' % (major, minor, micro)) print('Python %s.%s.%s detected.' % (major, minor, micro))

View file

@ -16,6 +16,7 @@
from collections import OrderedDict from collections import OrderedDict
from threading import Lock from threading import Lock
import copy
import datetime import datetime
import io import io
import os import os
@ -39,6 +40,7 @@ from . import auto_media_process, properFinder # must come after the above impo
from .common import SD, SKIPPED, USER_AGENT from .common import SD, SKIPPED, USER_AGENT
from .config import check_section, check_setting_int, check_setting_str, ConfigMigrator, minimax from .config import check_section, check_setting_int, check_setting_str, ConfigMigrator, minimax
from .databases import cache_db, failed_db, mainDB from .databases import cache_db, failed_db, mainDB
from .event_queue import ConfigEvents
from .indexers.indexer_api import TVInfoAPI from .indexers.indexer_api import TVInfoAPI
from .indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB, TmdbIndexer from .indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB, TmdbIndexer
from .providers.generic import GenericProvider from .providers.generic import GenericProvider
@ -73,6 +75,7 @@ ENV = {}
CFG = None # type: ConfigObj CFG = None # type: ConfigObj
CONFIG_FILE = '' CONFIG_FILE = ''
CONFIG_VERSION = None CONFIG_VERSION = None
CONFIG_OLD = None
# Default encryption version (0 for None) # Default encryption version (0 for None)
ENCRYPTION_VERSION = 0 ENCRYPTION_VERSION = 0
@ -86,6 +89,7 @@ DATA_DIR = ''
# system events # system events
# noinspection PyTypeChecker # noinspection PyTypeChecker
events = None # type: Events events = None # type: Events
config_events = None # type: ConfigEvents
show_queue_scheduler = None # type: Optional[scheduler.Scheduler] show_queue_scheduler = None # type: Optional[scheduler.Scheduler]
search_queue_scheduler = None # type: Optional[scheduler.Scheduler] search_queue_scheduler = None # type: Optional[scheduler.Scheduler]
@ -667,7 +671,7 @@ def init_stage_1(console_logging):
WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, ADD_SHOWS_METALANG, \ WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, ADD_SHOWS_METALANG, \
CREATE_MISSING_SHOW_DIRS, SHOW_DIRS_WITH_DOTS, \ CREATE_MISSING_SHOW_DIRS, SHOW_DIRS_WITH_DOTS, \
RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, TVINFO_DEFAULT, \ RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, TVINFO_DEFAULT, \
CONFIG_FILE, CONFIG_VERSION, \ CONFIG_FILE, CONFIG_VERSION, CONFIG_OLD, \
REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE, MODULE_UPDATE_STRING, EXT_UPDATES REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE, MODULE_UPDATE_STRING, EXT_UPDATES
# Add Show Search # Add Show Search
global RESULTS_SORTBY global RESULTS_SORTBY
@ -1537,7 +1541,7 @@ def init_stage_2():
search_recent_scheduler, search_subtitles_scheduler, \ search_recent_scheduler, search_subtitles_scheduler, \
search_queue_scheduler, show_queue_scheduler, people_queue_scheduler, \ search_queue_scheduler, show_queue_scheduler, people_queue_scheduler, \
watched_state_queue_scheduler, emby_watched_state_scheduler, plex_watched_state_scheduler, \ watched_state_queue_scheduler, emby_watched_state_scheduler, plex_watched_state_scheduler, \
process_media_scheduler, background_mapping_task process_media_scheduler, background_mapping_task, config_events
# Gen Config/Misc # Gen Config/Misc
global SHOW_UPDATE_HOUR, UPDATE_INTERVAL, UPDATE_PACKAGES_INTERVAL global SHOW_UPDATE_HOUR, UPDATE_INTERVAL, UPDATE_PACKAGES_INTERVAL
@ -1728,6 +1732,9 @@ def init_stage_2():
except (BaseException, Exception): except (BaseException, Exception):
pass pass
config_events = ConfigEvents(_save_config)
config_events.start()
__INITIALIZED__ = True __INITIALIZED__ = True
return True return True
@ -1801,7 +1808,7 @@ def sig_handler(signum=None, _=None):
def halt(): def halt():
global __INITIALIZED__, started global __INITIALIZED__, started, config_events
logger.debug('Check INIT_LOCK on halt') logger.debug('Check INIT_LOCK on halt')
with INIT_LOCK: with INIT_LOCK:
@ -1811,6 +1818,11 @@ def halt():
logger.log('Exiting threads') logger.log('Exiting threads')
try:
config_events.stopit()
except (BaseException, Exception):
pass
for p in provider_ping_thread_pool: for p in provider_ping_thread_pool:
provider_ping_thread_pool[p].stop = True provider_ping_thread_pool[p].stop = True
@ -1854,6 +1866,11 @@ def halt():
except (BaseException, Exception) as e: except (BaseException, Exception) as e:
logger.log('Thread %s exception %s' % (thread.name, e)) logger.log('Thread %s exception %s' % (thread.name, e))
try:
config_events.join(10)
except RuntimeError:
pass
__INITIALIZED__ = False __INITIALIZED__ = False
started = False started = False
@ -1869,10 +1886,23 @@ def save_all():
# save config # save config
logger.log('Saving config file to disk') logger.log('Saving config file to disk')
save_config() _save_config(force=True)
def save_config(): def save_config(force=False):
# type: (bool) -> None
"""
add queue request for saving the config.ini
:param force: force save config even if unchanged
"""
global config_events
config_events.put(force)
def _save_config(force=False, **kwargs):
# type: (bool, ...) -> None
global CONFIG_OLD
new_config = ConfigObj() new_config = ConfigObj()
new_config.filename = CONFIG_FILE new_config.filename = CONFIG_FILE
@ -2425,6 +2455,9 @@ def save_config():
new_config['ANIME'] = {} new_config['ANIME'] = {}
new_config['ANIME']['anime_treat_as_hdtv'] = int(ANIME_TREAT_AS_HDTV) new_config['ANIME']['anime_treat_as_hdtv'] = int(ANIME_TREAT_AS_HDTV)
if not force and CONFIG_OLD == new_config and os.path.isfile(new_config.filename):
logger.debug('config.ini not dirty, not saving.')
return
from sg_helpers import copy_file from sg_helpers import copy_file
backup_config = re.sub(r'\.ini$', '.bak', CONFIG_FILE) backup_config = re.sub(r'\.ini$', '.bak', CONFIG_FILE)
from .config import check_valid_config from .config import check_valid_config
@ -2440,6 +2473,7 @@ def save_config():
for _ in range(0, 3): for _ in range(0, 3):
new_config.write() new_config.write()
if check_valid_config(CONFIG_FILE): if check_valid_config(CONFIG_FILE):
CONFIG_OLD = copy.deepcopy(new_config)
return return
logger.warning('saving config file failed, retrying...') logger.warning('saving config file failed, retrying...')
remove_file_perm(CONFIG_FILE) remove_file_perm(CONFIG_FILE)

View file

@ -2,6 +2,67 @@ import queue
import threading import threading
class SetQueue(queue.Queue):
def _init(self, maxsize):
self.queue = set()
def _put(self, item):
self.queue.add(item)
def _get(self):
return self.queue.pop()
class ConfigEvents(threading.Thread):
def __init__(self, callback):
super(ConfigEvents, self).__init__()
self.queue = SetQueue()
self.callback = callback
self.name = 'CONFIG-EVENTS'
self._stopper = threading.Event()
def put(self, etype):
# type: (bool) -> None
"""
put config save event into queue
:param etype: force save config.ini if unchanged
"""
self.queue.put(etype)
def stopit(self):
self._stopper.set()
def run(self):
while not self._stopper.is_set():
try:
# get event type
ev_type = self.queue.get(True, 5)
except queue.Empty:
continue
except(BaseException, Exception):
continue
if ev_type in (True, False, None):
if ev_type is None:
continue
from sickgear import logger
logger.debug(f'Callback {self.callback.__name__}(event type:{ev_type})')
try:
# perform callback if we got an event type
self.callback(ev_type)
# event completed
self.queue.task_done()
except queue.Empty:
pass
except (BaseException, Exception):
pass
# exiting thread
self._stopper.clear()
class Event(object): class Event(object):
def __init__(self, etype): def __init__(self, etype):
self._type = etype self._type = etype

View file

@ -583,7 +583,7 @@ class Person(Referential):
continue continue
if cur_key not in self.__dict__: if cur_key not in self.__dict__:
raise Exception('Person has no property [%s]' % cur_key) raise Exception('Person has no property [%s]' % cur_key)
if None is not cur_value: if None is not cur_value or ('deathday' == cur_key and kwargs.get('birthday')):
if 'akas' == cur_key: if 'akas' == cur_key:
cur_value.update(self.akas) cur_value.update(self.akas)
elif 'nicknames' == cur_key: elif 'nicknames' == cur_key:
@ -768,7 +768,7 @@ class Person(Referential):
self._data_failure = True self._data_failure = True
logger.warning('Error searching extra info for person: %s - %s' % (self.name, ex(e))) logger.warning('Error searching extra info for person: %s - %s' % (self.name, ex(e)))
continue continue
if None is not pd and imdb_confirmed and TVINFO_IMDB == cur_tv_src: if None is not pd and imdb_confirmed and TVINFO_IMDB == cur_tv_info_src:
rp = pd rp = pd
break break
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences

View file

@ -1053,12 +1053,18 @@ class MainHandler(WebHandler):
@staticmethod @staticmethod
def set_display_show_glide(slidetime=None, tvid_prodid=None, start_at=None): def set_display_show_glide(slidetime=None, tvid_prodid=None, start_at=None):
if tvid_prodid and start_at: dirty_config = False
if tvid_prodid and start_at and start_at != sickgear.DISPLAY_SHOW_GLIDE.get(tvid_prodid, {}).get('start_at'):
sickgear.DISPLAY_SHOW_GLIDE.setdefault(tvid_prodid, {}).update({'start_at': start_at}) sickgear.DISPLAY_SHOW_GLIDE.setdefault(tvid_prodid, {}).update({'start_at': start_at})
dirty_config = True
if slidetime: if slidetime and (
sickgear.DISPLAY_SHOW_GLIDE_SLIDETIME = sg_helpers.try_int(slidetime, 3000) int_slidetime := sg_helpers.try_int(slidetime, 3000)) != sickgear.DISPLAY_SHOW_GLIDE_SLIDETIME:
sickgear.save_config() sickgear.DISPLAY_SHOW_GLIDE_SLIDETIME = int_slidetime
dirty_config = True
if dirty_config:
sickgear.save_config()
@staticmethod @staticmethod
def set_poster_sortby(sort): def set_poster_sortby(sort):

View file

@ -1 +1 @@
2024-06-25 2024-06-28

View file

@ -1,9 +1,14 @@
import os
import sys
import warnings import warnings
warnings.filterwarnings('ignore', module=r'.*fuz.*', message='.*Sequence.*') warnings.filterwarnings('ignore', module=r'.*fuz.*', message='.*Sequence.*')
warnings.filterwarnings('ignore', module=r'.*connectionpool.*', message='.*certificate verification.*') warnings.filterwarnings('ignore', module=r'.*connectionpool.*', message='.*certificate verification.*')
import unittest import unittest
sys.path.insert(1, os.path.abspath('..'))
sys.path.insert(1, os.path.abspath('../lib'))
from sickgear import properFinder from sickgear import properFinder
import sickgear import sickgear