4
4
//! then encode the expiry date in the tag name after the prefix, in a format
5
5
//! that sorts in the same order as the expiry date.
6
6
//!
7
- //! Then we can just use
8
- use std:: time:: { Duration , SystemTime } ;
7
+ //! The example creates a number of blobs and protects them directly or indirectly
8
+ //! with expiring tags. Watch as the expired tags are deleted and the blobs
9
+ //! are removed from the store.
10
+ use std:: {
11
+ ops:: Deref ,
12
+ time:: { Duration , SystemTime } ,
13
+ } ;
9
14
10
15
use chrono:: Utc ;
11
16
use futures_lite:: StreamExt ;
12
- use iroh:: endpoint;
13
17
use iroh_blobs:: {
14
- hashseq:: HashSeq , rpc:: client:: blobs:: MemClient as BlobsClient , store:: GcConfig , BlobFormat ,
15
- Hash , HashAndFormat , Tag ,
18
+ api:: { blobs:: AddBytesOptions , Store , Tag } ,
19
+ hashseq:: HashSeq ,
20
+ store:: fs:: options:: { BatchOptions , GcConfig , InlineOptions , Options , PathOptions } ,
21
+ BlobFormat , Hash ,
16
22
} ;
17
23
use tokio:: signal:: ctrl_c;
18
24
19
25
/// Using an iroh rpc client, create a tag that is marked to expire at `expiry` for all the given hashes.
20
26
///
21
27
/// The tag name will be `prefix`- followed by the expiry date in iso8601 format (e.g. `expiry-2025-01-01T12:00:00Z`).
22
28
async fn create_expiring_tag (
23
- iroh : & BlobsClient ,
29
+ store : & Store ,
24
30
hashes : & [ Hash ] ,
25
31
prefix : & str ,
26
32
expiry : SystemTime ,
27
33
) -> anyhow:: Result < ( ) > {
28
34
let expiry = chrono:: DateTime :: < chrono:: Utc > :: from ( expiry) ;
29
35
let expiry = expiry. to_rfc3339_opts ( chrono:: SecondsFormat :: Secs , true ) ;
30
- let tagname = format ! ( "{}-{}" , prefix, expiry) ;
31
- let batch = iroh. batch ( ) . await ?;
32
- let tt = if hashes. is_empty ( ) {
36
+ let tagname = format ! ( "{prefix}-{expiry}" ) ;
37
+ if hashes. is_empty ( ) {
33
38
return Ok ( ( ) ) ;
34
39
} else if hashes. len ( ) == 1 {
35
40
let hash = hashes[ 0 ] ;
36
- batch . temp_tag ( HashAndFormat :: raw ( hash) ) . await ?
41
+ store . tags ( ) . set ( & tagname , hash) . await ?;
37
42
} else {
38
43
let hs = hashes. iter ( ) . copied ( ) . collect :: < HashSeq > ( ) ;
39
- batch
40
- . add_bytes_with_opts ( hs. into_inner ( ) , BlobFormat :: HashSeq )
41
- . await ?
44
+ store
45
+ . add_bytes_with_opts ( AddBytesOptions {
46
+ data : hs. into ( ) ,
47
+ format : BlobFormat :: HashSeq ,
48
+ } )
49
+ . with_named_tag ( & tagname)
50
+ . await ?;
42
51
} ;
43
- batch. persist_to ( tt, tagname. as_str ( ) . into ( ) ) . await ?;
44
- println ! ( "Created tag {}" , tagname) ;
52
+ println ! ( "Created tag {tagname}" ) ;
45
53
Ok ( ( ) )
46
54
}
47
55
48
- async fn delete_expired_tags ( blobs : & BlobsClient , prefix : & str , bulk : bool ) -> anyhow:: Result < ( ) > {
49
- let mut tags = blobs. tags ( ) . list ( ) . await ?;
50
- let prefix = format ! ( "{}-" , prefix) ;
56
+ async fn delete_expired_tags ( blobs : & Store , prefix : & str , bulk : bool ) -> anyhow:: Result < ( ) > {
57
+ let prefix = format ! ( "{prefix}-" ) ;
51
58
let now = chrono:: Utc :: now ( ) ;
52
59
let end = format ! (
53
60
"{}-{}" ,
@@ -66,6 +73,7 @@ async fn delete_expired_tags(blobs: &BlobsClient, prefix: &str, bulk: bool) -> a
66
73
// find tags to delete one by one and then delete them
67
74
//
68
75
// this allows us to print the tags before deleting them
76
+ let mut tags = blobs. tags ( ) . list ( ) . await ?;
69
77
let mut to_delete = Vec :: new ( ) ;
70
78
while let Some ( tag) = tags. next ( ) . await {
71
79
let tag = tag?. name ;
@@ -85,99 +93,93 @@ async fn delete_expired_tags(blobs: &BlobsClient, prefix: &str, bulk: bool) -> a
85
93
}
86
94
}
87
95
for tag in to_delete {
88
- println ! ( "Deleting expired tag {}" , tag ) ;
96
+ println ! ( "Deleting expired tag {tag} \n " ) ;
89
97
blobs. tags ( ) . delete ( tag) . await ?;
90
98
}
91
99
}
92
100
Ok ( ( ) )
93
101
}
94
102
95
- async fn info_task ( blobs : BlobsClient ) -> anyhow:: Result < ( ) > {
103
+ async fn print_store_info ( store : & Store ) -> anyhow:: Result < ( ) > {
104
+ let now = chrono:: Utc :: now ( ) ;
105
+ let mut tags = store. tags ( ) . list ( ) . await ?;
106
+ println ! (
107
+ "Current time: {}" ,
108
+ now. to_rfc3339_opts( chrono:: SecondsFormat :: Secs , true )
109
+ ) ;
110
+ println ! ( "Tags:" ) ;
111
+ while let Some ( tag) = tags. next ( ) . await {
112
+ let tag = tag?;
113
+ println ! ( " {tag:?}" ) ;
114
+ }
115
+ let mut blobs = store. list ( ) . stream ( ) . await ?;
116
+ println ! ( "Blobs:" ) ;
117
+ while let Some ( item) = blobs. next ( ) . await {
118
+ println ! ( " {}" , item?) ;
119
+ }
120
+ println ! ( ) ;
121
+ Ok ( ( ) )
122
+ }
123
+
124
+ async fn info_task ( store : Store ) -> anyhow:: Result < ( ) > {
96
125
tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
97
126
loop {
98
- let now = chrono:: Utc :: now ( ) ;
99
- let mut tags = blobs. tags ( ) . list ( ) . await ?;
100
- println ! (
101
- "Current time: {}" ,
102
- now. to_rfc3339_opts( chrono:: SecondsFormat :: Secs , true )
103
- ) ;
104
- println ! ( "Tags:" ) ;
105
- while let Some ( tag) = tags. next ( ) . await {
106
- let tag = tag?;
107
- println ! ( " {:?}" , tag) ;
108
- }
109
- let mut blobs = blobs. list ( ) . await ?;
110
- println ! ( "Blobs:" ) ;
111
- while let Some ( info) = blobs. next ( ) . await {
112
- let info = info?;
113
- println ! ( " {} {} bytes" , info. hash, info. size) ;
114
- }
115
- println ! ( ) ;
127
+ print_store_info ( & store) . await ?;
116
128
tokio:: time:: sleep ( Duration :: from_secs ( 5 ) ) . await ;
117
129
}
118
130
}
119
131
120
- async fn delete_expired_tags_task ( blobs : BlobsClient , prefix : & str ) -> anyhow:: Result < ( ) > {
132
+ async fn delete_expired_tags_task ( store : Store , prefix : & str ) -> anyhow:: Result < ( ) > {
121
133
loop {
122
- delete_expired_tags ( & blobs , prefix, false ) . await ?;
134
+ delete_expired_tags ( & store , prefix, false ) . await ?;
123
135
tokio:: time:: sleep ( Duration :: from_secs ( 5 ) ) . await ;
124
136
}
125
137
}
126
138
127
139
#[ tokio:: main]
128
140
async fn main ( ) -> anyhow:: Result < ( ) > {
129
141
tracing_subscriber:: fmt:: init ( ) ;
130
- let endpoint = endpoint:: Endpoint :: builder ( ) . bind ( ) . await ?;
131
- let store = iroh_blobs:: store:: fs:: Store :: load ( "blobs" ) . await ?;
132
- let blobs = iroh_blobs:: net_protocol:: Blobs :: builder ( store) . build ( & endpoint) ;
133
- // enable gc with a short period
134
- blobs. start_gc ( GcConfig {
135
- period : Duration :: from_secs ( 1 ) ,
136
- done_callback : None ,
137
- } ) ?;
138
- // create a router and add blobs as a service
139
- //
140
- // You can skip this if you don't want to serve the data over the network.
141
- let router = iroh:: protocol:: Router :: builder ( endpoint)
142
- . accept ( iroh_blobs:: ALPN , blobs. clone ( ) )
143
- . spawn ( )
144
- . await ?;
142
+ let path = std:: env:: current_dir ( ) ?. join ( "blobs" ) ;
143
+ let options = Options {
144
+ path : PathOptions :: new ( & path) ,
145
+ gc : Some ( GcConfig {
146
+ interval : Duration :: from_secs ( 10 ) ,
147
+ } ) ,
148
+ inline : InlineOptions :: default ( ) ,
149
+ batch : BatchOptions :: default ( ) ,
150
+ } ;
151
+ let store =
152
+ iroh_blobs:: store:: fs:: FsStore :: load_with_opts ( path. join ( "blobs.db" ) , options) . await ?;
145
153
146
154
// setup: add some data and tag it
147
155
{
148
156
// add several blobs and tag them with an expiry date 10 seconds in the future
149
- let batch = blobs . client ( ) . batch ( ) . await ?;
157
+ let batch = store . batch ( ) . await ?;
150
158
let a = batch. add_bytes ( "blob 1" . as_bytes ( ) ) . await ?;
151
159
let b = batch. add_bytes ( "blob 2" . as_bytes ( ) ) . await ?;
160
+
152
161
let expires_at = SystemTime :: now ( )
153
162
. checked_add ( Duration :: from_secs ( 10 ) )
154
163
. unwrap ( ) ;
155
- create_expiring_tag (
156
- blobs. client ( ) ,
157
- & [ * a. hash ( ) , * b. hash ( ) ] ,
158
- "expiring" ,
159
- expires_at,
160
- )
161
- . await ?;
164
+ create_expiring_tag ( & store, & [ * a. hash ( ) , * b. hash ( ) ] , "expiring" , expires_at) . await ?;
162
165
163
166
// add a single blob and tag it with an expiry date 60 seconds in the future
164
167
let c = batch. add_bytes ( "blob 3" . as_bytes ( ) ) . await ?;
165
168
let expires_at = SystemTime :: now ( )
166
169
. checked_add ( Duration :: from_secs ( 60 ) )
167
170
. unwrap ( ) ;
168
- create_expiring_tag ( blobs . client ( ) , & [ * c. hash ( ) ] , "expiring" , expires_at) . await ?;
171
+ create_expiring_tag ( & store , & [ * c. hash ( ) ] , "expiring" , expires_at) . await ?;
169
172
// batch goes out of scope, so data is only protected by the tags we created
170
173
}
171
- let client = blobs. client ( ) . clone ( ) ;
172
174
173
175
// delete expired tags every 5 seconds
174
- let delete_task = tokio:: spawn ( delete_expired_tags_task ( client . clone ( ) , "expiring" ) ) ;
176
+ let delete_task = tokio:: spawn ( delete_expired_tags_task ( store . deref ( ) . clone ( ) , "expiring" ) ) ;
175
177
// print all tags and blobs every 5 seconds
176
- let info_task = tokio:: spawn ( info_task ( client . clone ( ) ) ) ;
178
+ let info_task = tokio:: spawn ( info_task ( store . deref ( ) . clone ( ) ) ) ;
177
179
178
180
ctrl_c ( ) . await ?;
179
181
delete_task. abort ( ) ;
180
182
info_task. abort ( ) ;
181
- router . shutdown ( ) . await ?;
183
+ store . shutdown ( ) . await ?;
182
184
Ok ( ( ) )
183
185
}
0 commit comments