mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

* TemplateSaveError -> CardTypeError * Don't show success tooltip if export fails * Attach help page to error Show help link if export fails due to card type error. * Add type (dae) * Add shared show_exception() (dae) - Use a shared routine for printing standard backend errors, so that we can take advantage of the help links in eg. the card layout screen as well. - The truthiness check on help in showInfo() would have ignored the enum 0 value. - Close the exporting dialog on a documented failure as well * Fix local variable help_page
243 lines
7.7 KiB
Rust
243 lines
7.7 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
mod db;
|
|
mod filtered;
|
|
mod network;
|
|
mod search;
|
|
|
|
use std::{fmt::Display, io, path::Path};
|
|
|
|
pub use db::{DbError, DbErrorKind};
|
|
pub use filtered::{CustomStudyError, FilteredDeckError};
|
|
pub use network::{NetworkError, NetworkErrorKind, SyncError, SyncErrorKind};
|
|
pub use search::{ParseError, SearchErrorKind};
|
|
use tempfile::PathPersistError;
|
|
|
|
use crate::{i18n::I18n, links::HelpPage};
|
|
|
|
pub type Result<T, E = AnkiError> = std::result::Result<T, E>;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum AnkiError {
|
|
InvalidInput(String),
|
|
TemplateError(String),
|
|
CardTypeError(CardTypeError),
|
|
IoError(String),
|
|
FileIoError(FileIoError),
|
|
DbError(DbError),
|
|
NetworkError(NetworkError),
|
|
SyncError(SyncError),
|
|
JsonError(String),
|
|
ProtoError(String),
|
|
ParseNumError,
|
|
Interrupted,
|
|
CollectionNotOpen,
|
|
CollectionAlreadyOpen,
|
|
NotFound,
|
|
/// Indicates an absent card or note, but (unlike [AnkiError::NotFound]) in
|
|
/// a non-critical context like the browser table, where deleted ids are
|
|
/// deliberately not removed.
|
|
Deleted,
|
|
Existing,
|
|
FilteredDeckError(FilteredDeckError),
|
|
SearchError(SearchErrorKind),
|
|
InvalidRegex(String),
|
|
UndoEmpty,
|
|
MultipleNotetypesSelected,
|
|
DatabaseCheckRequired,
|
|
MediaCheckRequired,
|
|
CustomStudyError(CustomStudyError),
|
|
ImportError(ImportError),
|
|
}
|
|
|
|
impl Display for AnkiError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
// error helpers
|
|
impl AnkiError {
|
|
pub(crate) fn invalid_input<S: Into<String>>(s: S) -> AnkiError {
|
|
AnkiError::InvalidInput(s.into())
|
|
}
|
|
|
|
pub fn localized_description(&self, tr: &I18n) -> String {
|
|
match self {
|
|
AnkiError::SyncError(err) => err.localized_description(tr),
|
|
AnkiError::NetworkError(err) => err.localized_description(tr),
|
|
AnkiError::TemplateError(info) => {
|
|
// already localized
|
|
info.into()
|
|
}
|
|
AnkiError::CardTypeError(err) => {
|
|
let header =
|
|
tr.card_templates_invalid_template_number(err.ordinal + 1, &err.notetype);
|
|
let details = match err.details {
|
|
CardTypeErrorDetails::TemplateError | CardTypeErrorDetails::NoSuchField => {
|
|
tr.card_templates_see_preview()
|
|
}
|
|
CardTypeErrorDetails::NoFrontField => tr.card_templates_no_front_field(),
|
|
CardTypeErrorDetails::Duplicate(i) => tr.card_templates_identical_front(i + 1),
|
|
CardTypeErrorDetails::MissingCloze => tr.card_templates_missing_cloze(),
|
|
CardTypeErrorDetails::ExtraneousCloze => tr.card_templates_extraneous_cloze(),
|
|
};
|
|
format!("{}<br>{}", header, details)
|
|
}
|
|
AnkiError::DbError(err) => err.localized_description(tr),
|
|
AnkiError::SearchError(kind) => kind.localized_description(tr),
|
|
AnkiError::InvalidInput(info) => {
|
|
if info.is_empty() {
|
|
tr.errors_invalid_input_empty().into()
|
|
} else {
|
|
tr.errors_invalid_input_details(info.as_str()).into()
|
|
}
|
|
}
|
|
AnkiError::ParseNumError => tr.errors_parse_number_fail().into(),
|
|
AnkiError::FilteredDeckError(err) => err.localized_description(tr),
|
|
AnkiError::InvalidRegex(err) => format!("<pre>{}</pre>", err),
|
|
AnkiError::MultipleNotetypesSelected => tr.errors_multiple_notetypes_selected().into(),
|
|
AnkiError::DatabaseCheckRequired => tr.errors_please_check_database().into(),
|
|
AnkiError::MediaCheckRequired => tr.errors_please_check_media().into(),
|
|
AnkiError::CustomStudyError(err) => err.localized_description(tr),
|
|
AnkiError::ImportError(err) => err.localized_description(tr),
|
|
AnkiError::Deleted => tr.browsing_row_deleted().into(),
|
|
AnkiError::IoError(_)
|
|
| AnkiError::JsonError(_)
|
|
| AnkiError::ProtoError(_)
|
|
| AnkiError::Interrupted
|
|
| AnkiError::CollectionNotOpen
|
|
| AnkiError::CollectionAlreadyOpen
|
|
| AnkiError::NotFound
|
|
| AnkiError::Existing
|
|
| AnkiError::UndoEmpty => format!("{:?}", self),
|
|
AnkiError::FileIoError(err) => {
|
|
format!("{}: {}", err.path, err.error)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn help_page(&self) -> Option<HelpPage> {
|
|
match self {
|
|
Self::CardTypeError(CardTypeError { details, .. }) => Some(match details {
|
|
CardTypeErrorDetails::TemplateError | CardTypeErrorDetails::NoSuchField => {
|
|
HelpPage::CardTypeTemplateError
|
|
}
|
|
CardTypeErrorDetails::Duplicate(_) => HelpPage::CardTypeDuplicate,
|
|
CardTypeErrorDetails::NoFrontField => HelpPage::CardTypeNoFrontField,
|
|
CardTypeErrorDetails::MissingCloze => HelpPage::CardTypeMissingCloze,
|
|
CardTypeErrorDetails::ExtraneousCloze => HelpPage::CardTypeExtraneousCloze,
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum TemplateError {
|
|
NoClosingBrackets(String),
|
|
ConditionalNotClosed(String),
|
|
ConditionalNotOpen {
|
|
closed: String,
|
|
currently_open: Option<String>,
|
|
},
|
|
FieldNotFound {
|
|
filters: String,
|
|
field: String,
|
|
},
|
|
NoSuchConditional(String),
|
|
}
|
|
|
|
impl From<io::Error> for AnkiError {
|
|
fn from(err: io::Error) -> Self {
|
|
AnkiError::IoError(format!("{:?}", err))
|
|
}
|
|
}
|
|
|
|
impl From<serde_json::Error> for AnkiError {
|
|
fn from(err: serde_json::Error) -> Self {
|
|
AnkiError::JsonError(err.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<prost::EncodeError> for AnkiError {
|
|
fn from(err: prost::EncodeError) -> Self {
|
|
AnkiError::ProtoError(err.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<prost::DecodeError> for AnkiError {
|
|
fn from(err: prost::DecodeError) -> Self {
|
|
AnkiError::ProtoError(err.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<PathPersistError> for AnkiError {
|
|
fn from(e: PathPersistError) -> Self {
|
|
AnkiError::IoError(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<regex::Error> for AnkiError {
|
|
fn from(err: regex::Error) -> Self {
|
|
AnkiError::InvalidRegex(err.to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct CardTypeError {
|
|
pub notetype: String,
|
|
pub ordinal: usize,
|
|
pub details: CardTypeErrorDetails,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum CardTypeErrorDetails {
|
|
TemplateError,
|
|
Duplicate(usize),
|
|
NoFrontField,
|
|
NoSuchField,
|
|
MissingCloze,
|
|
ExtraneousCloze,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum ImportError {
|
|
Corrupt,
|
|
TooNew,
|
|
MediaImportFailed(String),
|
|
}
|
|
|
|
impl ImportError {
|
|
fn localized_description(&self, tr: &I18n) -> String {
|
|
match self {
|
|
ImportError::Corrupt => tr.importing_the_provided_file_is_not_a(),
|
|
ImportError::TooNew => tr.errors_collection_too_new(),
|
|
ImportError::MediaImportFailed(err) => tr.importing_failed_to_import_media_file(err),
|
|
}
|
|
.into()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
|
|
pub struct FileIoError {
|
|
pub path: String,
|
|
pub error: String,
|
|
}
|
|
|
|
impl AnkiError {
|
|
pub(crate) fn file_io_error<P: AsRef<Path>>(err: std::io::Error, path: P) -> Self {
|
|
AnkiError::FileIoError(FileIoError::new(err, path.as_ref()))
|
|
}
|
|
}
|
|
|
|
impl FileIoError {
|
|
pub fn new(err: std::io::Error, path: &Path) -> FileIoError {
|
|
FileIoError {
|
|
path: path.to_string_lossy().to_string(),
|
|
error: err.to_string(),
|
|
}
|
|
}
|
|
}
|