From 07fe2ec5dc09a210229f69d2ca49fa7a60dd2327 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 13:42:14 +1000 Subject: [PATCH 01/16] Prep for Kodi Piers Fix error in clean logs Use new script.module.bossanov808 logging functions Minor formatting changes per PyCharm --- resources/lib/cabertoss.py | 14 +++++++------- resources/lib/clean.py | 4 ++-- resources/lib/store.py | 5 ----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index d8915ff..d46d879 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -4,6 +4,7 @@ from time import sleep from datetime import datetime, timedelta import socket +from typing import List import xbmc import xbmcvfs @@ -59,7 +60,6 @@ def gather_log_files(): filematch = 'kodi_crashlog_' if crashlog_path and os.path.isdir(crashlog_path): - lastcrash = None dirs, possible_crashlog_files = xbmcvfs.listdir(crashlog_path) for item in possible_crashlog_files: item_with_path = os.path.join(crashlog_path, item) @@ -88,17 +88,17 @@ def gather_log_files(): return log_files -def copy_log_files(log_files: []): +def copy_log_files(log_files: List) -> bool: """ Actually copy the log files to the path in the addon settings - @param log_files: [] list of log files to copy - @return: None + @param log_files: List list of log files to copy + @return bool: indicating success or failure """ if not log_files: Logger.error(LANGUAGE(32025)) Notify.error(LANGUAGE(32025)) - return + return False now_folder_name = f"{socket.gethostname()}_Kodi_Logs_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" now_destination_path = os.path.join(Store.destination_path, now_folder_name) @@ -128,7 +128,7 @@ def copy_log_files(log_files: []): # This is 'main'... def run(): - footprints() + Logger.start() Store.load_config_from_settings() if not Store.destination_path: @@ -142,4 +142,4 @@ def run(): else: Notify.info(LANGUAGE(32029)) # and, we're done... - footprints(startup=False) + Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 23c743b..57b7dc4 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -11,5 +11,5 @@ def clean_log(content): replaces = (('//.+?:.+?@', '//USER:PASSWORD@'), ('.+?', 'USER'), ('.+?', 'PASSWORD'),) for pattern, repl in replaces: - sanitised = re.sub(pattern, repl, content) - return sanitised + content = re.sub(pattern, repl, content) + return content diff --git a/resources/lib/store.py b/resources/lib/store.py index 5168dd8..f8b5a03 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -31,8 +31,3 @@ def load_config_from_settings(): Store.destination_path = ADDON.getSetting('log_path') Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') - - - - - From b0443c00d0a8c6987e101ad1e2acfde035caabd9 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 14:32:41 +1000 Subject: [PATCH 02/16] Update addon-checker.yml --- .github/workflows/addon-checker.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/addon-checker.yml b/.github/workflows/addon-checker.yml index 361f9fc..7bd261f 100644 --- a/.github/workflows/addon-checker.yml +++ b/.github/workflows/addon-checker.yml @@ -8,11 +8,12 @@ jobs: name: Kodi addon checker steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 # Updated from v1 - name: Kodi addon checker validation id: kodi-addon-checker - uses: xbmc/action-kodi-addon-checker@v1.1 + uses: xbmc/action-kodi-addon-checker@v1.3 # Updated from v1.1 with: kodi-version: matrix is-pr: false addon-id: ${{ github.event.repository.name }} + \ No newline at end of file From eb1a63ec6542649fc5b9e86b03e5c2a3f635b404 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 14:39:19 +1000 Subject: [PATCH 03/16] Update docstrings per Code Rabbit review --- resources/lib/cabertoss.py | 26 +++++++++++++++++++++++--- resources/lib/store.py | 5 +++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index d46d879..db48de5 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -90,10 +90,19 @@ def gather_log_files(): def copy_log_files(log_files: List) -> bool: """ - Actually copy the log files to the path in the addon settings + Copy the provided Kodi log files into a timestamped destination folder under the configured addon destination. - @param log_files: List list of log files to copy - @return bool: indicating success or failure + Detailed behavior: + - Expects log_files as a list of 2-element entries [type, path], where `type` is e.g. 'log', 'oldlog', or 'crashlog' and `path` is the source filesystem path. + - Creates a destination directory at Store.destination_path named "_Kodi_Logs_". + - For entries with type 'log' or 'oldlog', reads the source, sanitizes the content with clean_log() (because these paths may be URLs with embedded user/password details), and writes the sanitized content to a file with the same basename in the destination folder. + - For other types (e.g., crash logs), copies the source file to the destination folder unchanged. + + Parameters: + log_files (List): list of log descriptors [type, path] to copy. + + Returns: + bool: True if files were successfully copied, False otherwise. """ if not log_files: Logger.error(LANGUAGE(32025)) @@ -128,6 +137,17 @@ def copy_log_files(log_files: List) -> bool: # This is 'main'... def run(): + """ + Run the log collection and copying flow: initialize this addon's logging, load configuration, gather Kodi log files, copy them to the configured destination, notify the user, and stop this addon's logging. + + This function performs the module's main orchestration. It: + - Starts the logger for this addon's internal logging (not Kodi's general logging system) and loads addon configuration from settings. + - If no destination path is configured, shows an error notification and skips copying. + - Otherwise, notifies the user, gathers available log files, attempts to copy them to the configured destination, and notifies success (including number of files copied) or failure. + - Stops this addon's internal logging before returning. + + Side effects: starts/stops this addon's internal logging, reads configuration, performs filesystem operations (reading, sanitizing, and copying log files), and shows user notifications. Returns None. + """ Logger.start() Store.load_config_from_settings() diff --git a/resources/lib/store.py b/resources/lib/store.py index f8b5a03..e72a70d 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -24,8 +24,9 @@ def __init__(self): @staticmethod def load_config_from_settings(): """ - Load in the addon settings, at start or reload them if they have been changed - Log each setting as it is loaded + Load the addon's configuration from persistent settings. + + Reads the 'log_path' setting and assigns it to Store.destination_path, then logs the resolved path (sanitized with clean_log because these paths may be URLs with embedded user/password details). This is called at startup and when settings are reloaded; it has no return value. """ Logger.info("Loading configuration from settings") Store.destination_path = ADDON.getSetting('log_path') From d310d49de2fbbd4d6a6ac18bee88e0743fdac747 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 14:54:09 +1000 Subject: [PATCH 04/16] Update addon-checker.yml --- .github/workflows/addon-checker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/addon-checker.yml b/.github/workflows/addon-checker.yml index 7bd261f..69e39b1 100644 --- a/.github/workflows/addon-checker.yml +++ b/.github/workflows/addon-checker.yml @@ -8,10 +8,10 @@ jobs: name: Kodi addon checker steps: - name: Checkout - uses: actions/checkout@v4 # Updated from v1 + uses: actions/checkout@v4 - name: Kodi addon checker validation id: kodi-addon-checker - uses: xbmc/action-kodi-addon-checker@v1.3 # Updated from v1.1 + uses: xbmc/action-kodi-addon-checker@v1.2 with: kodi-version: matrix is-pr: false From 6ca8b3ac540f6ab00293c6319366871914d14c30 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 15:15:12 +1000 Subject: [PATCH 05/16] Updates per CR review --- .github/workflows/addon-checker.yml | 4 +-- resources/lib/cabertoss.py | 47 +++++++++++++++-------------- resources/lib/clean.py | 10 ++++-- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/.github/workflows/addon-checker.yml b/.github/workflows/addon-checker.yml index 361f9fc..76f5269 100644 --- a/.github/workflows/addon-checker.yml +++ b/.github/workflows/addon-checker.yml @@ -8,10 +8,10 @@ jobs: name: Kodi addon checker steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Kodi addon checker validation id: kodi-addon-checker - uses: xbmc/action-kodi-addon-checker@v1.1 + uses: xbmc/action-kodi-addon-checker@v1.2 with: kodi-version: matrix is-pr: false diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index db48de5..fac12b9 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -4,12 +4,12 @@ from time import sleep from datetime import datetime, timedelta import socket -from typing import List +from typing import List, Tuple import xbmc import xbmcvfs -from bossanova808.constants import * -from bossanova808.utilities import * +from bossanova808.constants import LOG_PATH +from bossanova808.constants import LANGUAGE from bossanova808.logger import Logger from bossanova808.notify import Notify from resources.lib.store import Store @@ -82,13 +82,13 @@ def gather_log_files(): for crashfile in lastcrash: log_files.append(['crashlog', crashfile]) - Logger.info("Found these log files to copy:") - Logger.info(log_files) + Logger.info("Found these log files to copy (type, basename):") + Logger.info([[t, os.path.basename(p)] for t, p in log_files]) return log_files -def copy_log_files(log_files: List) -> bool: +def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: """ Copy the provided Kodi log files into a timestamped destination folder under the configured addon destination. @@ -99,7 +99,7 @@ def copy_log_files(log_files: List) -> bool: - For other types (e.g., crash logs), copies the source file to the destination folder unchanged. Parameters: - log_files (List): list of log descriptors [type, path] to copy. + log_files (List[Tuple[str, str]]): list of log descriptors [type, path] to copy. Returns: bool: True if files were successfully copied, False otherwise. @@ -118,11 +118,12 @@ def copy_log_files(log_files: List) -> bool: for file in log_files: if file[0] in ['log', 'oldlog']: Logger.info(f'Copying sanitised {file[0]} {file[1]}') - with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8') as current: + with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8', errors='replace') as current: content = current.read() sanitised = clean_log(content) - with xbmcvfs.File(os.path.join(xbmcvfs.translatePath(now_destination_path),os.path.basename(file[1])), 'w') as output: - output.write(sanitised) + dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) + with xbmcvfs.File(dest_path, 'w') as output: + output.write(sanitised.encode('utf-8')) else: Logger.info(f'Copying {file[0]} {file[1]}') if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): @@ -149,17 +150,19 @@ def run(): Side effects: starts/stops this addon's internal logging, reads configuration, performs filesystem operations (reading, sanitizing, and copying log files), and shows user notifications. Returns None. """ Logger.start() - Store.load_config_from_settings() - - if not Store.destination_path: - Notify.error(LANGUAGE(32027)) - else: - Notify.info(LANGUAGE(32030)) - log_file_list = gather_log_files() - result = copy_log_files(log_file_list) - if result: - Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") + try: + Store.load_config_from_settings() + + if not Store.destination_path: + Notify.error(LANGUAGE(32027)) else: - Notify.info(LANGUAGE(32029)) + Notify.info(LANGUAGE(32030)) + log_file_list = gather_log_files() + result = copy_log_files(log_file_list) + if result: + Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") + else: + Notify.info(LANGUAGE(32029)) # and, we're done... - Logger.stop() + finally: + Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 57b7dc4..21bccb1 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -1,14 +1,20 @@ import re -def clean_log(content): +def clean_log(content: str) -> str: """ Remove username/password details from log file content @param content: @return: """ - replaces = (('//.+?:.+?@', '//USER:PASSWORD@'), ('.+?', 'USER'), ('.+?', 'PASSWORD'),) + replaces = ( + # Replace only the credentials part between '//' and '@' + (r'(?<=//)([^/@:\s]+):([^/@\s]+)@', r'USER:PASSWORD@'), + # Replace XML username/password; keep it local to the tag + (r'(?is)[^<]*', r'USER'), + (r'(?is)[^<]*', r'PASSWORD'), + ) for pattern, repl in replaces: content = re.sub(pattern, repl, content) From ae16c4359d268772e5c5572339dee1c565c6c3fa Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 16:42:58 +1000 Subject: [PATCH 06/16] Further updates suggested by CR review --- .../resource.language.en_gb/strings.po | 4 ++++ resources/lib/cabertoss.py | 22 ++++++++++++------- resources/lib/clean.py | 2 ++ resources/lib/store.py | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 9a82849..9f6e46b 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -51,4 +51,8 @@ msgstr " msgctxt "#32030" msgid "Working..." +msgstr " + +msgctxt "#32031" +msgid "Error making directory to copy log files to" msgstr " \ No newline at end of file diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index fac12b9..e74ea3e 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -13,7 +13,7 @@ from bossanova808.logger import Logger from bossanova808.notify import Notify from resources.lib.store import Store -from resources.lib.clean import * +from resources.lib.clean import clean_log def gather_log_files(): @@ -24,9 +24,9 @@ def gather_log_files(): """ # Basic log files - log_files = [['log', os.path.join(LOG_PATH, 'kodi.log')]] + log_files = [('log', os.path.join(LOG_PATH, 'kodi.log'))] if os.path.exists(os.path.join(LOG_PATH, 'kodi.old.log')): - log_files.append(['oldlog', os.path.join(LOG_PATH, 'kodi.old.log')]) + log_files.append(('oldlog', os.path.join(LOG_PATH, 'kodi.old.log'))) # Can we find a crashlog? # @TODO - add Android support if possible..? @@ -80,7 +80,7 @@ def gather_log_files(): if lastcrash: # Logger.info(f"lastcrash {lastcrash}") for crashfile in lastcrash: - log_files.append(['crashlog', crashfile]) + log_files.append(('crashlog', crashfile)) Logger.info("Found these log files to copy (type, basename):") Logger.info([[t, os.path.basename(p)] for t, p in log_files]) @@ -114,7 +114,10 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: try: Logger.info(f'Making destination folder: {now_destination_path}') - xbmcvfs.mkdir(now_destination_path) + if not xbmcvfs.mkdir(now_destination_path): + Logger.error(f'Failed to create destination folder: {now_destination_path}') + Notify.error(LANGUAGE(32031)) + return False for file in log_files: if file[0] in ['log', 'oldlog']: Logger.info(f'Copying sanitised {file[0]} {file[1]}') @@ -122,8 +125,11 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: content = current.read() sanitised = clean_log(content) dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) - with xbmcvfs.File(dest_path, 'w') as output: - output.write(sanitised.encode('utf-8')) + f = xbmcvfs.File(dest_path, 'w') + try: + f.write(sanitised.encode('utf-8')) + finally: + f.close() else: Logger.info(f'Copying {file[0]} {file[1]}') if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): @@ -162,7 +168,7 @@ def run(): if result: Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") else: - Notify.info(LANGUAGE(32029)) + Notify.error(LANGUAGE(32029)) # and, we're done... finally: Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 21bccb1..a000f6c 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -11,6 +11,8 @@ def clean_log(content: str) -> str: replaces = ( # Replace only the credentials part between '//' and '@' (r'(?<=//)([^/@:\s]+):([^/@\s]+)@', r'USER:PASSWORD@'), + # Also scrub username only (no password) + (r'(?<=//)([^/@:\s]+)@', r'USER@'), # Replace XML username/password; keep it local to the tag (r'(?is)[^<]*', r'USER'), (r'(?is)[^<]*', r'PASSWORD'), diff --git a/resources/lib/store.py b/resources/lib/store.py index e72a70d..ebb7299 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -1,6 +1,6 @@ -from bossanova808.constants import * +from bossanova808.constants import ADDON from bossanova808.logger import Logger -from resources.lib.clean import * +from resources.lib.clean import clean_log class Store: From 103b5159fef404b0068867dcb41d1e0524883aac Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:06:05 +1000 Subject: [PATCH 07/16] More CR changes --- resources/lib/cabertoss.py | 18 +++++++++++------- resources/lib/store.py | 8 +++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index e74ea3e..5b59b8b 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- import os -from datetime import datetime -from time import sleep from datetime import datetime, timedelta import socket from typing import List, Tuple @@ -16,6 +14,12 @@ from resources.lib.clean import clean_log +def _vfs_join(base: str, name: str) -> str: + if base.startswith(('special://', 'smb://', 'nfs://', 'ftp://', 'http://', 'https://')): + return base.rstrip('/') + '/' + name + return os.path.join(base, name) + + def gather_log_files(): """ Gather a list of the standard Kodi log files (Kodi.log, Kodi.old.log) and the latest crash log, if there is one. @@ -51,7 +55,7 @@ def gather_log_files(): filematch = 'kodi_' elif xbmc.getCondVisibility('system.platform.android'): Logger.info("System is Android") - Logger.info(LANGUAGE(32023)) + Logger.info(LANGUAGE(32024)) # If *ELEC, we can be more specific if xbmc.getCondVisibility('System.HasAddon(service.coreelec.settings)') or xbmc.getCondVisibility('System.HasAddon(service.libreelec.settings)'): @@ -70,7 +74,7 @@ def gather_log_files(): if three_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): items.append(os.path.join(crashlog_path, item)) - items.sort(key=lambda f: os.path.getmtime(f)) + items.sort(key=lambda f:os.path.getmtime(f)) # Windows crashlogs are a dmp and stacktrace combo... if xbmc.getCondVisibility('system.platform.windows'): lastcrash = items[-2:] @@ -114,7 +118,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: try: Logger.info(f'Making destination folder: {now_destination_path}') - if not xbmcvfs.mkdir(now_destination_path): + if not xbmcvfs.mkdirs(now_destination_path): Logger.error(f'Failed to create destination folder: {now_destination_path}') Notify.error(LANGUAGE(32031)) return False @@ -124,7 +128,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8', errors='replace') as current: content = current.read() sanitised = clean_log(content) - dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) + dest_path = _vfs_join(now_destination_path, os.path.basename(file[1])) f = xbmcvfs.File(dest_path, 'w') try: f.write(sanitised.encode('utf-8')) @@ -132,7 +136,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: f.close() else: Logger.info(f'Copying {file[0]} {file[1]}') - if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): + if not xbmcvfs.copy(file[1], _vfs_join(now_destination_path, os.path.basename(file[1]))): return False return True diff --git a/resources/lib/store.py b/resources/lib/store.py index ebb7299..958fcfe 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -29,6 +29,8 @@ def load_config_from_settings(): Reads the 'log_path' setting and assigns it to Store.destination_path, then logs the resolved path (sanitized with clean_log because these paths may be URLs with embedded user/password details). This is called at startup and when settings are reloaded; it has no return value. """ Logger.info("Loading configuration from settings") - Store.destination_path = ADDON.getSetting('log_path') - - Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') + Store.destination_path = ADDON.get_setting('log_path') + if Store.destination_path: + Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') + else: + Logger.warning(f'No path set to toss logs to.') From bcfbeab7cb49f8a7183afc14bef300eed088defa Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 13:42:14 +1000 Subject: [PATCH 08/16] Prep for Kodi Piers Fix error in clean logs Use new script.module.bossanov808 logging functions Minor formatting changes per PyCharm --- resources/lib/cabertoss.py | 14 +++++++------- resources/lib/clean.py | 4 ++-- resources/lib/store.py | 5 ----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index d8915ff..d46d879 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -4,6 +4,7 @@ from time import sleep from datetime import datetime, timedelta import socket +from typing import List import xbmc import xbmcvfs @@ -59,7 +60,6 @@ def gather_log_files(): filematch = 'kodi_crashlog_' if crashlog_path and os.path.isdir(crashlog_path): - lastcrash = None dirs, possible_crashlog_files = xbmcvfs.listdir(crashlog_path) for item in possible_crashlog_files: item_with_path = os.path.join(crashlog_path, item) @@ -88,17 +88,17 @@ def gather_log_files(): return log_files -def copy_log_files(log_files: []): +def copy_log_files(log_files: List) -> bool: """ Actually copy the log files to the path in the addon settings - @param log_files: [] list of log files to copy - @return: None + @param log_files: List list of log files to copy + @return bool: indicating success or failure """ if not log_files: Logger.error(LANGUAGE(32025)) Notify.error(LANGUAGE(32025)) - return + return False now_folder_name = f"{socket.gethostname()}_Kodi_Logs_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" now_destination_path = os.path.join(Store.destination_path, now_folder_name) @@ -128,7 +128,7 @@ def copy_log_files(log_files: []): # This is 'main'... def run(): - footprints() + Logger.start() Store.load_config_from_settings() if not Store.destination_path: @@ -142,4 +142,4 @@ def run(): else: Notify.info(LANGUAGE(32029)) # and, we're done... - footprints(startup=False) + Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 23c743b..57b7dc4 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -11,5 +11,5 @@ def clean_log(content): replaces = (('//.+?:.+?@', '//USER:PASSWORD@'), ('.+?', 'USER'), ('.+?', 'PASSWORD'),) for pattern, repl in replaces: - sanitised = re.sub(pattern, repl, content) - return sanitised + content = re.sub(pattern, repl, content) + return content diff --git a/resources/lib/store.py b/resources/lib/store.py index 5168dd8..f8b5a03 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -31,8 +31,3 @@ def load_config_from_settings(): Store.destination_path = ADDON.getSetting('log_path') Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') - - - - - From 9bdf478995e247315eabce97b7691c6ada9526a9 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 14:39:19 +1000 Subject: [PATCH 09/16] Update docstrings per Code Rabbit review --- resources/lib/cabertoss.py | 26 +++++++++++++++++++++++--- resources/lib/store.py | 5 +++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index d46d879..db48de5 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -90,10 +90,19 @@ def gather_log_files(): def copy_log_files(log_files: List) -> bool: """ - Actually copy the log files to the path in the addon settings + Copy the provided Kodi log files into a timestamped destination folder under the configured addon destination. - @param log_files: List list of log files to copy - @return bool: indicating success or failure + Detailed behavior: + - Expects log_files as a list of 2-element entries [type, path], where `type` is e.g. 'log', 'oldlog', or 'crashlog' and `path` is the source filesystem path. + - Creates a destination directory at Store.destination_path named "_Kodi_Logs_". + - For entries with type 'log' or 'oldlog', reads the source, sanitizes the content with clean_log() (because these paths may be URLs with embedded user/password details), and writes the sanitized content to a file with the same basename in the destination folder. + - For other types (e.g., crash logs), copies the source file to the destination folder unchanged. + + Parameters: + log_files (List): list of log descriptors [type, path] to copy. + + Returns: + bool: True if files were successfully copied, False otherwise. """ if not log_files: Logger.error(LANGUAGE(32025)) @@ -128,6 +137,17 @@ def copy_log_files(log_files: List) -> bool: # This is 'main'... def run(): + """ + Run the log collection and copying flow: initialize this addon's logging, load configuration, gather Kodi log files, copy them to the configured destination, notify the user, and stop this addon's logging. + + This function performs the module's main orchestration. It: + - Starts the logger for this addon's internal logging (not Kodi's general logging system) and loads addon configuration from settings. + - If no destination path is configured, shows an error notification and skips copying. + - Otherwise, notifies the user, gathers available log files, attempts to copy them to the configured destination, and notifies success (including number of files copied) or failure. + - Stops this addon's internal logging before returning. + + Side effects: starts/stops this addon's internal logging, reads configuration, performs filesystem operations (reading, sanitizing, and copying log files), and shows user notifications. Returns None. + """ Logger.start() Store.load_config_from_settings() diff --git a/resources/lib/store.py b/resources/lib/store.py index f8b5a03..e72a70d 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -24,8 +24,9 @@ def __init__(self): @staticmethod def load_config_from_settings(): """ - Load in the addon settings, at start or reload them if they have been changed - Log each setting as it is loaded + Load the addon's configuration from persistent settings. + + Reads the 'log_path' setting and assigns it to Store.destination_path, then logs the resolved path (sanitized with clean_log because these paths may be URLs with embedded user/password details). This is called at startup and when settings are reloaded; it has no return value. """ Logger.info("Loading configuration from settings") Store.destination_path = ADDON.getSetting('log_path') From d2c6e502a24ad2db8782344b0a00f7f5d26e06bd Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 15:15:12 +1000 Subject: [PATCH 10/16] Updates per CR review --- resources/lib/cabertoss.py | 47 ++++++++++++++++++++------------------ resources/lib/clean.py | 10 ++++++-- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index db48de5..fac12b9 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -4,12 +4,12 @@ from time import sleep from datetime import datetime, timedelta import socket -from typing import List +from typing import List, Tuple import xbmc import xbmcvfs -from bossanova808.constants import * -from bossanova808.utilities import * +from bossanova808.constants import LOG_PATH +from bossanova808.constants import LANGUAGE from bossanova808.logger import Logger from bossanova808.notify import Notify from resources.lib.store import Store @@ -82,13 +82,13 @@ def gather_log_files(): for crashfile in lastcrash: log_files.append(['crashlog', crashfile]) - Logger.info("Found these log files to copy:") - Logger.info(log_files) + Logger.info("Found these log files to copy (type, basename):") + Logger.info([[t, os.path.basename(p)] for t, p in log_files]) return log_files -def copy_log_files(log_files: List) -> bool: +def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: """ Copy the provided Kodi log files into a timestamped destination folder under the configured addon destination. @@ -99,7 +99,7 @@ def copy_log_files(log_files: List) -> bool: - For other types (e.g., crash logs), copies the source file to the destination folder unchanged. Parameters: - log_files (List): list of log descriptors [type, path] to copy. + log_files (List[Tuple[str, str]]): list of log descriptors [type, path] to copy. Returns: bool: True if files were successfully copied, False otherwise. @@ -118,11 +118,12 @@ def copy_log_files(log_files: List) -> bool: for file in log_files: if file[0] in ['log', 'oldlog']: Logger.info(f'Copying sanitised {file[0]} {file[1]}') - with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8') as current: + with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8', errors='replace') as current: content = current.read() sanitised = clean_log(content) - with xbmcvfs.File(os.path.join(xbmcvfs.translatePath(now_destination_path),os.path.basename(file[1])), 'w') as output: - output.write(sanitised) + dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) + with xbmcvfs.File(dest_path, 'w') as output: + output.write(sanitised.encode('utf-8')) else: Logger.info(f'Copying {file[0]} {file[1]}') if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): @@ -149,17 +150,19 @@ def run(): Side effects: starts/stops this addon's internal logging, reads configuration, performs filesystem operations (reading, sanitizing, and copying log files), and shows user notifications. Returns None. """ Logger.start() - Store.load_config_from_settings() - - if not Store.destination_path: - Notify.error(LANGUAGE(32027)) - else: - Notify.info(LANGUAGE(32030)) - log_file_list = gather_log_files() - result = copy_log_files(log_file_list) - if result: - Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") + try: + Store.load_config_from_settings() + + if not Store.destination_path: + Notify.error(LANGUAGE(32027)) else: - Notify.info(LANGUAGE(32029)) + Notify.info(LANGUAGE(32030)) + log_file_list = gather_log_files() + result = copy_log_files(log_file_list) + if result: + Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") + else: + Notify.info(LANGUAGE(32029)) # and, we're done... - Logger.stop() + finally: + Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 57b7dc4..21bccb1 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -1,14 +1,20 @@ import re -def clean_log(content): +def clean_log(content: str) -> str: """ Remove username/password details from log file content @param content: @return: """ - replaces = (('//.+?:.+?@', '//USER:PASSWORD@'), ('.+?', 'USER'), ('.+?', 'PASSWORD'),) + replaces = ( + # Replace only the credentials part between '//' and '@' + (r'(?<=//)([^/@:\s]+):([^/@\s]+)@', r'USER:PASSWORD@'), + # Replace XML username/password; keep it local to the tag + (r'(?is)[^<]*', r'USER'), + (r'(?is)[^<]*', r'PASSWORD'), + ) for pattern, repl in replaces: content = re.sub(pattern, repl, content) From 14b636fdc1688ee20a7d2e2c47c9d5d3de57b659 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 16:42:58 +1000 Subject: [PATCH 11/16] Further updates suggested by CR review --- .../resource.language.en_gb/strings.po | 4 ++++ resources/lib/cabertoss.py | 22 ++++++++++++------- resources/lib/clean.py | 2 ++ resources/lib/store.py | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 9a82849..9f6e46b 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -51,4 +51,8 @@ msgstr " msgctxt "#32030" msgid "Working..." +msgstr " + +msgctxt "#32031" +msgid "Error making directory to copy log files to" msgstr " \ No newline at end of file diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index fac12b9..e74ea3e 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -13,7 +13,7 @@ from bossanova808.logger import Logger from bossanova808.notify import Notify from resources.lib.store import Store -from resources.lib.clean import * +from resources.lib.clean import clean_log def gather_log_files(): @@ -24,9 +24,9 @@ def gather_log_files(): """ # Basic log files - log_files = [['log', os.path.join(LOG_PATH, 'kodi.log')]] + log_files = [('log', os.path.join(LOG_PATH, 'kodi.log'))] if os.path.exists(os.path.join(LOG_PATH, 'kodi.old.log')): - log_files.append(['oldlog', os.path.join(LOG_PATH, 'kodi.old.log')]) + log_files.append(('oldlog', os.path.join(LOG_PATH, 'kodi.old.log'))) # Can we find a crashlog? # @TODO - add Android support if possible..? @@ -80,7 +80,7 @@ def gather_log_files(): if lastcrash: # Logger.info(f"lastcrash {lastcrash}") for crashfile in lastcrash: - log_files.append(['crashlog', crashfile]) + log_files.append(('crashlog', crashfile)) Logger.info("Found these log files to copy (type, basename):") Logger.info([[t, os.path.basename(p)] for t, p in log_files]) @@ -114,7 +114,10 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: try: Logger.info(f'Making destination folder: {now_destination_path}') - xbmcvfs.mkdir(now_destination_path) + if not xbmcvfs.mkdir(now_destination_path): + Logger.error(f'Failed to create destination folder: {now_destination_path}') + Notify.error(LANGUAGE(32031)) + return False for file in log_files: if file[0] in ['log', 'oldlog']: Logger.info(f'Copying sanitised {file[0]} {file[1]}') @@ -122,8 +125,11 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: content = current.read() sanitised = clean_log(content) dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) - with xbmcvfs.File(dest_path, 'w') as output: - output.write(sanitised.encode('utf-8')) + f = xbmcvfs.File(dest_path, 'w') + try: + f.write(sanitised.encode('utf-8')) + finally: + f.close() else: Logger.info(f'Copying {file[0]} {file[1]}') if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): @@ -162,7 +168,7 @@ def run(): if result: Notify.info(LANGUAGE(32028) + f": {len(log_file_list)}") else: - Notify.info(LANGUAGE(32029)) + Notify.error(LANGUAGE(32029)) # and, we're done... finally: Logger.stop() diff --git a/resources/lib/clean.py b/resources/lib/clean.py index 21bccb1..a000f6c 100644 --- a/resources/lib/clean.py +++ b/resources/lib/clean.py @@ -11,6 +11,8 @@ def clean_log(content: str) -> str: replaces = ( # Replace only the credentials part between '//' and '@' (r'(?<=//)([^/@:\s]+):([^/@\s]+)@', r'USER:PASSWORD@'), + # Also scrub username only (no password) + (r'(?<=//)([^/@:\s]+)@', r'USER@'), # Replace XML username/password; keep it local to the tag (r'(?is)[^<]*', r'USER'), (r'(?is)[^<]*', r'PASSWORD'), diff --git a/resources/lib/store.py b/resources/lib/store.py index e72a70d..ebb7299 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -1,6 +1,6 @@ -from bossanova808.constants import * +from bossanova808.constants import ADDON from bossanova808.logger import Logger -from resources.lib.clean import * +from resources.lib.clean import clean_log class Store: From ba434fc0d4de1d37de98fbc96fc406cf5b1ae8a1 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:06:05 +1000 Subject: [PATCH 12/16] More CR changes --- resources/lib/cabertoss.py | 18 +++++++++++------- resources/lib/store.py | 8 +++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index e74ea3e..5b59b8b 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- import os -from datetime import datetime -from time import sleep from datetime import datetime, timedelta import socket from typing import List, Tuple @@ -16,6 +14,12 @@ from resources.lib.clean import clean_log +def _vfs_join(base: str, name: str) -> str: + if base.startswith(('special://', 'smb://', 'nfs://', 'ftp://', 'http://', 'https://')): + return base.rstrip('/') + '/' + name + return os.path.join(base, name) + + def gather_log_files(): """ Gather a list of the standard Kodi log files (Kodi.log, Kodi.old.log) and the latest crash log, if there is one. @@ -51,7 +55,7 @@ def gather_log_files(): filematch = 'kodi_' elif xbmc.getCondVisibility('system.platform.android'): Logger.info("System is Android") - Logger.info(LANGUAGE(32023)) + Logger.info(LANGUAGE(32024)) # If *ELEC, we can be more specific if xbmc.getCondVisibility('System.HasAddon(service.coreelec.settings)') or xbmc.getCondVisibility('System.HasAddon(service.libreelec.settings)'): @@ -70,7 +74,7 @@ def gather_log_files(): if three_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): items.append(os.path.join(crashlog_path, item)) - items.sort(key=lambda f: os.path.getmtime(f)) + items.sort(key=lambda f:os.path.getmtime(f)) # Windows crashlogs are a dmp and stacktrace combo... if xbmc.getCondVisibility('system.platform.windows'): lastcrash = items[-2:] @@ -114,7 +118,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: try: Logger.info(f'Making destination folder: {now_destination_path}') - if not xbmcvfs.mkdir(now_destination_path): + if not xbmcvfs.mkdirs(now_destination_path): Logger.error(f'Failed to create destination folder: {now_destination_path}') Notify.error(LANGUAGE(32031)) return False @@ -124,7 +128,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: with open(xbmcvfs.translatePath(file[1]), 'r', encoding='utf-8', errors='replace') as current: content = current.read() sanitised = clean_log(content) - dest_path = os.path.join(xbmcvfs.translatePath(now_destination_path), os.path.basename(file[1])) + dest_path = _vfs_join(now_destination_path, os.path.basename(file[1])) f = xbmcvfs.File(dest_path, 'w') try: f.write(sanitised.encode('utf-8')) @@ -132,7 +136,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: f.close() else: Logger.info(f'Copying {file[0]} {file[1]}') - if not xbmcvfs.copy(file[1], os.path.join(now_destination_path, os.path.basename(file[1]))): + if not xbmcvfs.copy(file[1], _vfs_join(now_destination_path, os.path.basename(file[1]))): return False return True diff --git a/resources/lib/store.py b/resources/lib/store.py index ebb7299..958fcfe 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -29,6 +29,8 @@ def load_config_from_settings(): Reads the 'log_path' setting and assigns it to Store.destination_path, then logs the resolved path (sanitized with clean_log because these paths may be URLs with embedded user/password details). This is called at startup and when settings are reloaded; it has no return value. """ Logger.info("Loading configuration from settings") - Store.destination_path = ADDON.getSetting('log_path') - - Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') + Store.destination_path = ADDON.get_setting('log_path') + if Store.destination_path: + Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') + else: + Logger.warning(f'No path set to toss logs to.') From 7185d8a32e8623bfe1d3ea6b369ef9c529d8d479 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:20:43 +1000 Subject: [PATCH 13/16] And more CR --- resources/lib/cabertoss.py | 17 ++++++++--------- resources/lib/store.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index 5b59b8b..8007c71 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -24,7 +24,7 @@ def gather_log_files(): """ Gather a list of the standard Kodi log files (Kodi.log, Kodi.old.log) and the latest crash log, if there is one. - @return: list of log files in form [type, path], where type is log, oldlog, or crashlog + @return: list of log files as (type, path) tuples, where type is 'log', 'oldlog', or 'crashlog' """ # Basic log files @@ -68,11 +68,10 @@ def gather_log_files(): for item in possible_crashlog_files: item_with_path = os.path.join(crashlog_path, item) if filematch in item and os.path.isfile(item_with_path): - if filematch in item: - # Don't bother with older crashlogs - three_days_ago = datetime.now() - timedelta(days=3) - if three_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): - items.append(os.path.join(crashlog_path, item)) + # Don't bother with older crashlogs + three_days_ago = datetime.now() - timedelta(days=3) + if three_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): + items.append(os.path.join(crashlog_path, item)) items.sort(key=lambda f:os.path.getmtime(f)) # Windows crashlogs are a dmp and stacktrace combo... @@ -97,9 +96,9 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: Copy the provided Kodi log files into a timestamped destination folder under the configured addon destination. Detailed behavior: - - Expects log_files as a list of 2-element entries [type, path], where `type` is e.g. 'log', 'oldlog', or 'crashlog' and `path` is the source filesystem path. + - Expects log_files as List[Tuple[str, str]] where `type` is e.g. 'log', 'oldlog', or 'crashlog' and `path` is the source filesystem path. - Creates a destination directory at Store.destination_path named "_Kodi_Logs_". - - For entries with type 'log' or 'oldlog', reads the source, sanitizes the content with clean_log() (because these paths may be URLs with embedded user/password details), and writes the sanitized content to a file with the same basename in the destination folder. + - For entries with type 'log' or 'oldlog', reads the source, sanitises the content with clean_log() (because the log content may contain URLs with embedded user/password details), and writes the sanitised content to a file with the same basename in the destination folder. - For other types (e.g., crash logs), copies the source file to the destination folder unchanged. Parameters: @@ -114,7 +113,7 @@ def copy_log_files(log_files: List[Tuple[str, str]]) -> bool: return False now_folder_name = f"{socket.gethostname()}_Kodi_Logs_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" - now_destination_path = os.path.join(Store.destination_path, now_folder_name) + now_destination_path = _vfs_join(Store.destination_path, now_folder_name) try: Logger.info(f'Making destination folder: {now_destination_path}') diff --git a/resources/lib/store.py b/resources/lib/store.py index 958fcfe..14cf1ca 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -33,4 +33,4 @@ def load_config_from_settings(): if Store.destination_path: Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') else: - Logger.warning(f'No path set to toss logs to.') + Logger.warning('No path set to toss logs to.') From 9ec822dadfd433f2237c76d21343e08b88462775 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:39:45 +1000 Subject: [PATCH 14/16] CR changes, add donfigurable crash log days --- .../resource.language.en_gb/strings.po | 22 +++++++++++-------- resources/lib/cabertoss.py | 4 ++-- resources/lib/store.py | 5 ++++- resources/settings.xml | 7 ++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 9f6e46b..2690497 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -27,32 +27,36 @@ msgstr "" msgctxt "#32024" msgid "Android crashlogs are not supported, sorry" -msgstr " +msgstr "" msgctxt "#32025" msgid "No log files found to copy ?!" -msgstr " +msgstr "" msgctxt "#32026" msgid "Error copying logs" -msgstr " +msgstr "" msgctxt "#32027" msgid "No destination path set in the addon settings!" -msgstr " +msgstr "" msgctxt "#32028" msgid "Log files copied" -msgstr " +msgstr "" msgctxt "#32029" msgid "Something went wrong, (ironically) check your logs!" -msgstr " +msgstr "" msgctxt "#32030" msgid "Working..." -msgstr " +msgstr "" msgctxt "#32031" -msgid "Error making directory to copy log files to" -msgstr " \ No newline at end of file +msgid "Error making directory to copy log files to!" +msgstr "" + +msgctxt "#32032" +msgid "Maximum days age of crashlogs to consider for copy" +msgstr "" \ No newline at end of file diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index 8007c71..7037fdb 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -69,8 +69,8 @@ def gather_log_files(): item_with_path = os.path.join(crashlog_path, item) if filematch in item and os.path.isfile(item_with_path): # Don't bother with older crashlogs - three_days_ago = datetime.now() - timedelta(days=3) - if three_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): + x_days_ago = datetime.now() - timedelta(days=Store.crashlog_max_days) + if x_days_ago < datetime.fromtimestamp(os.path.getmtime(item_with_path)): items.append(os.path.join(crashlog_path, item)) items.sort(key=lambda f:os.path.getmtime(f)) diff --git a/resources/lib/store.py b/resources/lib/store.py index 14cf1ca..e65c396 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -14,6 +14,7 @@ class Store: # Static class variables, referred to elsewhere by Store.whatever # https://docs.python.org/3/faq/programming.html#how-do-i-create-static-class-data-and-static-class-methods destination_path = None + crashlog_max_days = 3 def __init__(self): """ @@ -29,8 +30,10 @@ def load_config_from_settings(): Reads the 'log_path' setting and assigns it to Store.destination_path, then logs the resolved path (sanitized with clean_log because these paths may be URLs with embedded user/password details). This is called at startup and when settings are reloaded; it has no return value. """ Logger.info("Loading configuration from settings") - Store.destination_path = ADDON.get_setting('log_path') + Store.destination_path = ADDON.getSetting('log_path') or '' if Store.destination_path: Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') else: Logger.warning('No path set to toss logs to.') + Store.crashlog_max_days = ADDON.getSetting('crashlog_max_days') or 3 + Logger.info(f'Crashlog max days: {Store.crashlog_max_days}') diff --git a/resources/settings.xml b/resources/settings.xml index 69481d8..c4eff98 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -17,6 +17,13 @@ 32001 + + 0 + 3 + + 32005 + + From db3c7921b40155158cd935a7bc4d9652ef22cafc Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:42:13 +1000 Subject: [PATCH 15/16] Fix new settings --- resources/language/resource.language.en_gb/strings.po | 2 +- resources/settings.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 2690497..8a082c0 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -58,5 +58,5 @@ msgid "Error making directory to copy log files to!" msgstr "" msgctxt "#32032" -msgid "Maximum days age of crashlogs to consider for copy" +msgid "Max age of crashlogs to copy (days)" msgstr "" \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index c4eff98..53cdae7 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -17,11 +17,11 @@ 32001 - + 0 3 - 32005 + 32032 From ee7ce34b99118c4bdbba929ce53d78cabfe07e3a Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 20 Sep 2025 17:55:05 +1000 Subject: [PATCH 16/16] CR --- resources/lib/cabertoss.py | 4 ++-- resources/lib/store.py | 6 +++--- resources/settings.xml | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/resources/lib/cabertoss.py b/resources/lib/cabertoss.py index 7037fdb..0021409 100644 --- a/resources/lib/cabertoss.py +++ b/resources/lib/cabertoss.py @@ -20,7 +20,7 @@ def _vfs_join(base: str, name: str) -> str: return os.path.join(base, name) -def gather_log_files(): +def gather_log_files() -> List[Tuple[str, str]]: """ Gather a list of the standard Kodi log files (Kodi.log, Kodi.old.log) and the latest crash log, if there is one. @@ -29,7 +29,7 @@ def gather_log_files(): # Basic log files log_files = [('log', os.path.join(LOG_PATH, 'kodi.log'))] - if os.path.exists(os.path.join(LOG_PATH, 'kodi.old.log')): + if xbmcvfs.exists(os.path.join(LOG_PATH, 'kodi.old.log')): log_files.append(('oldlog', os.path.join(LOG_PATH, 'kodi.old.log'))) # Can we find a crashlog? diff --git a/resources/lib/store.py b/resources/lib/store.py index e65c396..478d7d0 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -13,8 +13,8 @@ class Store: # Static class variables, referred to elsewhere by Store.whatever # https://docs.python.org/3/faq/programming.html#how-do-i-create-static-class-data-and-static-class-methods - destination_path = None - crashlog_max_days = 3 + destination_path: str = '' + crashlog_max_days: int = 3 def __init__(self): """ @@ -35,5 +35,5 @@ def load_config_from_settings(): Logger.info(f'Logs will be tossed to: {clean_log(Store.destination_path)}') else: Logger.warning('No path set to toss logs to.') - Store.crashlog_max_days = ADDON.getSetting('crashlog_max_days') or 3 + Store.crashlog_max_days = int(ADDON.getSetting('crashlog_max_days')) or 3 Logger.info(f'Crashlog max days: {Store.crashlog_max_days}') diff --git a/resources/settings.xml b/resources/settings.xml index 53cdae7..938af45 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -20,6 +20,11 @@ 0 3 + + 0 + 365 + 1 + 32032