Scope cleanup routines to files created by the current download
cleanup_partial_files() deleted every *.part and *.fNNN.* file in the whole download directory, and cleanup_subtitle_files() deleted any new .vtt/.srt under it recursively - including files belonging to other applications (e.g. a browser's own .part downloads in ~/Downloads). Track every destination path yt-dlp reports for this download and restrict both cleanup passes to those files and their .part/.ytdl siblings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,18 +112,23 @@ class DownloadThread(QThread):
|
|||||||
self.last_file_path: Optional[str] = None # Initialize full file path storage
|
self.last_file_path: Optional[str] = None # Initialize full file path storage
|
||||||
self.subtitle_files: List[str] = [] # Track subtitle files that are created
|
self.subtitle_files: List[str] = [] # Track subtitle files that are created
|
||||||
self.initial_subtitle_files: Set[Path] = set() # Track initial subtitle files before download
|
self.initial_subtitle_files: Set[Path] = set() # Track initial subtitle files before download
|
||||||
|
self.download_files: Set[Path] = set() # Every destination path this download wrote to
|
||||||
|
|
||||||
def cleanup_partial_files(self) -> None:
|
def cleanup_partial_files(self) -> None:
|
||||||
"""Delete any partial files including .part and unmerged format-specific files"""
|
"""Delete partial files (.part/.ytdl and unmerged .fNNN. streams), but only
|
||||||
|
those belonging to destinations this download actually wrote — the download
|
||||||
|
directory may contain unrelated files from other applications."""
|
||||||
try:
|
try:
|
||||||
pattern = re.compile(r"\.f\d+\.") # Pattern to match format codes like .f243.
|
pattern = re.compile(r"\.f\d+\.") # Pattern to match format codes like .f243.
|
||||||
for file_path in self.path.iterdir():
|
for dest in self.download_files:
|
||||||
if file_path.suffix == ".part" or pattern.search(file_path.name):
|
candidates = [dest.with_name(dest.name + ".part"), dest.with_name(dest.name + ".ytdl")]
|
||||||
|
if dest.suffix == ".part" or pattern.search(dest.name):
|
||||||
|
candidates.append(dest)
|
||||||
|
for file_path in candidates:
|
||||||
|
if file_path.exists():
|
||||||
self._safe_delete_with_retry(file_path)
|
self._safe_delete_with_retry(file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error cleaning partial files: {e}")
|
logger.exception(f"Error cleaning partial files: {e}")
|
||||||
# Don't emit error signal for cleanup issues to avoid crashing the thread
|
|
||||||
logger.error(f"Error cleaning partial files: {e}")
|
|
||||||
|
|
||||||
def _safe_delete_with_retry(self, file_path: Path, max_retries: int = 5, delay: float = 2.0) -> None:
|
def _safe_delete_with_retry(self, file_path: Path, max_retries: int = 5, delay: float = 2.0) -> None:
|
||||||
"""Safely delete a file with retry mechanism for file locking issues across platforms"""
|
"""Safely delete a file with retry mechanism for file locking issues across platforms"""
|
||||||
@@ -219,8 +224,15 @@ class DownloadThread(QThread):
|
|||||||
logger.debug(f"Deleted {deleted_count[0]} of {len(self.subtitle_files)} tracked subtitle files")
|
logger.debug(f"Deleted {deleted_count[0]} of {len(self.subtitle_files)} tracked subtitle files")
|
||||||
|
|
||||||
# --- Method 2: Delete new subtitle files not in initial set ---
|
# --- Method 2: Delete new subtitle files not in initial set ---
|
||||||
|
# Only touch subtitles belonging to files this download wrote; the
|
||||||
|
# directory may contain subtitle files from other processes.
|
||||||
|
download_stems = {p.stem for p in self.download_files} | {Path(f).stem for f in self.subtitle_files or []}
|
||||||
new_subtitle_files: Set[Path] = {
|
new_subtitle_files: Set[Path] = {
|
||||||
f for f in Path(self.path).rglob("*") if f.suffix in [".vtt", ".srt"] and f not in self.initial_subtitle_files
|
f
|
||||||
|
for f in Path(self.path).rglob("*")
|
||||||
|
if f.suffix in [".vtt", ".srt"]
|
||||||
|
and f not in self.initial_subtitle_files
|
||||||
|
and any(f.name.startswith(stem) for stem in download_stems if stem)
|
||||||
}
|
}
|
||||||
for subtitle_file in new_subtitle_files:
|
for subtitle_file in new_subtitle_files:
|
||||||
deleted_count[1] += safe_delete(path=subtitle_file)
|
deleted_count[1] += safe_delete(path=subtitle_file)
|
||||||
@@ -619,6 +631,7 @@ class DownloadThread(QThread):
|
|||||||
filepath = dest_match.group(1).strip()
|
filepath = dest_match.group(1).strip()
|
||||||
self.current_filename = Path(filepath).name
|
self.current_filename = Path(filepath).name
|
||||||
self.last_file_path = filepath # Store the full path for later cleanup
|
self.last_file_path = filepath # Store the full path for later cleanup
|
||||||
|
self.download_files.add(Path(filepath))
|
||||||
logger.debug(f"Extracted filename: {self.current_filename}") # DEBUG
|
logger.debug(f"Extracted filename: {self.current_filename}") # DEBUG
|
||||||
|
|
||||||
# Check if this is an audio-only download by looking in the previous lines
|
# Check if this is an audio-only download by looking in the previous lines
|
||||||
@@ -711,6 +724,7 @@ class DownloadThread(QThread):
|
|||||||
dest_path = match.group(1).strip()
|
dest_path = match.group(1).strip()
|
||||||
self.current_filename = Path(dest_path).name
|
self.current_filename = Path(dest_path).name
|
||||||
self.last_file_path = dest_path
|
self.last_file_path = dest_path
|
||||||
|
self.download_files.add(Path(dest_path))
|
||||||
logger.debug(f"Captured destination filename: {self.current_filename}")
|
logger.debug(f"Captured destination filename: {self.current_filename}")
|
||||||
elif "Downloading API JSON" in line:
|
elif "Downloading API JSON" in line:
|
||||||
self.status_signal.emit(_("download.processing_playlist"))
|
self.status_signal.emit(_("download.processing_playlist"))
|
||||||
@@ -771,6 +785,7 @@ class DownloadThread(QThread):
|
|||||||
merged_filepath = merger_match.group(1).strip()
|
merged_filepath = merger_match.group(1).strip()
|
||||||
self.current_filename = Path(merged_filepath).name
|
self.current_filename = Path(merged_filepath).name
|
||||||
self.last_file_path = merged_filepath
|
self.last_file_path = merged_filepath
|
||||||
|
self.download_files.add(Path(merged_filepath))
|
||||||
logger.debug(f"Updated to merged filename: {self.current_filename}")
|
logger.debug(f"Updated to merged filename: {self.current_filename}")
|
||||||
elif "SponsorBlock" in line:
|
elif "SponsorBlock" in line:
|
||||||
self.status_signal.emit(_("download.removing_sponsor_segments"))
|
self.status_signal.emit(_("download.removing_sponsor_segments"))
|
||||||
|
|||||||
Reference in New Issue
Block a user