use media.trash for unused media deletion as well

This commit is contained in:
Damien Elmes 2020-02-11 17:30:10 +10:00
parent 4fc898ec1e
commit 4c0f216df2
6 changed files with 68 additions and 45 deletions

View file

@ -28,6 +28,7 @@ message BackendInput {
AddFileToMediaFolderIn add_file_to_media_folder = 26;
SyncMediaIn sync_media = 27;
Empty check_media = 28;
TrashMediaFilesIn trash_media_files = 29;
}
}
@ -46,6 +47,7 @@ message BackendOutput {
string add_file_to_media_folder = 26;
Empty sync_media = 27;
MediaCheckOut check_media = 28;
Empty trash_media_files = 29;
BackendError error = 2047;
}
@ -270,4 +272,8 @@ message MediaCheckOut {
repeated string dirs = 3;
repeated string oversize = 4;
map<string,string> renamed = 5;
}
message TrashMediaFilesIn {
repeated string fnames = 1;
}

View file

@ -111,6 +111,10 @@ class MediaManager:
def have(self, fname: str) -> bool:
return os.path.exists(os.path.join(self.dir(), fname))
def trash_files(self, fnames: List[str]) -> None:
"Move provided files to the trash."
self.col.backend.trash_media_files(fnames)
# String manipulation
##########################################################################

View file

@ -312,3 +312,8 @@ class RustBackend:
return self._run_command(
pb.BackendInput(check_media=pb.Empty()), release_gil=True,
).check_media
def trash_media_files(self, fnames: List[str]) -> None:
self._run_command(
pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames))
)

View file

@ -3,11 +3,10 @@
from __future__ import annotations
import itertools
import time
from concurrent.futures import Future
from typing import Optional
from send2trash import send2trash
from typing import Iterable, List, Optional, TypeVar
import aqt
from anki import hooks
@ -16,6 +15,17 @@ from anki.rsbackend import Interrupted, MediaCheckOutput, Progress, ProgressKind
from aqt.qt import *
from aqt.utils import askUser, restoreGeom, saveGeom, showText, tooltip
T = TypeVar("T")
def chunked_list(l: Iterable[T], n: int) -> Iterable[List[T]]:
l = iter(l)
while True:
res = list(itertools.islice(l, n))
if not res:
return
yield res
def check_media_db(mw: aqt.AnkiQt) -> None:
c = MediaChecker(mw)
@ -74,11 +84,12 @@ class MediaChecker:
layout.addWidget(text)
box = QDialogButtonBox(QDialogButtonBox.Close)
layout.addWidget(box)
if output.unused:
b = QPushButton(_("Delete Unused Files"))
b.setAutoDefault(False)
box.addButton(b, QDialogButtonBox.ActionRole)
b.clicked.connect(lambda c, u=output.unused, d=diag: deleteUnused(self.mw, u, d)) # type: ignore
box.addButton(b, QDialogButtonBox.RejectRole)
b.clicked.connect(lambda c: self._on_trash_files(output.unused)) # type: ignore
if output.missing:
if any(map(lambda x: x.startswith("latex-"), output.missing)):
@ -120,6 +131,32 @@ class MediaChecker:
self.mw.progress.update(_("Checked {}...").format(count))
return True
def _on_trash_files(self, fnames: List[str]):
if not askUser(_("Delete unused media?")):
return
self.progress_dialog = self.mw.progress.start()
last_progress = time.time()
remaining = len(fnames)
try:
for chunk in chunked_list(fnames, 25):
self.mw.col.media.trash_files(chunk)
remaining -= len(chunk)
if time.time() - last_progress >= 0.3:
label = (
ngettext(
"%d file remaining...", "%d files remaining...", remaining,
)
% remaining
)
self.mw.progress.update(label)
finally:
self.mw.progress.finish()
self.progress_dialog = None
tooltip(_("Files moved to trash."))
def describe_output(output: MediaCheckOutput) -> str:
buf = []
@ -172,41 +209,3 @@ def describe_output(output: MediaCheckOutput) -> str:
buf.append("")
return "\n".join(buf)
def deleteUnused(self, unused, diag):
if not askUser(_("Delete unused media?")):
return
mdir = self.col.media.dir()
self.progress.start(immediate=True)
try:
lastProgress = 0
for c, f in enumerate(unused):
path = os.path.join(mdir, f)
if os.path.exists(path):
send2trash(path)
now = time.time()
if now - lastProgress >= 0.3:
numberOfRemainingFilesToBeDeleted = len(unused) - c
lastProgress = now
label = (
ngettext(
"%d file remaining...",
"%d files remaining...",
numberOfRemainingFilesToBeDeleted,
)
% numberOfRemainingFilesToBeDeleted
)
self.progress.update(label)
finally:
self.progress.finish()
# caller must not pass in empty list
# pylint: disable=undefined-loop-variable
numberOfFilesDeleted = c + 1
tooltip(
ngettext("Deleted %d file.", "Deleted %d files.", numberOfFilesDeleted)
% numberOfFilesDeleted
)
diag.close()

View file

@ -7,6 +7,7 @@ use crate::backend_proto::{Empty, RenderedTemplateReplacement, SyncMediaIn};
use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind};
use crate::latex::{extract_latex, ExtractedLatex};
use crate::media::check::MediaChecker;
use crate::media::files::remove_files;
use crate::media::sync::MediaSyncProgress;
use crate::media::MediaManager;
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
@ -25,7 +26,7 @@ pub type ProtoProgressCallback = Box<dyn Fn(Vec<u8>) -> bool + Send>;
pub struct Backend {
#[allow(dead_code)]
col_path: PathBuf,
media_folder: String,
media_folder: PathBuf,
media_db: String,
progress_callback: Option<ProtoProgressCallback>,
}
@ -187,6 +188,10 @@ impl Backend {
OValue::SyncMedia(Empty {})
}
Value::CheckMedia(_) => OValue::CheckMedia(self.check_media()?),
Value::TrashMediaFiles(input) => {
self.remove_media_files(&input.fnames)?;
OValue::TrashMediaFiles(Empty {})
}
})
}
@ -363,6 +368,10 @@ impl Backend {
oversize: output.oversize,
})
}
fn remove_media_files(&self, fnames: &[String]) -> Result<()> {
remove_files(&self.media_folder, fnames)
}
}
fn ords_hash_to_set(ords: HashSet<u16>) -> Vec<u32> {

View file

@ -288,7 +288,7 @@ pub(super) fn mtime_as_i64<P: AsRef<Path>>(path: P) -> io::Result<i64> {
.as_secs() as i64)
}
pub(super) fn remove_files<S>(media_folder: &Path, files: &[S]) -> Result<()>
pub fn remove_files<S>(media_folder: &Path, files: &[S]) -> Result<()>
where
S: AsRef<str> + std::fmt::Debug,
{