Compare commits

..

4 Commits

Author SHA1 Message Date
Manos Pitsidianakis c1c41c9126
Update README.md and add Codeberg mirror
Tests / Test on ${{ matrix.build }} (linux-amd64, ubuntu-latest, stable, x86_64-unknown-linux-gnu) (push) Successful in 18m31s Details
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
2023-11-27 21:21:17 +02:00
Manos Pitsidianakis a1cbb1988b
types/File: return Results instead of panicking
Tests / Test on ${{ matrix.build }} (linux-amd64, ubuntu-latest, stable, x86_64-unknown-linux-gnu) (pull_request) Successful in 12m54s Details
Tests / Test on ${{ matrix.build }} (linux-amd64, ubuntu-latest, stable, x86_64-unknown-linux-gnu) (push) Successful in 23m47s Details
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
2023-11-27 09:40:40 +02:00
Manos Pitsidianakis 470cae6b88
Update thread cache on email flag modifications
Tests / Test on ${{ matrix.build }} (linux-amd64, ubuntu-latest, stable, x86_64-unknown-linux-gnu) (pull_request) Successful in 23m53s Details
Tests / Test on ${{ matrix.build }} (linux-amd64, ubuntu-latest, stable, x86_64-unknown-linux-gnu) (push) Successful in 23m44s Details
On a previous commit email flag modification logic was changed, but
threads cache was not updated, leading to threads unread count being
stale.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
2023-11-27 09:37:58 +02:00
Manos Pitsidianakis 23507932f9
imap: update cache on set_flags
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
2023-11-27 09:37:58 +02:00
12 changed files with 345 additions and 151 deletions

View File

@ -244,6 +244,7 @@ pub enum JobRequest {
},
SetFlags {
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[FlagOp; 8]>,
handle: JoinHandle<Result<()>>,
},
@ -329,10 +330,14 @@ impl std::fmt::Debug for JobRequest {
JobRequest::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
JobRequest::Refresh { .. } => write!(f, "JobRequest::Refresh"),
JobRequest::SetFlags {
env_hashes, flags, ..
env_hashes,
mailbox_hash,
flags,
..
} => f
.debug_struct(stringify!(JobRequest::SetFlags))
.field("env_hashes", &env_hashes)
.field("mailbox_hash", &mailbox_hash)
.field("flags", &flags)
.finish(),
JobRequest::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
@ -1202,15 +1207,15 @@ impl Account {
if let Some(mailbox_hash) = saved_at {
Ok(mailbox_hash)
} else {
let file = crate::types::create_temp_file(bytes, None, None, Some("eml"), false);
log::trace!("message saved in {}", file.path.display());
let file = crate::types::File::create_temp_file(bytes, None, None, Some("eml"), false)?;
log::trace!("message saved in {}", file.path().display());
log::info!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
file.path().display()
);
Err(Error::new(format!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
file.path().display()
))
.set_summary("Could not save in any mailbox"))
}
@ -1882,6 +1887,7 @@ impl Account {
JobRequest::SetFlags {
ref mut handle,
ref env_hashes,
ref mailbox_hash,
ref flags,
} => match handle.chan.try_recv() {
Ok(Some(Err(err))) => {
@ -1936,8 +1942,11 @@ impl Account {
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::EnvelopeUpdate(env_hash)));
}
for env_hash in env_hashes.iter() {
self.collection.update_flags(env_hash, *mailbox_hash);
}
}
_ => {}
Err(_) | Ok(None) => {}
},
JobRequest::SaveMessage {
ref mut handle,
@ -1949,22 +1958,33 @@ impl Account {
.job_executor
.set_job_success(job_id, false);
log::error!("Could not save message: {err}");
let file =
crate::types::create_temp_file(bytes, None, None, Some("eml"), false);
log::debug!("message saved in {}", file.path.display());
log::info!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
);
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not save message", &self.name)),
format!(
match crate::types::File::create_temp_file(
bytes,
None,
None,
Some("eml"),
false,
) {
Ok(file) => {
log::debug!("message saved in {}", file.path().display());
log::info!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
),
Some(crate::types::NotificationType::Info),
)));
file.path().display()
);
self.main_loop_handler.send(ThreadEvent::UIEvent(
UIEvent::Notification(
Some(format!("{}: could not save message", &self.name)),
format!(
"Message was stored in {} so that you can restore it \
manually.",
file.path().display()
),
Some(crate::types::NotificationType::Info),
),
));
}
Err(err) => log::error!("Could not save message: {err}"),
}
}
}
JobRequest::SendMessage => {}

View File

@ -38,12 +38,13 @@ impl Account {
let handle = self
.main_loop_handler
.job_executor
.spawn_specialized("set_unseen".into(), fut);
.spawn_specialized("set_flags".into(), fut);
let job_id = handle.job_id;
self.insert_job(
job_id,
JobRequest::SetFlags {
env_hashes,
mailbox_hash,
flags,
handle,
},

View File

@ -841,8 +841,12 @@ To: {}
}
fn update_from_file(&mut self, file: File, context: &mut Context) -> bool {
let result = file.read_to_string();
match self.draft.update(result.as_str()) {
match file.read_to_string().and_then(|res| {
self.draft.update(res.as_str()).map_err(|err| {
self.draft.set_body(res);
err
})
}) {
Ok(has_changes) => {
self.has_changes = has_changes;
true
@ -850,13 +854,9 @@ To: {}
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not parse draft headers correctly.".to_string()),
format!(
"{}\nThe invalid text has been set as the body of your draft",
&err
),
format!("{err}\nThe invalid text has been set as the body of your draft",),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.draft.set_body(result);
self.has_changes = true;
false
}
@ -1898,13 +1898,24 @@ impl Component for Composer {
.clone(),
);
let f = create_temp_file(
let f = match File::create_temp_file(
self.draft.to_edit_string().as_bytes(),
None,
None,
Some("eml"),
true,
);
) {
Ok(f) => f,
Err(err) => {
context.replies.push_back(UIEvent::Notification(
None,
err.to_string(),
Some(NotificationType::Error(err.kind)),
));
self.set_dirty(true);
return true;
}
};
if *account_settings!(context[self.account_hash].composing.embed) {
match crate::terminal::embed::create_pty(
@ -1966,8 +1977,12 @@ impl Component for Composer {
}
}
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
let result = f.read_to_string();
match self.draft.update(result.as_str()) {
match f.read_to_string().and_then(|res| {
self.draft.update(res.as_str()).map_err(|err| {
self.draft.set_body(res);
err
})
}) {
Ok(has_changes) => {
self.has_changes = has_changes;
}
@ -1975,12 +1990,10 @@ impl Component for Composer {
context.replies.push_back(UIEvent::Notification(
Some("Could not parse draft headers correctly.".to_string()),
format!(
"{}\nThe invalid text has been set as the body of your draft",
&err
"{err}\nThe invalid text has been set as the body of your draft",
),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.draft.set_body(result);
self.has_changes = true;
}
}
@ -1998,15 +2011,21 @@ impl Component for Composer {
));
return false;
}
let f = create_temp_file(&[], None, None, None, true);
match Command::new("sh")
.args(["-c", command])
.stdin(Stdio::null())
.stdout(Stdio::from(f.file()))
.spawn()
.and_then(|child| Ok(child.wait_with_output()?.stderr))
match File::create_temp_file(&[], None, None, None, true)
.and_then(|f| {
let std_file = f.as_std_file()?;
Ok((
f,
Command::new("sh")
.args(["-c", command])
.stdin(Stdio::null())
.stdout(Stdio::from(std_file))
.spawn()?,
))
})
.and_then(|(f, child)| Ok((f, child.wait_with_output()?.stderr)))
{
Ok(stderr) => {
Ok((f, stderr)) => {
if !stderr.is_empty() {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
@ -2016,7 +2035,7 @@ impl Component for Composer {
));
}
let attachment =
match melib::email::compose::attachment_from_file(f.path()) {
match melib::email::compose::attachment_from_file(&f.path()) {
Ok(a) => a,
Err(err) => {
context.replies.push_back(UIEvent::Notification(
@ -2548,7 +2567,7 @@ pub fn send_draft_async(
))))
.unwrap();
} else if !store_sent_mail && is_ok {
let f = create_temp_file(message.as_bytes(), None, None, Some("eml"), false);
let f = File::create_temp_file(message.as_bytes(), None, None, Some("eml"), false)?;
log::info!(
"store_sent_mail is false; stored sent mail to {}",
f.path().display()

View File

@ -1532,33 +1532,36 @@ impl Component for EnvelopeView {
let attachment_type = attachment.mime_type();
let filename = attachment.filename();
if let Ok(command) = query_default_app(&attachment_type) {
let p = create_temp_file(
match File::create_temp_file(
&attachment.decode(Default::default()),
filename.as_deref(),
None,
None,
true,
);
let exec_cmd = desktop_exec_to_command(
&command,
p.path.display().to_string(),
false,
);
match Command::new("sh")
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
Ok(child) => {
)
.and_then(|p| {
let exec_cmd = desktop_exec_to_command(
&command,
p.path().display().to_string(),
false,
);
Ok((
p,
Command::new("sh")
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?,
))
}) {
Ok((p, child)) => {
context.temp_files.push(p);
context.children.push(child);
}
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to start `{}`: {}",
&exec_cmd, err
"Failed to execute command: {err}"
)),
));
}

View File

@ -173,31 +173,36 @@ impl Component for HtmlView {
command
};
if let Some(command) = command {
let p = create_temp_file(&self.bytes, None, None, Some("html"), true);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(
command.to_string(),
)));
let exec_cmd =
super::desktop_exec_to_command(&command, p.path.display().to_string(), false);
match File::create_temp_file(&self.bytes, None, None, Some("html"), true).and_then(
|p| {
let exec_cmd = super::desktop_exec_to_command(
&command,
p.path().display().to_string(),
false,
);
match Command::new("sh")
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
Ok(child) => {
Ok((
p,
Command::new("sh")
.args(["-c", &exec_cmd])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?,
))
},
) {
Ok((p, child)) => {
context.temp_files.push(p);
context.children.push(child);
}
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to start `{}`: {}",
&exec_cmd, err
)),
StatusEvent::DisplayMessage(format!("Failed to start {err}",)),
));
}
}

View File

@ -34,7 +34,7 @@ use melib::{email::Attachment, log, text_processing::GlobMatch, Error, Result};
use crate::{
state::Context,
types::{create_temp_file, ForkType, UIEvent},
types::{File, ForkType, UIEvent},
};
pub struct MailcapEntry {
@ -165,31 +165,34 @@ impl MailcapEntry {
.map(|arg| match *arg {
"%s" => {
needs_stdin = false;
let _f = create_temp_file(
let _f = File::create_temp_file(
&a.decode(Default::default()),
None,
None,
None,
true,
);
)?;
let p = _f.path().display().to_string();
f = Some(_f);
p
Ok(p)
}
"%t" => a.content_type().to_string(),
"%t" => Ok(a.content_type().to_string()),
param if param.starts_with("%{") && param.ends_with('}') => {
let param = &param["%{".len()..param.len() - 1];
if let Some(v) = params.iter().find(|(k, _)| *k == param.as_bytes()) {
String::from_utf8_lossy(v.1).into()
} else if param == "charset" {
String::from("utf-8")
} else {
String::new()
}
Ok(
if let Some(v) = params.iter().find(|(k, _)| *k == param.as_bytes())
{
String::from_utf8_lossy(v.1).into()
} else if param == "charset" {
String::from("utf-8")
} else {
String::new()
},
)
}
a => a.to_string(),
a => Ok(a.to_string()),
})
.collect::<Vec<String>>();
.collect::<Result<Vec<String>>>()?;
let cmd_string = format!("{} {}", cmd, args.join(" "));
log::trace!("Executing: sh -c \"{}\"", cmd_string.replace('"', "\\\""));
if copiousoutput {

View File

@ -24,14 +24,17 @@ use std::{
fs::OpenOptions,
io::{Read, Write},
os::unix::fs::PermissionsExt,
path::PathBuf,
path::{Path, PathBuf},
};
use melib::uuid::Uuid;
use melib::{error::*, uuid::Uuid};
/// Temporary file that can optionally cleaned up when it is dropped.
#[derive(Debug)]
pub struct File {
pub path: PathBuf,
/// File's path.
path: PathBuf,
/// Delete file when it is dropped.
delete_on_drop: bool,
}
@ -44,69 +47,121 @@ impl Drop for File {
}
impl File {
pub fn file(&self) -> std::fs::File {
/// Open as a standard library file type.
pub fn as_std_file(&self) -> Result<std::fs::File> {
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&self.path)
.unwrap()
.chain_err_summary(|| format!("Could not create/open path {}", self.path.display()))
}
pub fn path(&self) -> &PathBuf {
/// The file's path.
pub fn path(&self) -> &Path {
&self.path
}
pub fn read_to_string(&self) -> String {
let mut buf = Vec::new();
let mut f = fs::File::open(&self.path)
.unwrap_or_else(|_| panic!("Can't open {}", &self.path.display()));
f.read_to_end(&mut buf)
.unwrap_or_else(|_| panic!("Can't read {}", &self.path.display()));
String::from_utf8(buf).unwrap()
}
}
/// Returned `File` will be deleted when dropped if delete_on_drop is set, so
/// make sure to add it on `context.temp_files` to reap it later.
pub fn create_temp_file(
bytes: &[u8],
filename: Option<&str>,
path: Option<&mut PathBuf>,
extension: Option<&str>,
delete_on_drop: bool,
) -> File {
let mut dir = std::env::temp_dir();
let path = path.unwrap_or_else(|| {
dir.push("meli");
std::fs::DirBuilder::new()
.recursive(true)
.create(&dir)
.unwrap();
if let Some(filename) = filename {
dir.push(filename)
} else {
let u = Uuid::new_v4();
dir.push(u.as_simple().to_string());
/// Convenience method to read `File` to `String`.
pub fn read_to_string(&self) -> Result<String> {
fn inner(path: &Path) -> Result<String> {
let mut buf = Vec::new();
let mut f = fs::File::open(path)?;
f.read_to_end(&mut buf)?;
Ok(String::from_utf8(buf)?)
}
&mut dir
});
if let Some(ext) = extension {
path.set_extension(ext);
inner(&self.path).chain_err_summary(|| format!("Can't read {}", self.path.display()))
}
let mut f = std::fs::File::create(&path).unwrap();
let metadata = f.metadata().unwrap();
let mut permissions = metadata.permissions();
/// Returned `File` will be deleted when dropped if delete_on_drop is set,
/// so make sure to add it on `context.temp_files` to reap it later.
pub fn create_temp_file(
bytes: &[u8],
filename: Option<&str>,
path: Option<&mut PathBuf>,
extension: Option<&str>,
delete_on_drop: bool,
) -> Result<Self> {
let mut dir = std::env::temp_dir();
permissions.set_mode(0o600); // Read/write for owner only.
f.set_permissions(permissions).unwrap();
let path = if let Some(p) = path {
p
} else {
dir.push("meli");
std::fs::DirBuilder::new().recursive(true).create(&dir)?;
if let Some(filename) = filename {
dir.push(filename)
} else {
let u = Uuid::new_v4();
dir.push(u.as_simple().to_string());
}
&mut dir
};
if let Some(ext) = extension {
path.set_extension(ext);
}
fn inner(path: &Path, bytes: &[u8], delete_on_drop: bool) -> Result<File> {
let mut f = std::fs::File::create(path)?;
let metadata = f.metadata()?;
let mut permissions = metadata.permissions();
f.write_all(bytes).unwrap();
f.flush().unwrap();
File {
path: path.clone(),
delete_on_drop,
permissions.set_mode(0o600); // Read/write for owner only.
f.set_permissions(permissions)?;
f.write_all(bytes)?;
f.flush()?;
Ok(File {
path: path.to_path_buf(),
delete_on_drop,
})
}
inner(path, bytes, delete_on_drop)
.chain_err_summary(|| format!("Could not create file at path {}", path.display()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_invalid_path() {
let f = File {
path: PathBuf::from("//////"),
delete_on_drop: true,
};
f.as_std_file().unwrap_err();
}
#[test]
fn test_file_delete_on_drop() {
const S: &str = "hello world";
let tempdir = tempfile::tempdir().unwrap();
let delete_on_drop = File::create_temp_file(
S.as_bytes(),
None,
Some(&mut tempdir.path().join("test")),
None,
true,
)
.unwrap();
assert_eq!(&delete_on_drop.read_to_string().unwrap(), S);
drop(delete_on_drop);
assert!(!tempdir.path().join("test").try_exists().unwrap());
let persist = File::create_temp_file(
S.as_bytes(),
None,
Some(&mut tempdir.path().join("test")),
None,
false,
)
.unwrap();
assert_eq!(&persist.read_to_string().unwrap(), S);
drop(persist);
assert!(tempdir.path().join("test").try_exists().unwrap());
_ = tempdir.close();
}
}

View File

@ -42,7 +42,7 @@ nom = { version = "7" }
notify = { version = "4.0.15", optional = true }
polling = "2.8"
regex = { version = "1" }
rusqlite = { version = "^0.29", default-features = false, optional = true }
rusqlite = { version = "^0.29", default-features = false, features = ["array"], optional = true }
serde = { version = "1.0", features = ["rc"] }
serde_derive = "1.0"
serde_json = { version = "1.0", features = ["raw_value"] }

View File

@ -92,6 +92,13 @@ pub trait ImapCache: Send + std::fmt::Debug {
identifier: std::result::Result<UID, EnvelopeHash>,
mailbox_hash: MailboxHash,
) -> Result<Option<Vec<u8>>>;
fn update_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[FlagOp; 8]>,
) -> Result<()>;
}
pub trait ImapCacheReset: Send + std::fmt::Debug {
@ -108,7 +115,7 @@ pub mod sqlite3_m {
use super::*;
use crate::utils::sqlite3::{
self,
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Value},
Connection, DatabaseDescription,
};
@ -151,6 +158,12 @@ pub mod sqlite3_m {
version: 3,
};
impl From<EnvelopeHash> for Value {
fn from(env_hash: EnvelopeHash) -> Self {
(env_hash.0 as i64).into()
}
}
impl ToSql for ModSequence {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
Ok(ToSqlOutput::from(self.0.get() as i64))
@ -539,6 +552,67 @@ pub mod sqlite3_m {
Ok(())
}
fn update_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[FlagOp; 8]>,
) -> Result<()> {
if self.mailbox_state(mailbox_hash)?.is_none() {
return Err(Error::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
}
let Self {
ref mut connection,
ref uid_store,
loaded_mailboxes: _,
} = self;
let tx = connection.transaction()?;
let values =
std::rc::Rc::new(env_hashes.iter().map(Value::from).collect::<Vec<Value>>());
let mut stmt =
tx.prepare("SELECT uid, envelope FROM envelopes WHERE hash IN rarray(?1);")?;
let rows = stmt
.query_map([values], |row| Ok((row.get(0)?, row.get(1)?)))?
.filter_map(|r| r.ok())
.collect::<Vec<(UID, Envelope)>>();
drop(stmt);
let mut stmt = tx.prepare(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
)?;
for (uid, mut env) in rows {
for op in flags.iter() {
match op {
FlagOp::UnSet(flag) | FlagOp::Set(flag) => {
let mut f = env.flags();
f.set(*flag, op.as_bool());
env.set_flags(f);
}
FlagOp::UnSetTag(tag) | FlagOp::SetTag(tag) => {
let hash = TagHash::from_bytes(tag.as_bytes());
if op.as_bool() {
env.tags_mut().insert(hash);
} else {
env.tags_mut().remove(&hash);
}
}
}
}
stmt.execute(sqlite3::params![&env, mailbox_hash, uid as Sqlite3UID])?;
uid_store
.envelopes
.lock()
.unwrap()
.entry(env.hash())
.and_modify(|entry| {
entry.inner = env;
});
}
drop(stmt);
tx.commit()?;
Ok(())
}
fn update(
&mut self,
mailbox_hash: MailboxHash,
@ -829,5 +903,14 @@ pub mod default_m {
) -> Result<Option<Vec<u8>>> {
Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
}
fn update_flags(
&mut self,
_env_hashes: EnvelopeHashBatch,
_mailbox_hash: MailboxHash,
_flags: SmallVec<[FlagOp; 8]>,
) -> Result<()> {
Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
}
}
}

View File

@ -857,6 +857,12 @@ impl MailBackend for ImapType {
}
}
}
#[cfg(feature = "sqlite3")]
if *uid_store.keep_offline_cache.lock().unwrap() {
let mut cache_handle = cache::Sqlite3Cache::get(uid_store.clone())?;
let res = cache_handle.update_flags(env_hashes, mailbox_hash, flags);
log::trace!("update_flags in cache: {:?}", res);
}
Ok(())
}))
}

View File

@ -102,11 +102,6 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
mailbox_hash,
kind: RefreshEventKind::Rescan,
});
/*
uid_store.uid_index.lock().unwrap().clear();
uid_store.hash_index.lock().unwrap().clear();
uid_store.byte_cache.lock().unwrap().clear();
*/
}
} else {
uidvalidities.insert(mailbox_hash, select_response.uidvalidity);

View File

@ -45,7 +45,10 @@ pub fn open_db(db_path: PathBuf) -> Result<Connection> {
if !db_path.exists() {
return Err(Error::new("Database doesn't exist"));
}
Connection::open(&db_path).map_err(|e| Error::new(e.to_string()))
Ok(Connection::open(&db_path).and_then(|db| {
rusqlite::vtab::array::load_module(&db)?;
Ok(db)
})?)
}
pub fn open_or_create_db(
@ -66,7 +69,8 @@ pub fn open_or_create_db(
db_path.display()
);
}
let conn = Connection::open(&db_path).map_err(|e| Error::new(e.to_string()))?;
let conn = Connection::open(&db_path)?;
rusqlite::vtab::array::load_module(&conn)?;
if set_mode {
use std::os::unix::fs::PermissionsExt;
let file = std::fs::File::open(&db_path)?;