11use flate2:: read:: GzDecoder ;
2+ use flate2:: write:: GzEncoder ;
3+ use flate2:: Compression ;
4+ use pyo3:: exceptions:: PyRuntimeError ;
25use pyo3:: prelude:: * ;
6+ use pyo3:: types:: { PyAny , PyType } ;
37use std:: fs:: File ;
8+ use std:: io:: Write ;
9+ use std:: path:: { Path , PathBuf } ;
410use tar:: Archive ;
511
612#[ pyfunction]
@@ -12,8 +18,124 @@ fn untar_gz(tar_gz_path: String, destination_path: String) -> PyResult<()> {
1218 Ok ( ( ) )
1319}
1420
21+ #[ pyclass]
22+ struct ArchiveWriter {
23+ builder : Option < tar:: Builder < Box < dyn Write + Send + Sync > > > ,
24+ }
25+
26+ #[ pymethods]
27+ impl ArchiveWriter {
28+ #[ classmethod]
29+ #[ pyo3( signature = ( path, mode="w:gz" ) ) ]
30+ fn open (
31+ _cls : & Bound < ' _ , PyType > ,
32+ py : Python < ' _ > ,
33+ path : PathBuf ,
34+ mode : & str ,
35+ ) -> PyResult < Py < ArchiveWriter > > {
36+ match mode {
37+ "w:gz" => {
38+ let file = File :: create ( path) ?;
39+ let enc = GzEncoder :: new ( file, Compression :: default ( ) ) ;
40+ let writer: Box < dyn Write + Send + Sync > = Box :: new ( enc) ;
41+ let builder = tar:: Builder :: new ( writer) ;
42+ Py :: new (
43+ py,
44+ ArchiveWriter {
45+ builder : Some ( builder) ,
46+ } ,
47+ )
48+ }
49+ "w" => {
50+ let file = File :: create ( path) ?;
51+ let writer: Box < dyn Write + Send + Sync > = Box :: new ( file) ;
52+ let builder = tar:: Builder :: new ( writer) ;
53+ Py :: new (
54+ py,
55+ ArchiveWriter {
56+ builder : Some ( builder) ,
57+ } ,
58+ )
59+ }
60+ _ => Err ( PyRuntimeError :: new_err (
61+ "unsupported mode; only 'w' and 'w:gz' are supported" ,
62+ ) ) ,
63+ }
64+ }
65+
66+ #[ pyo3( signature = ( path, arcname=None , recursive=true ) ) ]
67+ fn add ( & mut self , path : PathBuf , arcname : Option < String > , recursive : bool ) -> PyResult < ( ) > {
68+ let builder = self
69+ . builder
70+ . as_mut ( )
71+ . ok_or_else ( || PyRuntimeError :: new_err ( "archive is already closed" ) ) ?;
72+
73+ let default_name = || -> PyResult < String > {
74+ let name = Path :: new ( & path)
75+ . file_name ( )
76+ . ok_or_else ( || PyRuntimeError :: new_err ( "cannot derive name from path" ) ) ?
77+ . to_string_lossy ( )
78+ . into_owned ( ) ;
79+ Ok ( name)
80+ } ( ) ?;
81+
82+ let name = arcname. unwrap_or ( default_name) ;
83+
84+ if path. is_dir ( ) {
85+ if recursive {
86+ builder. append_dir_all ( & name, & path) ?;
87+ } else {
88+ builder. append_dir ( & name, & path) ?;
89+ }
90+ } else if path. is_file ( ) {
91+ builder. append_path_with_name ( & path, & name) ?;
92+ } else {
93+ return Err ( PyRuntimeError :: new_err ( "path does not exist" ) ) ;
94+ }
95+ Ok ( ( ) )
96+ }
97+
98+ fn close ( & mut self ) -> PyResult < ( ) > {
99+ if let Some ( builder) = self . builder . take ( ) {
100+ let mut writer = builder. into_inner ( ) ?;
101+ writer. flush ( ) ?;
102+ }
103+ Ok ( ( ) )
104+ }
105+
106+ fn __enter__ ( py_self : PyRef < ' _ , Self > ) -> PyRef < ' _ , Self > {
107+ py_self
108+ }
109+
110+ fn __exit__ (
111+ & mut self ,
112+ _exc_type : Option < Bound < ' _ , PyAny > > ,
113+ _exc : Option < Bound < ' _ , PyAny > > ,
114+ _tb : Option < Bound < ' _ , PyAny > > ,
115+ ) -> PyResult < bool > {
116+ self . close ( ) ?;
117+ Ok ( false ) // Propagate exceptions if any
118+ }
119+ }
120+
121+ #[ pyfunction]
122+ #[ pyo3( signature = ( path, mode) ) ]
123+ fn open ( py : Python < ' _ > , path : PathBuf , mode : & str ) -> PyResult < PyObject > {
124+ match mode {
125+ "w" | "w:gz" => {
126+ let writer = ArchiveWriter :: open ( & py. get_type :: < ArchiveWriter > ( ) , py, path, mode) ?;
127+ Ok ( writer. into ( ) )
128+ }
129+ _ => Err ( PyRuntimeError :: new_err (
130+ "unsupported mode; supported modes are 'w', 'w:gz'" ,
131+ ) ) ,
132+ }
133+ }
134+
15135#[ pymodule]
16136fn fastar ( m : & Bound < ' _ , PyModule > ) -> PyResult < ( ) > {
137+ m. add_class :: < ArchiveWriter > ( ) ?;
138+ m. add_function ( wrap_pyfunction ! ( open, m) ?) ?;
17139 m. add_function ( wrap_pyfunction ! ( untar_gz, m) ?) ?;
18140 Ok ( ( ) )
19141}
0 commit comments