This commit is contained in:
2026-01-11 10:59:50 -08:00
parent 5f8f49c530
commit 234f7aca5c
9 changed files with 112 additions and 53 deletions

View File

@@ -292,10 +292,10 @@ class PodcastIndex(Provider):
try: try:
from SYS.config import resolve_output_dir from SYS.config import resolve_output_dir
output_dir = resolve_output_dir(self.config or {}) output_dir = resolve_output_dir(self.config or {})
except Exception: except Exception:
output_dir = Path.home() / "Downloads" import tempfile
output_dir = Path(tempfile.gettempdir())
try: try:
output_dir = Path(output_dir).expanduser() output_dir = Path(output_dir).expanduser()

View File

@@ -286,7 +286,7 @@ class Soulseek(Provider):
# NOTE: These defaults preserve existing behavior. # NOTE: These defaults preserve existing behavior.
USERNAME = "asjhkjljhkjfdsd334" USERNAME = "asjhkjljhkjfdsd334"
PASSWORD = "khhhg" PASSWORD = "khhhg"
DOWNLOAD_DIR = "./downloads" DOWNLOAD_DIR = None
MAX_WAIT_TRANSFER = 1200 MAX_WAIT_TRANSFER = 1200
def __init__(self, config: Optional[Dict[str, Any]] = None): def __init__(self, config: Optional[Dict[str, Any]] = None):
@@ -325,13 +325,17 @@ class Soulseek(Provider):
) )
return None return None
# Use tempfile directory as default if '.' or generic placeholder was passed # Use tempfile directory as default if generic path elements were passed or None.
# by a caller that didn't know better. if output_dir is None:
target_dir = Path(output_dir)
if str(target_dir) == "." or str(target_dir) == "downloads":
import tempfile import tempfile
target_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek" target_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek"
target_dir.mkdir(parents=True, exist_ok=True) else:
target_dir = Path(output_dir)
if str(target_dir) in (".", "downloads", "Downloads"):
import tempfile
target_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek"
target_dir.mkdir(parents=True, exist_ok=True)
# This cmdlet stack is synchronous; use asyncio.run for clarity. # This cmdlet stack is synchronous; use asyncio.run for clarity.
return asyncio.run( return asyncio.run(
@@ -348,12 +352,22 @@ class Soulseek(Provider):
# dedicated loop in this thread. # dedicated loop in this thread.
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
try: try:
# Re-resolve target_dir inside rescue block just in case
if output_dir is None:
import tempfile
target_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek"
else:
target_dir = Path(output_dir)
if str(target_dir) in (".", "downloads", "Downloads"):
import tempfile
target_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek"
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
return loop.run_until_complete( return loop.run_until_complete(
download_soulseek_file( download_soulseek_file(
username=username, username=username,
filename=filename, filename=filename,
output_dir=output_dir, output_dir=target_dir,
timeout=self.MAX_WAIT_TRANSFER, timeout=self.MAX_WAIT_TRANSFER,
) )
) )
@@ -642,7 +656,7 @@ class Soulseek(Provider):
async def download_soulseek_file( async def download_soulseek_file(
username: str, username: str,
filename: str, filename: str,
output_dir: Path = Path("./downloads"), output_dir: Optional[Path] = None,
timeout: int = 1200, timeout: int = 1200,
*, *,
client_username: Optional[str] = None, client_username: Optional[str] = None,
@@ -656,6 +670,10 @@ async def download_soulseek_file(
from aioslsk.transfer.model import Transfer, TransferDirection from aioslsk.transfer.model import Transfer, TransferDirection
from aioslsk.transfer.state import TransferState from aioslsk.transfer.state import TransferState
if output_dir is None:
import tempfile
output_dir = Path(tempfile.gettempdir()) / "Medios" / "Soulseek"
output_dir = Path(output_dir) output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import re import re
import tempfile
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from SYS.logger import log from SYS.logger import log
@@ -22,6 +23,12 @@ def global_config() -> List[Dict[str, Any]]:
"label": "Debug Output", "label": "Debug Output",
"default": "false", "default": "false",
"choices": ["true", "false"] "choices": ["true", "false"]
},
{
"key": "auto_update",
"label": "Auto-Update",
"default": "true",
"choices": ["true", "false"]
} }
] ]
@@ -185,7 +192,7 @@ def parse_conf_text(text: str, *, base: Optional[Dict[str, Any]] = None) -> Dict
"""Parse a lightweight .conf format into the app's config dict. """Parse a lightweight .conf format into the app's config dict.
Supported patterns: Supported patterns:
- Top-level key/value: temp="C:\\Users\\Me\\Downloads" - Top-level key/value: temp="./temp"
- Sections: [store=folder] + name/path lines - Sections: [store=folder] + name/path lines
- Sections: [store=hydrusnetwork] + name/access key/url lines - Sections: [store=hydrusnetwork] + name/access key/url lines
- Sections: [provider=OpenLibrary] + email/password lines - Sections: [provider=OpenLibrary] + email/password lines
@@ -447,7 +454,7 @@ def resolve_output_dir(config: Dict[str, Any]) -> Path:
Priority: Priority:
1. config["temp"] - explicitly set temp/output directory 1. config["temp"] - explicitly set temp/output directory
2. config["outfile"] - fallback to outfile setting 2. config["outfile"] - fallback to outfile setting
3. Home/Videos - safe user directory fallback 3. System Temp - default fallback directory
Returns: Returns:
Path to output directory Path to output directory
@@ -471,8 +478,8 @@ def resolve_output_dir(config: Dict[str, Any]) -> Path:
except Exception: except Exception:
pass pass
# Fallback to user's Videos directory # Fallback to system temp directory
return Path.home() / "Videos" return Path(tempfile.gettempdir())
def get_local_storage_path(config: Dict[str, Any]) -> Optional[Path]: def get_local_storage_path(config: Dict[str, Any]) -> Optional[Path]:

View File

@@ -65,7 +65,7 @@ class CmdletArg:
Example: Example:
# For STORAGE arg with a handler # For STORAGE arg with a handler
storage_path = SharedArgs.STORAGE.resolve('local') # Returns Path.home() / "Videos" storage_path = SharedArgs.STORAGE.resolve('local') # Returns Path(tempfile.gettempdir())
""" """
if self.handler is not None and callable(self.handler): if self.handler is not None and callable(self.handler):
return self.handler(value) return self.handler(value)
@@ -354,47 +354,29 @@ class SharedArgs:
) -> Path: ) -> Path:
"""Resolve a storage location name to a filesystem Path. """Resolve a storage location name to a filesystem Path.
Maps storage identifiers (hydrus, local, ftp) to their actual Maps storage identifiers to their actual filesystem paths.
filesystem paths. This is the single source of truth for storage location resolution. This project has been refactored to use system temporary directories
Note: 0x0.st is now accessed via file providers (-provider 0x0), not storage. for all staging/downloads by default.
Args: Args:
storage_value: One of 'hydrus', 'local', 'ftp', or None storage_value: One of 'hydrus', 'local', 'ftp', or None (currently unified to temp)
default: Path to return if storage_value is None (defaults to Videos) default: Path to return if storage_value is None (defaults to temp directory)
Returns: Returns:
Resolved Path object for the storage location Resolved Path object for the storage location (typically system temp)
Raises:
ValueError: If storage_value is not a recognized storage type
Example: Example:
# In a cmdlet: # In a cmdlet:
storage_path = SharedArgs.resolve_storage(parsed.storage) storage_path = SharedArgs.resolve_storage(parsed.get('storage'))
# Returns Path(tempfile.gettempdir())
# With defaults:
path = SharedArgs.resolve_storage(None) # Returns home/Videos
path = SharedArgs.resolve_storage('local') # Returns home/Videos
path = SharedArgs.resolve_storage('hydrus') # Returns home/.hydrus/client_files
""" """
storage_map = { # We no longer maintain a hardcoded map for 'hydrus' (~/.hydrus) or 'local' (~/Videos).
"local": Path.home() / "Videos", # Everything defaults to the system temp directory unless a specific default is provided.
"hydrus": Path.home() / ".hydrus" / "client_files", # This ensures environment independence.
"ftp": Path.home() / "FTP", if default is not None:
"matrix": Path.home() / "Matrix", # Placeholder, not used for upload path return default
}
if storage_value is None: return Path(tempfile.gettempdir())
return default or (Path.home() / "Videos")
storage_lower = storage_value.lower()
if storage_lower not in storage_map:
raise ValueError(
f"Unknown storage location '{storage_value}'. "
f"Must be one of: {', '.join(storage_map.keys())}"
)
return storage_map[storage_lower]
@classmethod @classmethod
def get(cls, name: str) -> Optional[CmdletArg]: def get(cls, name: str) -> Optional[CmdletArg]:

View File

@@ -3946,7 +3946,7 @@ class Download_File(Cmdlet):
final_output_dir = resolve_output_dir(config) final_output_dir = resolve_output_dir(config)
except Exception: except Exception:
import tempfile import tempfile
final_output_dir = Path(tempfile.gettempdir()) / "Medios-Macina" final_output_dir = Path(tempfile.gettempdir())
debug(f"Using default directory: {final_output_dir}") debug(f"Using default directory: {final_output_dir}")

View File

@@ -30,7 +30,7 @@ class Get_File(sh.Cmdlet):
super().__init__( super().__init__(
name="get-file", name="get-file",
summary="Export file to local path", summary="Export file to local path",
usage="@1 | get-file -path C:\\Downloads", usage="@1 | get-file -path ./output",
arg=[ arg=[
sh.SharedArgs.QUERY, sh.SharedArgs.QUERY,
sh.SharedArgs.STORE, sh.SharedArgs.STORE,

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
import hashlib import hashlib
import sys import sys
import tempfile
import time import time
from datetime import datetime from datetime import datetime
import httpx import httpx
@@ -948,9 +949,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
except Exception: except Exception:
pass pass
# Default: User's Videos directory # Default: system temp directory
if screenshot_dir is None: if screenshot_dir is None:
screenshot_dir = Path.home() / "Videos" screenshot_dir = Path(tempfile.gettempdir())
debug(f"[screen_shot] Using default directory: {screenshot_dir}") debug(f"[screen_shot] Using default directory: {screenshot_dir}")
ensure_directory(screenshot_dir) ensure_directory(screenshot_dir)

View File

@@ -47,7 +47,7 @@ CMDLET = Cmdlet(
CmdletArg( CmdletArg(
"-outdir", "-outdir",
description= description=
"Output directory for the clip (defaults to source folder for local files; otherwise uses config temp/videos).", "Output directory for the clip (defaults to source folder for local files; otherwise uses system temp).",
), ),
CmdletArg( CmdletArg(
"-delete", "-delete",

View File

@@ -1094,6 +1094,26 @@ def main() -> int:
ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args) ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path $repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
# Automatically check for updates if this is a git repository
if (Test-Path (Join-Path $repo ".git")) {
try {
if (-not $env:MM_NO_UPDATE) {
$conf = Join-Path $repo "config.conf"
$skip = $false
if (Test-Path $conf) {
if ((Get-Content $conf | Select-String "auto_update\s*=\s*(false|no|off|0)") -ne $null) {
$skip = $true
}
}
if (-not $skip) {
Write-Host "Checking for updates..." -ForegroundColor Gray
git -C "$repo" pull --ff-only --quiet
}
}
} catch {}
}
$venv = Join-Path $repo '.venv' $venv = Join-Path $repo '.venv'
$py = Join-Path $venv 'Scripts\python.exe' $py = Join-Path $venv 'Scripts\python.exe'
if (Test-Path $py) { if (Test-Path $py) {
@@ -1139,6 +1159,22 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
"@echo off\n" "@echo off\n"
"setlocal enabledelayedexpansion\n" "setlocal enabledelayedexpansion\n"
f'set "REPO={repo_bat_str}"\n' f'set "REPO={repo_bat_str}"\n'
"\n"
"# Automatically check for updates if this is a git repository\n"
"if not defined MM_NO_UPDATE (\n"
" if exist \"!REPO!\\.git\" (\n"
" set \"AUTO_UPDATE=true\"\n"
" if exist \"!REPO!\\config.conf\" (\n"
" findstr /i /r \"auto_update.*=.*false auto_update.*=.*no auto_update.*=.*off auto_update.*=.*0\" \"!REPO!\\config.conf\" >nul 2>&1\n"
" if !errorlevel! == 0 set \"AUTO_UPDATE=false\"\n"
" )\n"
" if \"!AUTO_UPDATE!\" == \"true\" (\n"
" echo Checking for updates...\n"
" git -C \"!REPO!\" pull --ff-only --quiet\n"
" )\n"
" )\n"
")\n"
"\n"
"set \"VENV=!REPO!\\.venv\"\n" "set \"VENV=!REPO!\\.venv\"\n"
"set \"PY=!VENV!\\Scripts\\python.exe\"\n" "set \"PY=!VENV!\\Scripts\\python.exe\"\n"
"set \"ENTRY=!REPO!\\scripts\\cli_entry.py\"\n" "set \"ENTRY=!REPO!\\scripts\\cli_entry.py\"\n"
@@ -1299,6 +1335,21 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
" done\n" " done\n"
' echo "MM_DEBUG: end diagnostics" >&2\n' ' echo "MM_DEBUG: end diagnostics" >&2\n'
"fi\n" "fi\n"
"\n"
"# Automatically check for updates if this is a git repository\n"
'if [ -z "${MM_NO_UPDATE:-}" ] && [ -d "$REPO/.git" ] && command -v git >/dev/null 2>&1; then\n'
' AUTO_UPDATE="true"\n'
' if [ -f "$REPO/config.conf" ]; then\n'
' if grep -qiE \'auto_update\s*=\s*(false|no|off|0)\' "$REPO/config.conf"; then\n'
' AUTO_UPDATE="false"\n'
' fi\n'
' fi\n'
' if [ "$AUTO_UPDATE" = "true" ]; then\n'
' echo "Checking for updates..."\n'
' git -C "$REPO" pull --ff-only --quiet || true\n'
' fi\n'
"fi\n"
"\n"
"# Use -m scripts.cli_entry directly instead of pip-generated wrapper to avoid entry point issues\n" "# Use -m scripts.cli_entry directly instead of pip-generated wrapper to avoid entry point issues\n"
"# Prefer venv's python3, then venv's python\n" "# Prefer venv's python3, then venv's python\n"
'if [ -x "$VENV/bin/python3" ]; then\n' 'if [ -x "$VENV/bin/python3" ]; then\n'