@@ -35,39 +35,37 @@ use crate::{
35
35
app:: { APP , APPID , ORG , QUALIFIER } ,
36
36
config:: Config ,
37
37
message:: AppMsg ,
38
- utils:: { self , now_millis, remove_dir_contents } ,
38
+ utils:: { self , now_millis} ,
39
39
} ;
40
40
41
41
type TimeId = i64 ;
42
42
43
- const DB_VERSION : & str = "4" ;
43
+ const DB_VERSION : & str = "4-dev " ;
44
44
const DB_PATH : & str = constcat:: concat!( APPID , "-db-" , DB_VERSION , ".sqlite" ) ;
45
45
46
- // warning: if you change somethings in here, change the db version
47
46
#[ derive( Clone , Eq , Derivative ) ]
48
- #[ derivative( PartialEq , Hash ) ]
49
47
pub struct Entry {
50
- #[ derivative( PartialEq = "ignore" ) ]
51
- #[ derivative( Hash = "ignore" ) ]
52
48
pub creation : TimeId ,
53
-
54
- #[ derivative( PartialEq = "ignore" ) ]
55
- #[ derivative( Hash = "ignore" ) ]
56
49
pub mime : String ,
57
-
58
50
// todo: lazelly load image in memory, since we can't search them anyways
59
51
pub content : Vec < u8 > ,
60
-
61
- #[ derivative( PartialEq = "ignore" ) ]
62
- #[ derivative( Hash = "ignore" ) ]
63
52
/// (Mime, Content)
64
53
pub metadata : Option < EntryMetadata > ,
65
-
66
- #[ derivative( PartialEq = "ignore" ) ]
67
- #[ derivative( Hash = "ignore" ) ]
68
54
pub is_favorite : bool ,
69
55
}
70
56
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
+
71
69
#[ derive( Debug , Clone , Eq , PartialEq ) ]
72
70
pub struct EntryMetadata {
73
71
pub mime : String ,
@@ -228,8 +226,6 @@ pub struct Db {
228
226
query : String ,
229
227
needle : Option < Atom > ,
230
228
matcher : Matcher ,
231
- // time
232
- last_update : i64 ,
233
229
data_version : i64 ,
234
230
favorites : Favorites ,
235
231
}
@@ -275,6 +271,13 @@ impl Favorites {
275
271
fn fav ( & self ) -> & Vec < TimeId > {
276
272
& self . favorites
277
273
}
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
+ }
278
281
}
279
282
280
283
impl Db {
@@ -300,7 +303,11 @@ impl Db {
300
303
301
304
let mut conn = SqliteConnection :: connect ( db_path) . await ?;
302
305
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 ( ) ;
304
311
305
312
match sqlx:: migrate:: Migrator :: new ( migration_path) . await {
306
313
Ok ( migrator) => migrator,
@@ -352,7 +359,6 @@ impl Db {
352
359
query : String :: default ( ) ,
353
360
needle : None ,
354
361
matcher : Matcher :: new ( nucleo:: Config :: DEFAULT ) ,
355
- last_update : 0 ,
356
362
favorites : Favorites :: default ( ) ,
357
363
} ;
358
364
@@ -378,9 +384,9 @@ impl Db {
378
384
379
385
let mut max = 0 ;
380
386
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 ) ;
382
388
}
383
- assert ! ( max == self . favorite_len( ) - 1 ) ;
389
+ assert ! ( max == self . favorite_len( ) ) ;
384
390
}
385
391
386
392
{
@@ -467,18 +473,39 @@ impl Db {
467
473
// safe to unwrap since we insert before
468
474
let last_row = self . get_last_row ( ) . await ?. unwrap ( ) ;
469
475
476
+ let new_id = last_row. creation ;
477
+
470
478
let data_hash = data. get_hash ( ) ;
471
479
472
480
if let Some ( old_id) = self . hashs . remove ( & data_hash) {
473
481
self . state . remove ( & old_id) ;
474
482
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
+
475
502
// in case 2 same data were inserted in a short period
476
503
// we don't want to remove the old_id
477
- if last_row . creation != old_id {
504
+ if new_id != old_id {
478
505
let query_delete_old_id = r#"
479
- DELETE FROM ClipboardEntries
480
- WHERE creation = ?;
481
- "# ;
506
+ DELETE FROM ClipboardEntries
507
+ WHERE creation = ?;
508
+ "# ;
482
509
483
510
sqlx:: query ( query_delete_old_id)
484
511
. bind ( old_id)
@@ -487,11 +514,10 @@ impl Db {
487
514
}
488
515
}
489
516
490
- data. creation = last_row . creation ;
517
+ data. creation = new_id ;
491
518
492
519
self . hashs . insert ( data_hash, data. creation ) ;
493
520
self . state . insert ( data. creation , data) ;
494
- self . last_update = now_millis ( ) ;
495
521
496
522
self . search ( ) ;
497
523
Ok ( ( ) )
@@ -512,7 +538,6 @@ impl Db {
512
538
513
539
self . hashs . remove ( & data. get_hash ( ) ) ;
514
540
self . state . remove ( & data. creation ) ;
515
- self . last_update = now_millis ( ) ;
516
541
517
542
if data. is_favorite {
518
543
self . favorites . remove ( & data. creation ) ;
@@ -532,7 +557,6 @@ impl Db {
532
557
self . state . clear ( ) ;
533
558
self . filtered . clear ( ) ;
534
559
self . hashs . clear ( ) ;
535
- self . last_update = now_millis ( ) ;
536
560
self . favorites . clear ( ) ;
537
561
538
562
Ok ( ( ) )
@@ -552,10 +576,11 @@ impl Db {
552
576
sqlx:: query ( query)
553
577
. bind ( pos as i32 )
554
578
. execute ( & mut self . conn )
555
- . await ?;
579
+ . await
580
+ . unwrap ( ) ;
556
581
}
557
582
558
- let index = index. unwrap_or ( self . favorite_len ( ) ) ;
583
+ let index = index. unwrap_or ( self . favorite_len ( ) - 1 ) ;
559
584
560
585
{
561
586
let query = r#"
@@ -570,6 +595,10 @@ impl Db {
570
595
. await ?;
571
596
}
572
597
598
+ if let Some ( e) = self . state . get_mut ( & entry. creation ) {
599
+ e. is_favorite = true ;
600
+ }
601
+
573
602
Ok ( ( ) )
574
603
}
575
604
@@ -600,6 +629,9 @@ impl Db {
600
629
. await ?;
601
630
}
602
631
632
+ if let Some ( e) = self . state . get_mut ( & entry. creation ) {
633
+ e. is_favorite = false ;
634
+ }
603
635
Ok ( ( ) )
604
636
}
605
637
@@ -745,6 +777,8 @@ mod test {
745
777
746
778
use anyhow:: Result ;
747
779
use cosmic:: { iced_sctk:: util, widget:: canvas:: Path } ;
780
+ use tracing_subscriber:: { fmt, layer:: SubscriberExt , util:: SubscriberInitExt , EnvFilter } ;
781
+
748
782
749
783
use crate :: {
750
784
config:: Config ,
@@ -754,6 +788,16 @@ mod test {
754
788
use super :: { Db , Entry } ;
755
789
756
790
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
+
757
801
let db_dir = PathBuf :: from ( "tests" ) ;
758
802
let _ = std:: fs:: create_dir_all ( & db_dir) ;
759
803
remove_dir_contents ( & db_dir) ;
@@ -925,4 +969,86 @@ mod test {
925
969
db. insert ( data) . await . unwrap ( ) ;
926
970
assert ! ( db. len( ) == 2 ) ;
927
971
}
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
+ }
928
1054
}
0 commit comments