@@ -131,12 +131,30 @@ def fs_printfile(self, src, chunk_size=256):
131131 raise _convert_filesystem_error (e , src ) from None
132132
133133 def fs_readfile (self , src , chunk_size = 256 , progress_callback = None ):
134+ """Read file data from device filesystem.
135+
136+ Args:
137+ src: Source path on device
138+ chunk_size: Size of chunks for transfer
139+ progress_callback: Optional callback(bytes_read, total_size)
140+
141+ Returns:
142+ Bytes read from file
143+
144+ Note:
145+ Compression for device→host transfers is not currently supported
146+ because many MicroPython builds don't include deflate compression
147+ (only decompression). The fs_writefile() function does support
148+ compression for host→device transfers.
149+ """
150+ # Get file size for progress callback
134151 if progress_callback :
135152 src_size = self .fs_stat (src ).st_size
136153
137154 contents = bytearray ()
138155
139156 try :
157+ # Standard uncompressed read
140158 self .exec ("f=open('%s','rb')\n r=f.read" % src )
141159 while True :
142160 chunk = self .eval ("r({})" .format (chunk_size ))
@@ -146,25 +164,91 @@ def fs_readfile(self, src, chunk_size=256, progress_callback=None):
146164 if progress_callback :
147165 progress_callback (len (contents ), src_size )
148166 self .exec ("f.close()" )
167+
149168 except TransportExecError as e :
150169 raise _convert_filesystem_error (e , src ) from None
151170
152171 return contents
153172
154- def fs_writefile (self , dest , data , chunk_size = 256 , progress_callback = None ):
173+ def fs_writefile (
174+ self , dest , data , chunk_size = 256 , progress_callback = None , use_compression = True
175+ ):
176+ """Write file data to device filesystem.
177+
178+ Args:
179+ dest: Destination path on device
180+ data: Bytes to write
181+ chunk_size: Size of chunks for transfer (increased to 4096 for compression)
182+ progress_callback: Optional callback(bytes_written, total_size)
183+ use_compression: Try to compress data if supported (default True)
184+ """
185+ from .compression_utils import compress_data , MIN_COMPRESS_SIZE , MIN_COMPRESS_RATIO
186+ import binascii
187+
188+ # Detect if device supports deflate compression
189+ supports_deflate = getattr (self , "supports_deflate" , None )
190+ if supports_deflate is None and hasattr (self , "_detect_deflate_support" ):
191+ supports_deflate = self ._detect_deflate_support ()
192+
193+ # Try compression if enabled, supported, and file is large enough
194+ compress = False
195+ if use_compression and supports_deflate and len (data ) >= MIN_COMPRESS_SIZE :
196+ try :
197+ compressed = compress_data (data )
198+ # Only use compression if it achieves meaningful reduction
199+ if len (compressed ) < len (data ) * MIN_COMPRESS_RATIO :
200+ original_size = len (data )
201+ data = compressed
202+ compress = True
203+ if hasattr (self , "verbose" ) and self .verbose :
204+ ratio = original_size / len (compressed )
205+ print (
206+ f"Compressing { dest } : { original_size } B → { len (compressed )} B ({ ratio :.1f} x)"
207+ )
208+ except Exception :
209+ pass # Fall back to uncompressed
210+
211+ # Use larger chunks when compressing (data is already compressed, less overhead)
212+ if compress and chunk_size < 4096 :
213+ chunk_size = 4096
214+
155215 if progress_callback :
156216 src_size = len (data )
157217 written = 0
158218
159219 try :
160- self .exec ("f=open('%s','wb')\n w=f.write" % dest )
220+ if compress :
221+ # Setup decompression on device side
222+ self .exec (
223+ "from deflate import DeflateIO,ZLIB\n "
224+ "from io import BytesIO\n "
225+ "import binascii\n "
226+ "f=open('%s','wb')" % dest
227+ )
228+ else :
229+ # Standard file write
230+ self .exec ("import binascii\n f=open('%s','wb')\n w=f.write" % dest )
231+
161232 while data :
162233 chunk = data [:chunk_size ]
163- self .exec ("w(" + repr (chunk ) + ")" )
234+ # Encode chunk as base64 (more efficient than repr)
235+ b64_chunk = binascii .b2a_base64 (chunk ).decode ("ascii" ).strip ()
236+
237+ if compress :
238+ # Decompress on device and write
239+ self .exec (
240+ "d=binascii.a2b_base64('%s')\n "
241+ "f.write(DeflateIO(BytesIO(d),ZLIB).read())" % b64_chunk
242+ )
243+ else :
244+ # Decode base64 and write directly
245+ self .exec ("w(binascii.a2b_base64('%s'))" % b64_chunk )
246+
164247 data = data [len (chunk ) :]
165248 if progress_callback :
166249 written += len (chunk )
167250 progress_callback (written , src_size )
251+
168252 self .exec ("f.close()" )
169253 except TransportExecError as e :
170254 raise _convert_filesystem_error (e , dest ) from None
0 commit comments