11use std:: {
22 collections:: HashMap ,
3+ fs:: Permissions ,
4+ os:: unix:: fs:: PermissionsExt ,
35 path:: { Path , PathBuf } ,
46} ;
57
@@ -11,7 +13,7 @@ use tokio::{
1113 io:: AsyncWriteExt ,
1214} ;
1315
14- use crate :: error:: DownloadError ;
16+ use crate :: { error:: DownloadError , utils :: is_elf } ;
1517
1618#[ derive( Deserialize ) ]
1719pub struct OciLayer {
@@ -41,43 +43,55 @@ pub struct OciManifest {
4143#[ derive( Clone ) ]
4244pub struct OciClient {
4345 client : reqwest:: Client ,
44- reference : Reference ,
46+ pub reference : Reference ,
4547}
4648
4749#[ derive( Clone ) ]
4850pub struct Reference {
49- package : String ,
50- tag : String ,
51+ pub package : String ,
52+ pub tag : String ,
5153}
5254
5355impl From < & str > for Reference {
5456 fn from ( value : & str ) -> Self {
5557 let paths = value. trim_start_matches ( "ghcr.io/" ) ;
56- let ( package, tag) = paths. split_once ( ':' ) . unwrap_or ( ( paths, "latest" ) ) ;
58+
59+ // <package>@sha256:<digest>
60+ if let Some ( ( package, digest) ) = paths. split_once ( "@" ) {
61+ return Self {
62+ package : package. to_string ( ) ,
63+ tag : digest. to_string ( ) ,
64+ } ;
65+ }
66+
67+ // <package>:<tag>
68+ if let Some ( ( package, tag) ) = paths. split_once ( ':' ) {
69+ return Self {
70+ package : package. to_string ( ) ,
71+ tag : tag. to_string ( ) ,
72+ } ;
73+ }
5774
5875 Self {
59- package : package . to_string ( ) ,
60- tag : tag . to_string ( ) ,
76+ package : paths . to_string ( ) ,
77+ tag : "latest" . to_string ( ) ,
6178 }
6279 }
6380}
6481
6582impl From < String > for Reference {
6683 fn from ( value : String ) -> Self {
67- let paths = value. trim_start_matches ( "ghcr.io/" ) ;
68- let ( package, tag) = paths. split_once ( ':' ) . unwrap_or ( ( paths, "latest" ) ) ;
69-
70- Self {
71- package : package. to_string ( ) ,
72- tag : tag. to_string ( ) ,
73- }
84+ value. as_str ( ) . into ( )
7485 }
7586}
7687
7788impl OciClient {
78- pub fn new ( reference : Reference ) -> Self {
89+ pub fn new ( reference : & Reference ) -> Self {
7990 let client = reqwest:: Client :: new ( ) ;
80- Self { client, reference }
91+ Self {
92+ client,
93+ reference : reference. clone ( ) ,
94+ }
8195 }
8296
8397 pub fn headers ( & self ) -> HeaderMap {
@@ -114,14 +128,15 @@ impl OciClient {
114128 Ok ( manifest)
115129 }
116130
117- pub async fn pull_layer < F , P : AsRef < Path > > (
131+ pub async fn pull_layer < F , P > (
118132 & self ,
119133 layer : & OciLayer ,
120- output_dir : Option < P > ,
134+ output_path : P ,
121135 progress_callback : F ,
122136 ) -> Result < u64 , DownloadError >
123137 where
124- F : Fn ( u64 ) + Send + ' static ,
138+ P : AsRef < Path > ,
139+ F : Fn ( u64 , u64 ) + Send + ' static ,
125140 {
126141 let blob_url = format ! (
127142 "https://ghcr.io/v2/{}/blobs/{}" ,
@@ -142,22 +157,11 @@ impl OciClient {
142157 } ) ;
143158 }
144159
145- let Some ( filename) = layer. get_title ( ) else {
146- // skip if layer doesn't contain title
147- return Ok ( 0 ) ;
148- } ;
149-
150- let ( temp_path, final_path) = if let Some ( output_dir) = output_dir {
151- let output_dir = output_dir. as_ref ( ) ;
152- fs:: create_dir_all ( output_dir) . await ?;
153- let final_path = output_dir. join ( format ! ( "{filename}" ) ) ;
154- let temp_path = output_dir. join ( format ! ( "{filename}.part" ) ) ;
155- ( temp_path, final_path)
156- } else {
157- let final_path = PathBuf :: from ( & filename) ;
158- let temp_path = PathBuf :: from ( format ! ( "{filename}.part" ) ) ;
159- ( temp_path, final_path)
160- } ;
160+ let content_length = resp. content_length ( ) . unwrap_or ( 0 ) ;
161+ progress_callback ( 0 , content_length) ;
162+
163+ let output_path = output_path. as_ref ( ) ;
164+ let temp_path = PathBuf :: from ( & format ! ( "{}.part" , output_path. display( ) ) ) ;
161165
162166 let mut file = OpenOptions :: new ( )
163167 . create ( true )
@@ -173,11 +177,15 @@ impl OciClient {
173177 let chunk_size = chunk. len ( ) as u64 ;
174178 file. write_all ( & chunk) . await . unwrap ( ) ;
175179
176- progress_callback ( chunk_size) ;
180+ progress_callback ( chunk_size, 0 ) ;
177181 total_bytes_downloaded += chunk_size;
178182 }
179183
180- fs:: rename ( & temp_path, & final_path) . await ?;
184+ fs:: rename ( & temp_path, & output_path) . await ?;
185+
186+ if is_elf ( & output_path) . await {
187+ fs:: set_permissions ( & output_path, Permissions :: from_mode ( 0o755 ) ) . await ?;
188+ }
181189
182190 Ok ( total_bytes_downloaded)
183191 }
@@ -189,4 +197,11 @@ impl OciLayer {
189197 . get ( "org.opencontainers.image.title" )
190198 . cloned ( )
191199 }
200+
201+ pub fn set_title ( & mut self , title : & str ) {
202+ self . annotations . insert (
203+ "org.opencontainers.image.title" . to_string ( ) ,
204+ title. to_string ( ) ,
205+ ) ;
206+ }
192207}
0 commit comments