add a builder for Collection

This commit is contained in:
Damien Elmes 2021-11-06 14:40:39 +10:00
parent 283776d8e7
commit cb07b232d8
8 changed files with 130 additions and 130 deletions

View File

@ -8,8 +8,8 @@ pub(super) use crate::backend_proto::collection_service::Service as CollectionSe
use crate::{
backend::progress::progress_to_proto,
backend_proto as pb,
collection::open_collection,
log::{self, default_logger},
collection::CollectionBuilder,
log::{self},
prelude::*,
};
@ -30,25 +30,16 @@ impl CollectionService for Backend {
return Err(AnkiError::CollectionAlreadyOpen);
}
let mut path = input.collection_path.clone();
path.push_str(".log");
let mut builder = CollectionBuilder::new(input.collection_path);
builder
.set_media_paths(input.media_folder_path, input.media_db_path)
.set_server(self.server)
.set_tr(self.tr.clone());
if !input.log_path.is_empty() {
builder.set_log_file(&input.log_path)?;
}
let log_path = match input.log_path.as_str() {
"" => None,
path => Some(path),
};
let logger = default_logger(log_path)?;
let new_col = open_collection(
input.collection_path,
input.media_folder_path,
input.media_db_path,
self.server,
self.tr.clone(),
logger,
)?;
*col = Some(new_col);
*col = Some(builder.build()?);
Ok(().into())
}

View File

@ -12,7 +12,6 @@ use super::{progress::AbortHandleSlot, Backend};
pub(super) use crate::backend_proto::sync_service::Service as SyncService;
use crate::{
backend_proto as pb,
collection::open_collection,
media::MediaManager,
prelude::*,
sync::{
@ -329,10 +328,7 @@ impl Backend {
let (_guard, abort_reg) = self.sync_abort_handle()?;
let col_path = col_inner.col_path.clone();
let media_folder_path = col_inner.media_folder.clone();
let media_db_path = col_inner.media_db.clone();
let logger = col_inner.log.clone();
let builder = col_inner.as_builder();
let mut handler = self.new_progress_handler();
let progress_fn = move |progress: FullSyncProgress, throttle: bool| {
@ -350,14 +346,7 @@ impl Backend {
};
// ensure re-opened regardless of outcome
col.replace(open_collection(
col_path,
media_folder_path,
media_db_path,
self.server,
self.tr.clone(),
logger,
)?);
col.replace(builder.build()?);
match result {
Ok(sync_result) => {

View File

@ -5,18 +5,14 @@ pub(crate) mod timestamps;
mod transact;
pub(crate) mod undo;
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use crate::{
browser_table,
decks::{Deck, DeckId},
error::Result,
i18n::I18n,
log::Logger,
log::{default_logger, Logger},
notetype::{Notetype, NotetypeId},
scheduler::{queue::CardQueues, SchedulerInfo},
storage::SqliteStorage,
@ -24,58 +20,88 @@ use crate::{
undo::UndoManager,
};
pub fn open_collection<P: Into<PathBuf>>(
path: P,
media_folder: P,
media_db: P,
server: bool,
tr: I18n,
log: Logger,
) -> Result<Collection> {
let col_path = path.into();
let storage = SqliteStorage::open_or_create(&col_path, &tr, server)?;
let col = Collection {
storage,
col_path,
media_folder: media_folder.into(),
media_db: media_db.into(),
tr,
log,
server,
state: CollectionState::default(),
};
Ok(col)
#[derive(Default)]
pub struct CollectionBuilder {
collection_path: Option<PathBuf>,
media_folder: Option<PathBuf>,
media_db: Option<PathBuf>,
server: Option<bool>,
tr: Option<I18n>,
log: Option<Logger>,
}
// We need to make a Builder for Collection in the future.
impl CollectionBuilder {
/// Create a new builder with the provided collection path.
/// If an in-memory database is desired, used ::default() instead.
pub fn new(col_path: impl Into<PathBuf>) -> Self {
let mut builder = Self::default();
builder.set_collection_path(col_path);
builder
}
pub fn build(&self) -> Result<Collection> {
let col_path = self
.collection_path
.clone()
.unwrap_or_else(|| PathBuf::from(":memory:"));
let tr = self.tr.clone().unwrap_or_else(I18n::template_only);
let server = self.server.unwrap_or_default();
let media_folder = self.media_folder.clone().unwrap_or_default();
let media_db = self.media_db.clone().unwrap_or_default();
let log = self.log.clone().unwrap_or_else(crate::log::terminal);
let storage = SqliteStorage::open_or_create(&col_path, &tr, server)?;
let col = Collection {
storage,
col_path,
media_folder,
media_db,
tr,
log,
server,
state: CollectionState::default(),
};
Ok(col)
}
pub fn set_collection_path<P: Into<PathBuf>>(&mut self, collection: P) -> &mut Self {
self.collection_path = Some(collection.into());
self
}
pub fn set_media_paths<P: Into<PathBuf>>(&mut self, media_folder: P, media_db: P) -> &mut Self {
self.media_folder = Some(media_folder.into());
self.media_db = Some(media_db.into());
self
}
pub fn set_server(&mut self, server: bool) -> &mut Self {
self.server = Some(server);
self
}
pub fn set_tr(&mut self, tr: I18n) -> &mut Self {
self.tr = Some(tr);
self
}
/// Directly set the logger.
pub fn set_logger(&mut self, log: Logger) -> &mut Self {
self.log = Some(log);
self
}
/// Log to the provided file.
pub fn set_log_file(&mut self, log_file: &str) -> Result<&mut Self, std::io::Error> {
self.set_logger(default_logger(Some(log_file))?);
Ok(self)
}
}
#[cfg(test)]
pub fn open_test_collection() -> Collection {
use crate::config::SchedulerVersion;
let mut col = open_test_collection_with_server(false);
// our unit tests assume v2 is the default, but at the time of writing v1
// is still the default
col.set_scheduler_version_config_key(SchedulerVersion::V2)
.unwrap();
col
}
#[cfg(test)]
pub fn open_test_collection_with_server(server: bool) -> Collection {
use crate::log;
let tr = I18n::template_only();
open_collection(":memory:", "", "", server, tr, log::terminal()).unwrap()
}
/// Helper used by syncing to make sure the file can be opened. This should be replaced
/// with a builder in the future.
pub(crate) fn open_and_check_collection(col_path: &Path) -> Result<Collection> {
use crate::log;
let tr = I18n::template_only();
let empty = Path::new("");
open_collection(col_path, empty, empty, true, tr, log::terminal())
CollectionBuilder::default().build().unwrap()
}
#[derive(Debug, Default)]
@ -104,6 +130,16 @@ pub struct Collection {
}
impl Collection {
pub fn as_builder(&self) -> CollectionBuilder {
let mut builder = CollectionBuilder::new(&self.col_path);
builder
.set_media_paths(self.media_folder.clone(), self.media_db.clone())
.set_server(self.server)
.set_tr(self.tr.clone())
.set_logger(self.log.clone());
builder
}
pub(crate) fn close(self, downgrade: bool) -> Result<()> {
self.storage.close(downgrade)
}

View File

@ -5,8 +5,8 @@ use serde_json::json;
/// These items are expected to exist in schema 11. When adding
/// new config variables, you do not need to add them here -
/// just create an accessor function below with an appropriate
/// default on missing/invalid values instead.
/// just create an accessor function in one of the config/*.rs files,
/// with an appropriate default for missing/invalid values instead.
pub(crate) fn schema11_config_as_string(creation_offset: Option<i32>) -> String {
let obj = json!({
"activeDecks": [1],

View File

@ -8,6 +8,8 @@ use std::{
path::Path,
};
use anki_i18n::without_unicode_isolation;
use crate::{
collection::Collection,
error::{AnkiError, DbErrorKind, Result},
@ -25,8 +27,6 @@ use crate::{
text::{extract_media_refs, normalize_to_nfc, MediaRef, REMOTE_FILENAME},
};
use anki_i18n::without_unicode_isolation;
#[derive(Debug, PartialEq, Clone)]
pub struct MediaCheckOutput {
pub unused: Vec<String>,
@ -518,10 +518,8 @@ pub(crate) mod test {
use super::normalize_and_maybe_rename_files;
use crate::{
collection::{open_collection, Collection},
collection::{Collection, CollectionBuilder},
error::Result,
i18n::I18n,
log,
media::{
check::{MediaCheckOutput, MediaChecker},
files::trash_folder,
@ -531,18 +529,16 @@ pub(crate) mod test {
fn common_setup() -> Result<(TempDir, MediaManager, Collection)> {
let dir = tempdir()?;
let media_dir = dir.path().join("media");
fs::create_dir(&media_dir)?;
let media_folder = dir.path().join("media");
fs::create_dir(&media_folder)?;
let media_db = dir.path().join("media.db");
let col_path = dir.path().join("col.anki2");
fs::write(&col_path, MEDIACHECK_ANKI2)?;
let mgr = MediaManager::new(&media_dir, media_db.clone())?;
let log = log::terminal();
let tr = I18n::template_only();
let col = open_collection(col_path, media_dir, media_db, false, tr, log)?;
let mgr = MediaManager::new(&media_folder, media_db.clone())?;
let col = CollectionBuilder::new(col_path)
.set_media_paths(media_folder, media_db)
.build()?;
Ok((dir, mgr, col))
}

View File

@ -610,16 +610,12 @@ impl SearchNode {
#[cfg(test)]
mod test {
use std::{fs, path::PathBuf};
use std::fs;
use tempfile::tempdir;
use super::{super::parser::parse, *};
use crate::{
collection::{open_collection, Collection},
i18n::I18n,
log,
};
use crate::collection::{Collection, CollectionBuilder};
// shortcut
fn s(req: &mut Collection, search: &str) -> (String, Vec<String>) {
@ -638,17 +634,7 @@ mod test {
let col_path = dir.path().join("col.anki2");
fs::write(&col_path, MEDIACHECK_ANKI2).unwrap();
let tr = I18n::template_only();
let mut col = open_collection(
&col_path,
&PathBuf::new(),
&PathBuf::new(),
false,
tr,
log::terminal(),
)
.unwrap();
let mut col = CollectionBuilder::new(col_path).build().unwrap();
let ctx = &mut col;
// unqualified search

View File

@ -1199,7 +1199,7 @@ mod test {
use super::{server::LocalServer, *};
use crate::{
collection::open_collection, deckconfig::DeckConfig, decks::DeckKind, i18n::I18n, log,
collection::CollectionBuilder, deckconfig::DeckConfig, decks::DeckKind,
notetype::all_stock_notetypes, search::SortMode,
};
@ -1225,22 +1225,20 @@ mod test {
rt.block_on(regular_sync(&ctx))
}
fn open_col(dir: &Path, server: bool, fname: &str) -> Result<Collection> {
let path = dir.join(fname);
let tr = I18n::template_only();
open_collection(path, "".into(), "".into(), server, tr, log::terminal())
}
#[async_trait(?Send)]
trait TestContext {
fn server(&self) -> Box<dyn SyncServer>;
fn col1(&self) -> Collection {
open_col(self.dir(), false, "col1.anki2").unwrap()
CollectionBuilder::new(self.dir().join("col1.anki2"))
.build()
.unwrap()
}
fn col2(&self) -> Collection {
open_col(self.dir(), false, "col2.anki2").unwrap()
CollectionBuilder::new(self.dir().join("col2.anki2"))
.build()
.unwrap()
}
fn dir(&self) -> &Path {
@ -1274,7 +1272,11 @@ mod test {
#[async_trait(?Send)]
impl TestContext for LocalTestContext {
fn server(&self) -> Box<dyn SyncServer> {
let col = open_col(self.dir(), true, "server.anki2").unwrap();
let col_path = self.dir().join("server.anki2");
let col = CollectionBuilder::new(col_path)
.set_server(true)
.build()
.unwrap();
Box::new(LocalServer::new(col))
}
}

View File

@ -8,7 +8,7 @@ use tempfile::NamedTempFile;
use super::ChunkableIds;
use crate::{
collection::open_and_check_collection,
collection::CollectionBuilder,
prelude::*,
storage::open_and_check_sqlite_file,
sync::{
@ -200,7 +200,7 @@ impl SyncServer for LocalServer {
// ensure it's a valid sqlite file, and a valid collection
open_and_check_sqlite_file(col_path)
.and_then(|_| open_and_check_collection(col_path))
.and_then(|_| CollectionBuilder::new(col_path).build())
.map_err(|check_err| match fs::remove_file(col_path) {
Ok(_) => check_err,
Err(remove_err) => remove_err.into(),