Merge branch 'main' into fix-cargo-build-implicit-deps

This commit is contained in:
Abdo 2025-12-16 19:36:19 +03:00 committed by GitHub
commit 962468e39e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 60 additions and 29 deletions

View file

@ -234,7 +234,7 @@ class DeckBrowser:
if node.collapsed: if node.collapsed:
prefix = "+" prefix = "+"
else: else:
prefix = "-" prefix = ""
def indent() -> str: def indent() -> str:
return " " * 6 * (node.level - 1) return " " * 6 * (node.level - 1)

View file

@ -14,7 +14,7 @@ from markdown import markdown
import aqt import aqt
from anki.collection import HelpPage from anki.collection import HelpPage
from anki.errors import BackendError, Interrupted from anki.errors import BackendError, CardTypeError, Interrupted
from anki.utils import is_win from anki.utils import is_win
from aqt.addons import AddonManager, AddonMeta from aqt.addons import AddonManager, AddonMeta
from aqt.qt import * from aqt.qt import *
@ -36,6 +36,14 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
global _mbox global _mbox
error_lines = [] error_lines = []
help_page = HelpPage.TROUBLESHOOTING help_page = HelpPage.TROUBLESHOOTING
# default to PlainText
text_format = Qt.TextFormat.PlainText
# set CardTypeError messages as rich text to allow HTML formatting
if isinstance(exception, CardTypeError):
text_format = Qt.TextFormat.RichText
if isinstance(exception, BackendError): if isinstance(exception, BackendError):
if exception.context: if exception.context:
error_lines.append(exception.context) error_lines.append(exception.context)
@ -51,7 +59,7 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
) )
error_text = "\n".join(error_lines) error_text = "\n".join(error_lines)
print(error_lines) print(error_lines)
_mbox = _init_message_box(str(exception), error_text, help_page) _mbox = _init_message_box(str(exception), error_text, help_page, text_format)
_mbox.show() _mbox.show()
@ -171,7 +179,10 @@ if not os.environ.get("DEBUG"):
def _init_message_box( def _init_message_box(
user_text: str, debug_text: str, help_page=HelpPage.TROUBLESHOOTING user_text: str,
debug_text: str,
help_page=HelpPage.TROUBLESHOOTING,
text_format=Qt.TextFormat.PlainText,
): ):
global _mbox global _mbox
@ -179,7 +190,7 @@ def _init_message_box(
_mbox.setWindowTitle("Anki") _mbox.setWindowTitle("Anki")
_mbox.setText(user_text) _mbox.setText(user_text)
_mbox.setIcon(QMessageBox.Icon.Warning) _mbox.setIcon(QMessageBox.Icon.Warning)
_mbox.setTextFormat(Qt.TextFormat.PlainText) _mbox.setTextFormat(text_format)
def show_help(): def show_help():
openHelp(help_page) openHelp(help_page)

View file

@ -17,6 +17,7 @@ use crate::import_export::package::media::SafeMediaEntry;
use crate::import_export::ImportProgress; use crate::import_export::ImportProgress;
use crate::media::files::add_hash_suffix_to_file_stem; use crate::media::files::add_hash_suffix_to_file_stem;
use crate::media::files::sha1_of_reader; use crate::media::files::sha1_of_reader;
use crate::media::Checksums;
use crate::prelude::*; use crate::prelude::*;
use crate::progress::ThrottlingProgressHandler; use crate::progress::ThrottlingProgressHandler;
@ -75,7 +76,7 @@ impl Context<'_> {
fn prepare_media( fn prepare_media(
media_entries: Vec<SafeMediaEntry>, media_entries: Vec<SafeMediaEntry>,
archive: &mut ZipArchive<File>, archive: &mut ZipArchive<File>,
existing_sha1s: &HashMap<String, Sha1Hash>, existing_sha1s: &Checksums,
progress: &mut ThrottlingProgressHandler<ImportProgress>, progress: &mut ThrottlingProgressHandler<ImportProgress>,
) -> Result<MediaUseMap> { ) -> Result<MediaUseMap> {
let mut media_map = MediaUseMap::default(); let mut media_map = MediaUseMap::default();

View file

@ -173,7 +173,9 @@ pub fn add_data_to_folder_uniquely<'a, P>(
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let normalized_name = normalize_filename(desired_name); // force lowercase to account for case-insensitive filesystems
// but not within normalize_filename, for existing media refs
let normalized_name: Cow<_> = normalize_filename(desired_name).to_lowercase().into();
let mut target_path = folder.as_ref().join(normalized_name.as_ref()); let mut target_path = folder.as_ref().join(normalized_name.as_ref());
@ -496,8 +498,14 @@ mod test {
"test.mp3" "test.mp3"
); );
// different contents // different contents, filenames differ only by case
let h2 = sha1_of_data(b"hello1"); let h2 = sha1_of_data(b"hello1");
assert_eq!(
add_data_to_folder_uniquely(dpath, "Test.mp3", b"hello1", h2).unwrap(),
"test-88fdd585121a4ccb3d1540527aee53a77c77abb8.mp3"
);
// same contents, filenames differ only by case
assert_eq!( assert_eq!(
add_data_to_folder_uniquely(dpath, "test.mp3", b"hello1", h2).unwrap(), add_data_to_folder_uniquely(dpath, "test.mp3", b"hello1", h2).unwrap(),
"test-88fdd585121a4ccb3d1540527aee53a77c77abb8.mp3" "test-88fdd585121a4ccb3d1540527aee53a77c77abb8.mp3"

View file

@ -6,7 +6,6 @@ pub mod files;
mod service; mod service;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -22,6 +21,7 @@ use crate::progress::ThrottlingProgressHandler;
use crate::sync::http_client::HttpSyncClient; use crate::sync::http_client::HttpSyncClient;
use crate::sync::login::SyncAuth; use crate::sync::login::SyncAuth;
use crate::sync::media::database::client::changetracker::ChangeTracker; use crate::sync::media::database::client::changetracker::ChangeTracker;
pub use crate::sync::media::database::client::Checksums;
use crate::sync::media::database::client::MediaDatabase; use crate::sync::media::database::client::MediaDatabase;
use crate::sync::media::database::client::MediaEntry; use crate::sync::media::database::client::MediaEntry;
use crate::sync::media::progress::MediaSyncProgress; use crate::sync::media::progress::MediaSyncProgress;
@ -157,7 +157,7 @@ impl MediaManager {
pub fn all_checksums_after_checking( pub fn all_checksums_after_checking(
&self, &self,
progress: impl FnMut(usize) -> bool, progress: impl FnMut(usize) -> bool,
) -> Result<HashMap<String, Sha1Hash>> { ) -> Result<Checksums> {
ChangeTracker::new(&self.media_folder, progress).register_changes(&self.db)?; ChangeTracker::new(&self.media_folder, progress).register_changes(&self.db)?;
self.db.all_registered_checksums() self.db.all_registered_checksums()
} }
@ -176,7 +176,7 @@ impl MediaManager {
/// All checksums without registering changes first. /// All checksums without registering changes first.
#[cfg(test)] #[cfg(test)]
pub(crate) fn all_checksums_as_is(&self) -> HashMap<String, [u8; 20]> { pub(crate) fn all_checksums_as_is(&self) -> Checksums {
self.db.all_registered_checksums().unwrap() self.db.all_registered_checksums().unwrap()
} }
} }

View file

@ -18,6 +18,20 @@ use crate::prelude::*;
pub mod changetracker; pub mod changetracker;
pub struct Checksums(HashMap<String, Sha1Hash>);
impl Checksums {
// case-fold filenames when checking files to be imported
// to account for case-insensitive filesystems
pub fn get(&self, key: impl AsRef<str>) -> Option<&Sha1Hash> {
self.0.get(key.as_ref().to_lowercase().as_str())
}
pub fn contains_key(&self, key: impl AsRef<str>) -> bool {
self.get(key).is_some()
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct MediaEntry { pub struct MediaEntry {
pub fname: String, pub fname: String,
@ -175,11 +189,12 @@ delete from media where fname=?",
} }
/// Returns all filenames and checksums, where the checksum is not null. /// Returns all filenames and checksums, where the checksum is not null.
pub(crate) fn all_registered_checksums(&self) -> error::Result<HashMap<String, Sha1Hash>> { pub(crate) fn all_registered_checksums(&self) -> error::Result<Checksums> {
self.db self.db
.prepare("SELECT fname, csum FROM media WHERE csum IS NOT NULL")? .prepare("SELECT fname, csum FROM media WHERE csum IS NOT NULL")?
.query_and_then([], row_to_name_and_checksum)? .query_and_then([], row_to_name_and_checksum)?
.collect() .collect::<error::Result<_>>()
.map(Checksums)
} }
pub(crate) fn force_resync(&self) -> error::Result<()> { pub(crate) fn force_resync(&self) -> error::Result<()> {

View file

@ -202,7 +202,7 @@ fn sveltekit_temp_file(path: &str) -> bool {
} }
fn check_cargo_deny() -> Result<()> { fn check_cargo_deny() -> Result<()> {
Command::run("cargo install cargo-deny@0.18.3")?; Command::run("cargo install cargo-deny@0.18.6")?;
Command::run("cargo deny check")?; Command::run("cargo deny check")?;
Ok(()) Ok(())
} }

View file

@ -4,7 +4,12 @@
import { getRange, getSelection } from "./cross-browser"; import { getRange, getSelection } from "./cross-browser";
function wrappedExceptForWhitespace(text: string, front: string, back: string): string { function wrappedExceptForWhitespace(text: string, front: string, back: string): string {
const match = text.match(/^(\s*)([^]*?)(\s*)$/)!; const normalizedText = text
.replace(/&nbsp;/g, " ")
.replace(/&#160;/g, " ")
.replace(/\u00A0/g, " ");
const match = normalizedText.match(/^(\s*)([^]*?)(\s*)$/)!;
return match[1] + front + match[2] + back + match[3]; return match[1] + front + match[2] + back + match[3];
} }

View file

@ -37,7 +37,9 @@ export const addOrUpdateNote = async function(
backExtra, backExtra,
tags, tags,
}); });
showResult(mode.noteId, result, noteCount); if (result.note) {
showResult(mode.noteId, result, noteCount);
}
} else { } else {
const result = await addImageOcclusionNote({ const result = await addImageOcclusionNote({
// IOCloningMode is not used on mobile // IOCloningMode is not used on mobile
@ -55,23 +57,12 @@ export const addOrUpdateNote = async function(
// show toast message // show toast message
const showResult = (noteId: number | null, result: OpChanges, count: number) => { const showResult = (noteId: number | null, result: OpChanges, count: number) => {
const props = $state({ const props = $state({
message: "", message: noteId ? tr.browsingCardsUpdated({ count: count }) : tr.importingCardsAdded({ count: count }),
type: "error" as "error" | "success", type: "success" as "error" | "success",
showToast: true, showToast: true,
}); });
mount(Toast, { mount(Toast, {
target: document.body, target: document.body,
props, props,
}); });
if (result.note) {
const msg = noteId ? tr.browsingCardsUpdated({ count: count }) : tr.importingCardsAdded({ count: count });
props.message = msg;
props.type = "success";
props.showToast = true;
} else {
const msg = tr.notetypesErrorGeneratingCloze();
props.message = msg;
props.showToast = true;
}
}; };