diff --git a/ytsage/core/ytsage_downloader.py b/ytsage/core/ytsage_downloader.py index 7ffa0e7..820b12b 100644 --- a/ytsage/core/ytsage_downloader.py +++ b/ytsage/core/ytsage_downloader.py @@ -112,18 +112,23 @@ class DownloadThread(QThread): self.last_file_path: Optional[str] = None # Initialize full file path storage 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.download_files: Set[Path] = set() # Every destination path this download wrote to 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: pattern = re.compile(r"\.f\d+\.") # Pattern to match format codes like .f243. - for file_path in self.path.iterdir(): - if file_path.suffix == ".part" or pattern.search(file_path.name): - self._safe_delete_with_retry(file_path) + for dest in self.download_files: + 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) except Exception as 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: """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") # --- 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] = { - 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: deleted_count[1] += safe_delete(path=subtitle_file) @@ -619,6 +631,7 @@ class DownloadThread(QThread): filepath = dest_match.group(1).strip() self.current_filename = Path(filepath).name 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 # 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() self.current_filename = Path(dest_path).name self.last_file_path = dest_path + self.download_files.add(Path(dest_path)) logger.debug(f"Captured destination filename: {self.current_filename}") elif "Downloading API JSON" in line: self.status_signal.emit(_("download.processing_playlist")) @@ -771,6 +785,7 @@ class DownloadThread(QThread): merged_filepath = merger_match.group(1).strip() self.current_filename = Path(merged_filepath).name self.last_file_path = merged_filepath + self.download_files.add(Path(merged_filepath)) logger.debug(f"Updated to merged filename: {self.current_filename}") elif "SponsorBlock" in line: self.status_signal.emit(_("download.removing_sponsor_segments"))