Skip to content

Commit aa455b2

Browse files
committed
hum
1 parent 4f2b7e1 commit aa455b2

File tree

5 files changed

+181
-34
lines changed

5 files changed

+181
-34
lines changed

.cargo/config.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[env]
22
RUST_LOG = "warn,cosmic_ext_applet_clipboard_manager=debug"
3-
RUST_BACKTRACE = "full"
3+
# RUST_BACKTRACE = "full"
44

Cargo.lock

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ tracing-journald = "0.3"
4040
constcat = "0.5"
4141
nucleo = "0.5"
4242
futures = "0.3"
43+
include_dir = "0.7.4"
4344

4445
[dependencies.libcosmic]
4546
git = "https://github.com/pop-os/libcosmic"

migrations/20240929151638_favorite.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ CREATE TABLE IF NOT EXISTS FavoriteClipboardEntries (
22
id INTEGER PRIMARY KEY,
33
position INTEGER NOT NULL,
44
FOREIGN KEY (id) REFERENCES ClipboardEntries(creation) ON DELETE CASCADE,
5-
UNIQUE (position),
5+
-- UNIQUE (position),
66
CHECK (position >= 0)
77
);

src/db.rs

+158-32
Original file line numberDiff line numberDiff line change
@@ -35,39 +35,37 @@ use crate::{
3535
app::{APP, APPID, ORG, QUALIFIER},
3636
config::Config,
3737
message::AppMsg,
38-
utils::{self, now_millis, remove_dir_contents},
38+
utils::{self, now_millis},
3939
};
4040

4141
type TimeId = i64;
4242

43-
const DB_VERSION: &str = "4";
43+
const DB_VERSION: &str = "4-dev";
4444
const DB_PATH: &str = constcat::concat!(APPID, "-db-", DB_VERSION, ".sqlite");
4545

46-
// warning: if you change somethings in here, change the db version
4746
#[derive(Clone, Eq, Derivative)]
48-
#[derivative(PartialEq, Hash)]
4947
pub struct Entry {
50-
#[derivative(PartialEq = "ignore")]
51-
#[derivative(Hash = "ignore")]
5248
pub creation: TimeId,
53-
54-
#[derivative(PartialEq = "ignore")]
55-
#[derivative(Hash = "ignore")]
5649
pub mime: String,
57-
5850
// todo: lazelly load image in memory, since we can't search them anyways
5951
pub content: Vec<u8>,
60-
61-
#[derivative(PartialEq = "ignore")]
62-
#[derivative(Hash = "ignore")]
6352
/// (Mime, Content)
6453
pub metadata: Option<EntryMetadata>,
65-
66-
#[derivative(PartialEq = "ignore")]
67-
#[derivative(Hash = "ignore")]
6854
pub is_favorite: bool,
6955
}
7056

57+
impl Hash for Entry {
58+
fn hash<H: Hasher>(&self, state: &mut H) {
59+
self.content.hash(state);
60+
}
61+
}
62+
63+
impl PartialEq for Entry {
64+
fn eq(&self, other: &Self) -> bool {
65+
self.content == other.content
66+
}
67+
}
68+
7169
#[derive(Debug, Clone, Eq, PartialEq)]
7270
pub struct EntryMetadata {
7371
pub mime: String,
@@ -228,8 +226,6 @@ pub struct Db {
228226
query: String,
229227
needle: Option<Atom>,
230228
matcher: Matcher,
231-
// time
232-
last_update: i64,
233229
data_version: i64,
234230
favorites: Favorites,
235231
}
@@ -275,6 +271,13 @@ impl Favorites {
275271
fn fav(&self) -> &Vec<TimeId> {
276272
&self.favorites
277273
}
274+
275+
fn change(&mut self, prev: &TimeId, new: TimeId) {
276+
let pos = self.favorites.iter().position(|e| e == prev).unwrap();
277+
self.favorites[pos] = new;
278+
self.favorites_hash_set.remove(prev);
279+
self.favorites_hash_set.insert(new);
280+
}
278281
}
279282

280283
impl Db {
@@ -300,7 +303,11 @@ impl Db {
300303

301304
let mut conn = SqliteConnection::connect(db_path).await?;
302305

303-
let migration_path = Path::new(constcat::concat!("/usr/share/", APP, "/migrations"));
306+
let migration_path = db_dir.join("migrations");
307+
std::fs::create_dir_all(&migration_path)?;
308+
include_dir::include_dir!("migrations")
309+
.extract(&migration_path)
310+
.unwrap();
304311

305312
match sqlx::migrate::Migrator::new(migration_path).await {
306313
Ok(migrator) => migrator,
@@ -352,7 +359,6 @@ impl Db {
352359
query: String::default(),
353360
needle: None,
354361
matcher: Matcher::new(nucleo::Config::DEFAULT),
355-
last_update: 0,
356362
favorites: Favorites::default(),
357363
};
358364

@@ -378,9 +384,9 @@ impl Db {
378384

379385
let mut max = 0;
380386
for row in &rows {
381-
max = std::cmp::max(max, self.favorites.insert_row(row));
387+
max = std::cmp::max(max, self.favorites.insert_row(row) + 1);
382388
}
383-
assert!(max == self.favorite_len() - 1);
389+
assert!(max == self.favorite_len());
384390
}
385391

386392
{
@@ -467,18 +473,39 @@ impl Db {
467473
// safe to unwrap since we insert before
468474
let last_row = self.get_last_row().await?.unwrap();
469475

476+
let new_id = last_row.creation;
477+
470478
let data_hash = data.get_hash();
471479

472480
if let Some(old_id) = self.hashs.remove(&data_hash) {
473481
self.state.remove(&old_id);
474482

483+
if self.favorites.contains(&old_id) {
484+
data.is_favorite = true;
485+
let query_delete_old_id = r#"
486+
UPDATE FavoriteClipboardEntries
487+
SET id = $1
488+
WHERE id = $2;
489+
"#;
490+
491+
sqlx::query(query_delete_old_id)
492+
.bind(new_id)
493+
.bind(old_id)
494+
.execute(&mut self.conn)
495+
.await?;
496+
497+
self.favorites.change(&old_id, new_id);
498+
} else {
499+
data.is_favorite = false;
500+
}
501+
475502
// in case 2 same data were inserted in a short period
476503
// we don't want to remove the old_id
477-
if last_row.creation != old_id {
504+
if new_id != old_id {
478505
let query_delete_old_id = r#"
479-
DELETE FROM ClipboardEntries
480-
WHERE creation = ?;
481-
"#;
506+
DELETE FROM ClipboardEntries
507+
WHERE creation = ?;
508+
"#;
482509

483510
sqlx::query(query_delete_old_id)
484511
.bind(old_id)
@@ -487,11 +514,10 @@ impl Db {
487514
}
488515
}
489516

490-
data.creation = last_row.creation;
517+
data.creation = new_id;
491518

492519
self.hashs.insert(data_hash, data.creation);
493520
self.state.insert(data.creation, data);
494-
self.last_update = now_millis();
495521

496522
self.search();
497523
Ok(())
@@ -512,7 +538,6 @@ impl Db {
512538

513539
self.hashs.remove(&data.get_hash());
514540
self.state.remove(&data.creation);
515-
self.last_update = now_millis();
516541

517542
if data.is_favorite {
518543
self.favorites.remove(&data.creation);
@@ -532,7 +557,6 @@ impl Db {
532557
self.state.clear();
533558
self.filtered.clear();
534559
self.hashs.clear();
535-
self.last_update = now_millis();
536560
self.favorites.clear();
537561

538562
Ok(())
@@ -552,10 +576,11 @@ impl Db {
552576
sqlx::query(query)
553577
.bind(pos as i32)
554578
.execute(&mut self.conn)
555-
.await?;
579+
.await
580+
.unwrap();
556581
}
557582

558-
let index = index.unwrap_or(self.favorite_len());
583+
let index = index.unwrap_or(self.favorite_len() - 1);
559584

560585
{
561586
let query = r#"
@@ -570,6 +595,10 @@ impl Db {
570595
.await?;
571596
}
572597

598+
if let Some(e) = self.state.get_mut(&entry.creation) {
599+
e.is_favorite = true;
600+
}
601+
573602
Ok(())
574603
}
575604

@@ -600,6 +629,9 @@ impl Db {
600629
.await?;
601630
}
602631

632+
if let Some(e) = self.state.get_mut(&entry.creation) {
633+
e.is_favorite = false;
634+
}
603635
Ok(())
604636
}
605637

@@ -745,6 +777,8 @@ mod test {
745777

746778
use anyhow::Result;
747779
use cosmic::{iced_sctk::util, widget::canvas::Path};
780+
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
781+
748782

749783
use crate::{
750784
config::Config,
@@ -754,6 +788,16 @@ mod test {
754788
use super::{Db, Entry};
755789

756790
fn prepare_db_dir() -> PathBuf {
791+
let fmt_layer = fmt::layer().with_target(false);
792+
let filter_layer = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new(format!(
793+
"warn,{}=info",
794+
env!("CARGO_CRATE_NAME")
795+
)));
796+
tracing_subscriber::registry()
797+
.with(filter_layer)
798+
.with(fmt_layer)
799+
.init();
800+
757801
let db_dir = PathBuf::from("tests");
758802
let _ = std::fs::create_dir_all(&db_dir);
759803
remove_dir_contents(&db_dir);
@@ -925,4 +969,86 @@ mod test {
925969
db.insert(data).await.unwrap();
926970
assert!(db.len() == 2);
927971
}
972+
973+
#[tokio::test]
974+
#[serial]
975+
async fn favorites() {
976+
let db_path = prepare_db_dir();
977+
978+
let mut db = Db::inner_new(&Config::default(), &db_path).await.unwrap();
979+
980+
let now1 = 1000;
981+
982+
let data1 = Entry::new(
983+
now1,
984+
"text/plain".into(),
985+
"content1".as_bytes().into(),
986+
None,
987+
false,
988+
);
989+
990+
db.insert(data1).await.unwrap();
991+
992+
let now2 = 2000;
993+
994+
let data2 = Entry::new(
995+
now2,
996+
"text/plain".into(),
997+
"content2".as_bytes().into(),
998+
None,
999+
false,
1000+
);
1001+
1002+
db.insert(data2).await.unwrap();
1003+
1004+
let now3 = 3000;
1005+
1006+
let data3 = Entry::new(
1007+
now3,
1008+
"text/plain".into(),
1009+
"content3".as_bytes().into(),
1010+
None,
1011+
false,
1012+
);
1013+
1014+
db.insert(data3.clone()).await.unwrap();
1015+
1016+
db.add_favorite(&db.state.get(&now3).unwrap().clone(), None)
1017+
.await
1018+
.unwrap();
1019+
1020+
db.delete(&db.state.get(&now3).unwrap().clone())
1021+
.await
1022+
.unwrap();
1023+
1024+
assert_eq!(db.favorite_len(), 0);
1025+
1026+
db.insert(data3).await.unwrap();
1027+
1028+
db.add_favorite(&db.state.get(&now1).unwrap().clone(), None)
1029+
.await
1030+
.unwrap();
1031+
1032+
db.add_favorite(&db.state.get(&now3).unwrap().clone(), None)
1033+
.await
1034+
.unwrap();
1035+
1036+
db.add_favorite(&db.state.get(&now2).unwrap().clone(), Some(1))
1037+
.await
1038+
.unwrap();
1039+
1040+
assert_eq!(db.favorite_len(), 3);
1041+
1042+
assert_eq!(db.favorites.fav(), &vec![now1, now2, now3]);
1043+
1044+
drop(db);
1045+
1046+
1047+
let db = Db::inner_new(&Config::default(), &db_path).await.unwrap();
1048+
1049+
assert_eq!(db.len(), 3);
1050+
1051+
assert_eq!(db.favorite_len(), 3);
1052+
assert_eq!(db.favorites.fav(), &vec![now1, now2, now3]);
1053+
}
9281054
}

0 commit comments

Comments
 (0)