@@ -5,19 +5,10 @@ use pyo3::exceptions::PyRuntimeError;
55use pyo3:: prelude:: * ;
66use pyo3:: types:: { PyAny , PyType } ;
77use std:: fs:: File ;
8- use std:: io:: Write ;
8+ use std:: io:: { Read , Write } ;
99use std:: path:: { Path , PathBuf } ;
1010use tar:: Archive ;
1111
12- #[ pyfunction]
13- fn untar_gz ( tar_gz_path : String , destination_path : String ) -> PyResult < ( ) > {
14- let tar_gz = File :: open ( tar_gz_path) ?;
15- let tar = GzDecoder :: new ( tar_gz) ;
16- let mut archive = Archive :: new ( tar) ;
17- archive. unpack ( destination_path) ?;
18- Ok ( ( ) )
19- }
20-
2112#[ pyclass]
2213struct ArchiveWriter {
2314 builder : Option < tar:: Builder < Box < dyn Write + Send + Sync > > > ,
@@ -118,6 +109,81 @@ impl ArchiveWriter {
118109 }
119110}
120111
112+ #[ pyclass( unsendable) ]
113+ struct ArchiveReader {
114+ archive : Option < Archive < Box < dyn Read > > > ,
115+ }
116+
117+ #[ pymethods]
118+ impl ArchiveReader {
119+ #[ classmethod]
120+ #[ pyo3( signature = ( path, mode="r:gz" ) ) ]
121+ fn open (
122+ _cls : & Bound < ' _ , PyType > ,
123+ py : Python < ' _ > ,
124+ path : PathBuf ,
125+ mode : & str ,
126+ ) -> PyResult < Py < ArchiveReader > > {
127+ match mode {
128+ "r:gz" => {
129+ let file = File :: open ( path) ?;
130+ let decoder = GzDecoder :: new ( file) ;
131+ let reader: Box < dyn Read > = Box :: new ( decoder) ;
132+ let archive = Archive :: new ( reader) ;
133+ Py :: new (
134+ py,
135+ ArchiveReader {
136+ archive : Some ( archive) ,
137+ } ,
138+ )
139+ }
140+ "r" => {
141+ let file = File :: open ( path) ?;
142+ let reader: Box < dyn Read > = Box :: new ( file) ;
143+ let archive = Archive :: new ( reader) ;
144+ Py :: new (
145+ py,
146+ ArchiveReader {
147+ archive : Some ( archive) ,
148+ } ,
149+ )
150+ }
151+ _ => Err ( PyRuntimeError :: new_err (
152+ "unsupported mode; only 'r' and 'r:gz' are supported" ,
153+ ) ) ,
154+ }
155+ }
156+
157+ fn extract ( & mut self , to : PathBuf ) -> PyResult < ( ) > {
158+ let archive = self
159+ . archive
160+ . as_mut ( )
161+ . ok_or_else ( || PyRuntimeError :: new_err ( "archive is already closed" ) ) ?;
162+
163+ archive. unpack ( to) ?;
164+ Ok ( ( ) )
165+ }
166+
167+ fn close ( & mut self ) -> PyResult < ( ) > {
168+ self . archive . take ( ) ;
169+ Ok ( ( ) )
170+ }
171+
172+ fn __enter__ ( py_self : PyRef < ' _ , Self > ) -> PyRef < ' _ , Self > {
173+ py_self
174+ }
175+
176+ fn __exit__ (
177+ & mut self ,
178+ _exc_type : Option < Bound < ' _ , PyAny > > ,
179+ _exc : Option < Bound < ' _ , PyAny > > ,
180+ _tb : Option < Bound < ' _ , PyAny > > ,
181+ ) -> PyResult < bool > {
182+ self . close ( ) ?;
183+ Ok ( false ) // Propagate exceptions if any
184+ }
185+ }
186+
121187#[ pyfunction]
122188#[ pyo3( signature = ( path, mode) ) ]
123189fn open ( py : Python < ' _ > , path : PathBuf , mode : & str ) -> PyResult < PyObject > {
@@ -126,16 +192,20 @@ fn open(py: Python<'_>, path: PathBuf, mode: &str) -> PyResult<PyObject> {
126192 let writer = ArchiveWriter :: open ( & py. get_type :: < ArchiveWriter > ( ) , py, path, mode) ?;
127193 Ok ( writer. into ( ) )
128194 }
195+ "r" | "r:gz" => {
196+ let reader = ArchiveReader :: open ( & py. get_type :: < ArchiveReader > ( ) , py, path, mode) ?;
197+ Ok ( reader. into ( ) )
198+ }
129199 _ => Err ( PyRuntimeError :: new_err (
130- "unsupported mode; supported modes are 'w', 'w:gz'" ,
200+ "unsupported mode; supported modes are 'w', 'w:gz', 'r', 'r:gz' " ,
131201 ) ) ,
132202 }
133203}
134204
135205#[ pymodule]
136206fn fastar ( m : & Bound < ' _ , PyModule > ) -> PyResult < ( ) > {
137207 m. add_class :: < ArchiveWriter > ( ) ?;
208+ m. add_class :: < ArchiveReader > ( ) ?;
138209 m. add_function ( wrap_pyfunction ! ( open, m) ?) ?;
139- m. add_function ( wrap_pyfunction ! ( untar_gz, m) ?) ?;
140210 Ok ( ( ) )
141211}
0 commit comments