Split io.rs into separate crate, and use it in proto build

Will be handy to use it in our other scripts in the future too - thanks
Rumo!

Results of benchmarking ./run before and after these crate splits:

- Touching a proto file leads to a slight increase: about +90ms
- Touching an rslib file leads to a bigger decrease, as there's less to
recompile: about -700ms

And ./ninja test is even better: about +200ms and -3800ms.
This commit is contained in:
Damien Elmes 2023-06-12 15:24:57 +10:00
parent a83c4a7da7
commit d380f3034c
39 changed files with 160 additions and 121 deletions

43
Cargo.lock generated
View File

@ -83,6 +83,7 @@ version = "0.0.0"
dependencies = [
"ammonia",
"anki_i18n",
"anki_io",
"anki_proto",
"anyhow",
"async-compression",
@ -184,10 +185,19 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "anki_io"
version = "0.0.0"
dependencies = [
"snafu",
"tempfile",
]
[[package]]
name = "anki_proto"
version = "0.0.0"
dependencies = [
"anki_io",
"anyhow",
"inflections",
"num_enum",
@ -1148,13 +1158,13 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
@ -1935,13 +1945,13 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.9"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
@ -2033,9 +2043,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.140"
version = "0.2.146"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
[[package]]
name = "libsqlite3-sys"
@ -2117,9 +2127,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "lock_api"
@ -3415,16 +3425,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.5"
version = "0.37.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75"
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
@ -3906,15 +3916,16 @@ checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
[[package]]
name = "tempfile"
version = "3.5.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
dependencies = [
"autocfg",
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]

View File

@ -12,6 +12,7 @@ members = [
"rslib/i18n_helpers",
"rslib/linkchecker",
"rslib/proto",
"rslib/io",
"pylib/rsbridge",
"build/configure",
"build/ninja_gen",

View File

@ -43,6 +43,7 @@ features = ["json", "socks", "stream", "multipart"]
[dependencies]
anki_i18n = { path = "i18n" }
anki_io = { path = "io" }
anki_proto = { path = "proto" }
csv = { git = "https://github.com/ankitects/rust-csv.git", rev = "1c9d3aab6f79a7d815c69f925a46a4590c115f90" }

14
rslib/io/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "anki_io"
publish = false
description = "Utils for better I/O error reporting"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
snafu = "0.7.4"
tempfile = "3.6.0"

View File

@ -65,7 +65,7 @@ impl FileIoError {
)
}
pub(crate) fn is_not_found(&self) -> bool {
pub fn is_not_found(&self) -> bool {
self.source.kind() == std::io::ErrorKind::NotFound
}
}

View File

@ -1,23 +1,33 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod error;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::path::Component;
use std::path::Path;
use snafu::ResultExt;
use tempfile::NamedTempFile;
use crate::error::FileIoError;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::prelude::*;
pub use crate::error::FileIoError;
pub use crate::error::FileIoSnafu;
pub use crate::error::FileOp;
pub(crate) type Result<T, E = FileIoError> = std::result::Result<T, E>;
pub type Result<T, E = FileIoError> = std::result::Result<T, E>;
/// See [File::create].
pub fn create_file(path: impl AsRef<Path>) -> Result<File> {
File::create(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Create,
})
}
/// See [File::open].
pub(crate) fn open_file(path: impl AsRef<Path>) -> Result<File> {
pub fn open_file(path: impl AsRef<Path>) -> Result<File> {
File::open(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Open,
@ -25,7 +35,7 @@ pub(crate) fn open_file(path: impl AsRef<Path>) -> Result<File> {
}
/// See [std::fs::write].
pub(crate) fn write_file(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
pub fn write_file(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
std::fs::write(&path, contents).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Write,
@ -34,7 +44,7 @@ pub(crate) fn write_file(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) ->
/// See [std::fs::remove_file].
#[allow(dead_code)]
pub(crate) fn remove_file(path: impl AsRef<Path>) -> Result<()> {
pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
std::fs::remove_file(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Remove,
@ -42,7 +52,7 @@ pub(crate) fn remove_file(path: impl AsRef<Path>) -> Result<()> {
}
/// See [std::fs::create_dir].
pub(crate) fn create_dir(path: impl AsRef<Path>) -> Result<()> {
pub fn create_dir(path: impl AsRef<Path>) -> Result<()> {
std::fs::create_dir(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Create,
@ -50,7 +60,7 @@ pub(crate) fn create_dir(path: impl AsRef<Path>) -> Result<()> {
}
/// See [std::fs::create_dir_all].
pub(crate) fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
std::fs::create_dir_all(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Create,
@ -58,7 +68,7 @@ pub(crate) fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
}
/// See [std::fs::read].
pub(crate) fn read_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
pub fn read_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
std::fs::read(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Read,
@ -67,7 +77,7 @@ pub(crate) fn read_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
/// Like [read_file], but skips the section that is potentially locked by
/// SQLite.
pub(crate) fn read_locked_db_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
pub fn read_locked_db_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
read_locked_db_file_inner(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Read,
@ -94,28 +104,28 @@ fn read_locked_db_file_inner(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>>
}
/// See [std::fs::metadata].
pub(crate) fn metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> {
pub fn metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> {
std::fs::metadata(&path).context(FileIoSnafu {
path: path.as_ref(),
op: FileOp::Metadata,
})
}
pub(crate) fn new_tempfile() -> Result<NamedTempFile> {
pub fn new_tempfile() -> Result<NamedTempFile> {
NamedTempFile::new().context(FileIoSnafu {
path: std::env::temp_dir(),
op: FileOp::Create,
})
}
pub(crate) fn new_tempfile_in(dir: impl AsRef<Path>) -> Result<NamedTempFile> {
pub fn new_tempfile_in(dir: impl AsRef<Path>) -> Result<NamedTempFile> {
NamedTempFile::new_in(&dir).context(FileIoSnafu {
path: dir.as_ref(),
op: FileOp::Create,
})
}
pub(crate) fn new_tempfile_in_parent_of(file: &Path) -> Result<NamedTempFile> {
pub fn new_tempfile_in_parent_of(file: &Path) -> Result<NamedTempFile> {
let dir = file.parent().unwrap_or(file);
NamedTempFile::new_in(dir).context(FileIoSnafu {
path: dir,
@ -129,7 +139,7 @@ pub(crate) fn new_tempfile_in_parent_of(file: &Path) -> Result<NamedTempFile> {
/// folder is synced on UNIX platforms after renaming. This minimizes the
/// chances of corruption if there is a crash or power loss directly after the
/// op, but it can be considerably slower.
pub(crate) fn atomic_rename(file: NamedTempFile, target: &Path, fsync: bool) -> Result<()> {
pub fn atomic_rename(file: NamedTempFile, target: &Path, fsync: bool) -> Result<()> {
if fsync {
file.as_file().sync_all().context(FileIoSnafu {
path: file.path(),
@ -150,7 +160,7 @@ pub(crate) fn atomic_rename(file: NamedTempFile, target: &Path, fsync: bool) ->
}
/// Like [std::fs::read_dir], but only yielding files. [Err]s are not filtered.
pub(crate) fn read_dir_files(path: impl AsRef<Path>) -> Result<ReadDirFiles> {
pub fn read_dir_files(path: impl AsRef<Path>) -> Result<ReadDirFiles> {
std::fs::read_dir(&path)
.map(ReadDirFiles)
.context(FileIoSnafu {
@ -160,7 +170,7 @@ pub(crate) fn read_dir_files(path: impl AsRef<Path>) -> Result<ReadDirFiles> {
}
/// True if name does not contain any path separators.
pub(crate) fn filename_is_safe(name: &str) -> bool {
pub fn filename_is_safe(name: &str) -> bool {
let mut components = Path::new(name).components();
let first_element_normal = components
.next()
@ -170,7 +180,7 @@ pub(crate) fn filename_is_safe(name: &str) -> bool {
first_element_normal && components.next().is_none()
}
pub(crate) struct ReadDirFiles(std::fs::ReadDir);
pub struct ReadDirFiles(std::fs::ReadDir);
impl Iterator for ReadDirFiles {
type Item = std::io::Result<std::fs::DirEntry>;

View File

@ -10,6 +10,7 @@ license.workspace = true
rust-version.workspace = true
[build-dependencies]
anki_io = { version = "0.0.0", path = "../io" }
anyhow = "1.0.71"
inflections = "1.1.1"
prost-build = "0.11.9"

View File

@ -1,12 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::fs::File;
use std::io::BufWriter;
use std::io::Write;
use std::path::Path;
use anyhow::Context;
use anki_io::create_file;
use anyhow::Result;
use inflections::Inflect;
use prost_reflect::DescriptorPool;
@ -18,9 +17,7 @@ use prost_reflect::ServiceDescriptor;
pub(crate) fn write_python_interface(pool: &DescriptorPool) -> Result<()> {
let output_path = Path::new("../../out/pylib/anki/_backend_generated.py");
let mut out = BufWriter::new(
File::create(output_path).with_context(|| format!("opening {output_path:?}"))?,
);
let mut out = BufWriter::new(create_file(output_path)?);
write_header(&mut out)?;
for service in pool.services() {

View File

@ -7,6 +7,8 @@ use std::fs;
use std::path::Path;
use std::path::PathBuf;
use anki_io::create_dir_all;
use anki_io::read_file;
use anyhow::Context;
use anyhow::Result;
use prost_build::ServiceGenerator;
@ -17,13 +19,11 @@ pub fn write_backend_proto_rs(descriptors_path: &Path) -> Result<DescriptorPool>
let proto_dir = PathBuf::from("../../proto");
let paths = gather_proto_paths(&proto_dir)?;
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::create_dir_all(
create_dir_all(
descriptors_path
.parent()
.context("no parent found for descriptors path")?,
)
.with_context(|| format!("creating {descriptors_path:?}"))?;
.context("missing parent of descriptor")?,
)?;
prost_build::Config::new()
.out_dir(&out_dir)
.file_descriptor_set_path(descriptors_path)
@ -57,8 +57,7 @@ pub fn write_backend_proto_rs(descriptors_path: &Path) -> Result<DescriptorPool>
}
fn write_service_index(out_dir: &Path, descriptors_path: &Path) -> Result<DescriptorPool> {
let descriptors = fs::read(descriptors_path)
.with_context(|| format!("failed to read {descriptors_path:?}"))?;
let descriptors = read_file(descriptors_path)?;
let pool =
DescriptorPool::decode(descriptors.as_ref()).context("unable to decode descriptors")?;
let mut buf = String::new();

View File

@ -11,13 +11,13 @@ use std::thread;
use std::thread::JoinHandle;
use std::time::SystemTime;
use anki_io::read_locked_db_file;
use anki_proto::config::preferences::BackupLimits;
use chrono::prelude::*;
use itertools::Itertools;
use tracing::error;
use crate::import_export::package::export_colpkg_from_data;
use crate::io::read_locked_db_file;
use crate::prelude::*;
const BACKUP_FORMAT_STRING: &str = "backup-%Y-%m-%d-%H.%M.%S.colpkg";

View File

@ -13,12 +13,12 @@ use std::path::PathBuf;
use std::sync::Arc;
use anki_i18n::I18n;
use anki_io::create_dir_all;
use crate::browser_table;
use crate::decks::Deck;
use crate::decks::DeckId;
use crate::error::Result;
use crate::io::create_dir_all;
use crate::notetype::Notetype;
use crate::notetype::NotetypeId;
use crate::scheduler::queue::CardQueues;

View File

@ -2,7 +2,6 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod db;
mod file_io;
mod filtered;
mod invalid_input;
pub(crate) mod network;
@ -12,6 +11,8 @@ mod search;
pub mod windows;
use anki_i18n::I18n;
use anki_io::FileIoError;
use anki_io::FileOp;
use anki_proto::ProtoError;
pub use db::DbError;
pub use db::DbErrorKind;
@ -25,9 +26,6 @@ pub use search::ParseError;
pub use search::SearchErrorKind;
use snafu::Snafu;
pub use self::file_io::FileIoError;
pub use self::file_io::FileIoSnafu;
pub use self::file_io::FileOp;
pub use self::invalid_input::InvalidInputError;
pub use self::invalid_input::OrInvalid;
pub use self::not_found::NotFoundError;

View File

@ -4,14 +4,14 @@
use std::path::Path;
use std::path::PathBuf;
use anki_io::metadata;
use anki_io::read_file;
use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageClozeNote;
use anki_proto::image_occlusion::get_image_occlusion_note_response::Value;
use anki_proto::image_occlusion::GetImageForOcclusionResponse;
use anki_proto::image_occlusion::GetImageOcclusionNoteResponse;
use regex::Regex;
use crate::io::metadata;
use crate::io::read_file;
use crate::media::MediaManager;
use crate::notetype::CardGenContext;
use crate::prelude::*;

View File

@ -4,12 +4,12 @@
use std::collections::HashMap;
use std::collections::HashSet;
use anki_io::filename_is_safe;
use itertools::Itertools;
use super::ExportProgress;
use super::IncrementableProgress;
use crate::decks::immediate_parent_name;
use crate::io::filename_is_safe;
use crate::latex::extract_latex;
use crate::prelude::*;
use crate::revlog::RevlogEntry;

View File

@ -5,6 +5,10 @@ use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use anki_io::atomic_rename;
use anki_io::new_tempfile;
use anki_io::new_tempfile_in_parent_of;
use super::super::meta::MetaExt;
use crate::collection::CollectionBuilder;
use crate::import_export::gather::ExchangeData;
@ -13,9 +17,6 @@ use crate::import_export::package::media::MediaIter;
use crate::import_export::package::Meta;
use crate::import_export::ExportProgress;
use crate::import_export::IncrementableProgress;
use crate::io::atomic_rename;
use crate::io::new_tempfile;
use crate::io::new_tempfile_in_parent_of;
use crate::prelude::*;
impl Collection {

View File

@ -5,12 +5,12 @@ use std::collections::HashMap;
use std::fs::File;
use std::mem;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use zip::ZipArchive;
use super::super::super::meta::MetaExt;
use super::Context;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::import_export::package::media::extract_media_entries;
use crate::import_export::package::media::MediaCopier;
use crate::import_export::package::media::SafeMediaEntry;

View File

@ -10,6 +10,10 @@ use std::collections::HashSet;
use std::fs::File;
use std::path::Path;
use anki_io::new_tempfile;
use anki_io::open_file;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
pub(crate) use notes::NoteMeta;
use rusqlite::OptionalExtension;
use tempfile::NamedTempFile;
@ -17,15 +21,11 @@ use zip::ZipArchive;
use super::super::meta::MetaExt;
use crate::collection::CollectionBuilder;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::import_export::gather::ExchangeData;
use crate::import_export::package::Meta;
use crate::import_export::ImportProgress;
use crate::import_export::IncrementableProgress;
use crate::import_export::NoteLog;
use crate::io::new_tempfile;
use crate::io::open_file;
use crate::media::MediaManager;
use crate::prelude::*;
use crate::search::SearchNode;

View File

@ -7,7 +7,8 @@ use std::collections::HashSet;
use std::fs::File;
use std::io::Write;
use crate::io::read_file;
use anki_io::read_file;
use crate::media::files::sha1_of_data;
use crate::media::MediaManager;
use crate::prelude::*;

View File

@ -9,6 +9,10 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use anki_io::atomic_rename;
use anki_io::new_tempfile;
use anki_io::new_tempfile_in_parent_of;
use anki_io::open_file;
use prost::Message;
use tempfile::NamedTempFile;
use zip::write::FileOptions;
@ -30,10 +34,6 @@ use crate::import_export::package::media::MediaCopier;
use crate::import_export::package::media::MediaIter;
use crate::import_export::ExportProgress;
use crate::import_export::IncrementableProgress;
use crate::io::atomic_rename;
use crate::io::new_tempfile;
use crate::io::new_tempfile_in_parent_of;
use crate::io::open_file;
use crate::prelude::*;
use crate::storage::SchemaVersion;

View File

@ -7,24 +7,24 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use anki_io::atomic_rename;
use anki_io::create_dir_all;
use anki_io::new_tempfile_in_parent_of;
use anki_io::open_file;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use zip::read::ZipFile;
use zip::ZipArchive;
use zstd::stream::copy_decode;
use super::super::meta::MetaExt;
use crate::collection::CollectionBuilder;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::import_export::package::media::extract_media_entries;
use crate::import_export::package::media::SafeMediaEntry;
use crate::import_export::package::Meta;
use crate::import_export::ImportError;
use crate::import_export::ImportProgress;
use crate::import_export::IncrementableProgress;
use crate::io::atomic_rename;
use crate::io::create_dir_all;
use crate::io::new_tempfile_in_parent_of;
use crate::io::open_file;
use crate::media::MediaManager;
use crate::prelude::*;

View File

@ -5,12 +5,12 @@
use std::path::Path;
use anki_io::create_dir_all;
use anki_io::read_file;
use tempfile::tempdir;
use crate::collection::CollectionBuilder;
use crate::import_export::package::import_colpkg;
use crate::io::create_dir_all;
use crate::io::read_file;
use crate::media::MediaManager;
use crate::prelude::*;
@ -79,7 +79,7 @@ fn roundtrip() -> Result<()> {
#[test]
#[cfg(not(target_vendor = "apple"))]
fn normalization_check_on_export() -> Result<()> {
use crate::io::write_file;
use anki_io::write_file;
let _dir = tempdir()?;
let dir = _dir.path();

View File

@ -12,6 +12,12 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use anki_io::atomic_rename;
use anki_io::filename_is_safe;
use anki_io::new_tempfile_in;
use anki_io::read_dir_files;
use anki_io::FileIoError;
use anki_io::FileOp;
use prost::Message;
use sha1::Digest;
use sha1::Sha1;
@ -24,15 +30,9 @@ use super::meta::MetaExt;
use super::MediaEntries;
use super::MediaEntry;
use super::Meta;
use crate::error::FileIoError;
use crate::error::FileOp;
use crate::error::InvalidInputError;
use crate::import_export::package::colpkg::export::MaybeEncodedWriter;
use crate::import_export::ImportError;
use crate::io::atomic_rename;
use crate::io::filename_is_safe;
use crate::io::new_tempfile_in;
use crate::io::read_dir_files;
use crate::media::files::filename_if_normalized;
use crate::media::files::normalize_filename;
use crate::prelude::*;

View File

@ -7,6 +7,8 @@ use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use anki_io::open_file;
use crate::import_export::text::csv::metadata::CsvDeck;
use crate::import_export::text::csv::metadata::CsvMetadata;
use crate::import_export::text::csv::metadata::CsvMetadataHelpers;
@ -18,7 +20,6 @@ use crate::import_export::text::ForeignNote;
use crate::import_export::text::NameOrId;
use crate::import_export::ImportProgress;
use crate::import_export::NoteLog;
use crate::io::open_file;
use crate::prelude::*;
use crate::text::strip_utf8_bom;

View File

@ -9,6 +9,7 @@ use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use anki_io::open_file;
pub use anki_proto::import_export::csv_metadata::Deck as CsvDeck;
pub use anki_proto::import_export::csv_metadata::Delimiter;
pub use anki_proto::import_export::csv_metadata::DupeResolution;
@ -24,7 +25,6 @@ use crate::config::I32ConfigKey;
use crate::import_export::text::csv::import::FieldSourceColumns;
use crate::import_export::text::NameOrId;
use crate::import_export::ImportError;
use crate::io::open_file;
use crate::notetype::NoteField;
use crate::prelude::*;
use crate::text::html_to_text_line;

View File

@ -1,10 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki_io::read_file;
use crate::import_export::text::ForeignData;
use crate::import_export::ImportProgress;
use crate::import_export::NoteLog;
use crate::io::read_file;
use crate::prelude::*;
impl Collection {

View File

@ -18,7 +18,6 @@ pub mod error;
pub mod findreplace;
pub mod image_occlusion;
pub mod import_export;
mod io;
pub mod latex;
pub mod links;
pub mod log;

View File

@ -521,13 +521,13 @@ pub(crate) mod test {
use std::collections::HashMap;
use anki_io::create_dir;
use anki_io::write_file;
use tempfile::tempdir;
use tempfile::TempDir;
use super::*;
use crate::collection::CollectionBuilder;
use crate::io::create_dir;
use crate::io::write_file;
fn common_setup() -> Result<(TempDir, MediaManager, Collection)> {
let dir = tempdir()?;

View File

@ -9,6 +9,12 @@ use std::path::Path;
use std::path::PathBuf;
use std::time;
use anki_io::create_dir;
use anki_io::open_file;
use anki_io::write_file;
use anki_io::FileIoError;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use lazy_static::lazy_static;
use regex::Regex;
use sha1::Digest;
@ -18,12 +24,6 @@ use unic_ucd_category::GeneralCategory;
use unicode_normalization::is_nfc;
use unicode_normalization::UnicodeNormalization;
use crate::error::FileIoError;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::io::create_dir;
use crate::io::open_file;
use crate::io::write_file;
use crate::prelude::*;
use crate::sync::media::MAX_MEDIA_FILENAME_LENGTH;

View File

@ -9,7 +9,8 @@ use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use crate::io::create_dir_all;
use anki_io::create_dir_all;
use crate::media::files::add_data_to_folder_uniquely;
use crate::media::files::mtime_as_i64;
use crate::media::files::remove_files;

View File

@ -949,13 +949,13 @@ impl SearchNode {
#[cfg(test)]
mod test {
use anki_io::write_file;
use tempfile::tempdir;
use super::super::parser::parse;
use super::*;
use crate::collection::Collection;
use crate::collection::CollectionBuilder;
use crate::io::write_file;
// shortcut
fn s(req: &mut Collection, search: &str) -> (String, Vec<String>) {

View File

@ -77,9 +77,10 @@ impl SqliteStorage {
#[cfg(test)]
mod test {
use anki_io::new_tempfile;
use super::*;
use crate::collection::CollectionBuilder;
use crate::io::new_tempfile;
use crate::prelude::*;
#[test]

View File

@ -1,11 +1,12 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki_io::atomic_rename;
use anki_io::new_tempfile_in_parent_of;
use anki_io::read_file;
use anki_io::write_file;
use crate::collection::CollectionBuilder;
use crate::io::atomic_rename;
use crate::io::new_tempfile_in_parent_of;
use crate::io::read_file;
use crate::io::write_file;
use crate::prelude::*;
use crate::storage::SchemaVersion;
use crate::sync::collection::progress::FullSyncProgressFn;

View File

@ -4,6 +4,9 @@
use std::fs;
use std::io::Write;
use anki_io::atomic_rename;
use anki_io::new_tempfile_in_parent_of;
use anki_io::write_file;
use axum::response::IntoResponse;
use axum::response::Response;
use flate2::write::GzEncoder;
@ -13,9 +16,6 @@ use tokio_util::io::ReaderStream;
use crate::collection::CollectionBuilder;
use crate::error::SyncErrorKind;
use crate::io::atomic_rename;
use crate::io::new_tempfile_in_parent_of;
use crate::io::write_file;
use crate::prelude::*;
use crate::storage::SchemaVersion;
use crate::sync::collection::progress::FullSyncProgressFn;

View File

@ -4,10 +4,10 @@
use std::fs;
use std::io::ErrorKind;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use snafu::ResultExt;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::sync::error::HttpResult;
use crate::sync::error::OrHttpErr;
use crate::sync::http_server::media_manager::ServerMediaManager;

View File

@ -7,7 +7,8 @@ pub mod upload;
use std::path::Path;
use std::path::PathBuf;
use crate::io::create_dir_all;
use anki_io::create_dir_all;
use crate::prelude::*;
use crate::sync::error::HttpResult;
use crate::sync::error::OrHttpErr;

View File

@ -5,14 +5,14 @@ use std::fs;
use std::io::ErrorKind;
use std::path::Path;
use anki_io::write_file;
use anki_io::FileIoError;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use snafu::ResultExt;
use tracing::info;
use crate::error;
use crate::error::FileIoError;
use crate::error::FileIoSnafu;
use crate::error::FileOp;
use crate::io::write_file;
use crate::sync::error::HttpResult;
use crate::sync::error::OrHttpErr;
use crate::sync::http_server::media_manager::ServerMediaManager;

View File

@ -18,6 +18,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::sync::Mutex;
use anki_io::create_dir_all;
use axum::extract::DefaultBodyLimit;
use axum::Router;
use snafu::whatever;
@ -27,7 +28,6 @@ use snafu::Whatever;
use tracing::Span;
use crate::error;
use crate::io::create_dir_all;
use crate::media::files::sha1_of_data;
use crate::sync::error::HttpResult;
use crate::sync::error::OrHttpErr;

View File

@ -5,9 +5,9 @@ use std::collections::HashMap;
use std::path::Path;
use std::time;
use anki_io::read_dir_files;
use tracing::debug;
use crate::io::read_dir_files;
use crate::media::files::filename_if_normalized;
use crate::media::files::mtime_as_i64;
use crate::media::files::sha1_of_file;
@ -243,12 +243,12 @@ mod test {
use std::time;
use std::time::Duration;
use anki_io::create_dir;
use anki_io::write_file;
use tempfile::tempdir;
use super::*;
use crate::error::Result;
use crate::io::create_dir;
use crate::io::write_file;
use crate::media::files::sha1_of_data;
use crate::media::MediaManager;
use crate::sync::media::database::client::MediaEntry;

View File

@ -321,10 +321,10 @@ fn initial_db_setup(db: &mut Connection) -> error::Result<()> {
#[cfg(test)]
mod test {
use anki_io::new_tempfile;
use tempfile::TempDir;
use crate::error::Result;
use crate::io::new_tempfile;
use crate::media::files::sha1_of_data;
use crate::media::MediaManager;
use crate::sync::media::database::client::MediaEntry;