@@ -43,9 +43,7 @@ class FileServer:
43
43
The lifetime of this instance matches the lifetime of its node.
44
44
"""
45
45
46
- def __init__ (
47
- self , node : pycyphal .application .Node , roots : typing .Iterable [typing .Union [str , pathlib .Path ]]
48
- ) -> None :
46
+ def __init__ (self , node : pycyphal .application .Node , roots : typing .Iterable [str | pathlib .Path ]) -> None :
49
47
"""
50
48
:param node:
51
49
The node instance to initialize the file server on.
@@ -85,7 +83,7 @@ def close() -> None:
85
83
node .add_lifetime_hooks (start , close )
86
84
87
85
@property
88
- def roots (self ) -> typing . List [pathlib .Path ]:
86
+ def roots (self ) -> list [pathlib .Path ]:
89
87
"""
90
88
File operations will be performed within these root directories.
91
89
The first directory to match takes precedence.
@@ -94,7 +92,7 @@ def roots(self) -> typing.List[pathlib.Path]:
94
92
"""
95
93
return self ._roots
96
94
97
- def locate (self , p : typing . Union [ pathlib .Path , str , Path ] ) -> typing . Tuple [pathlib .Path , pathlib .Path ]:
95
+ def locate (self , p : pathlib .Path | str | Path ) -> tuple [pathlib .Path , pathlib .Path ]:
98
96
"""
99
97
Iterate through :attr:`roots` until a root r is found such that ``r/p`` exists and return ``(r, p)``.
100
98
Otherwise, return nonexistent ``(roots[0], p)``.
@@ -413,7 +411,7 @@ async def move(self, src: str, dst: str, overwrite: bool = False) -> int:
413
411
assert isinstance (res , Modify .Response )
414
412
return int (res .error .value )
415
413
416
- async def read (self , path : str , offset : int = 0 , size : typing . Optional [ int ] = None ) -> typing . Union [ int , bytes ] :
414
+ async def read (self , path : str , offset : int = 0 , size : int | None = None ) -> int | bytes :
417
415
"""
418
416
Proxy for ``uavcan.file.Read``.
419
417
@@ -434,7 +432,7 @@ async def read(self, path: str, offset: int = 0, size: typing.Optional[int] = No
434
432
data on success (empty if the offset is out of bounds or the file is empty).
435
433
"""
436
434
437
- async def once () -> typing . Union [ int , bytes ] :
435
+ async def once () -> int | bytes :
438
436
res = await self ._call (Read , Read .Request (offset = offset , path = Path (path )))
439
437
assert isinstance (res , Read .Response )
440
438
if res .error .value != 0 :
@@ -455,9 +453,7 @@ async def once() -> typing.Union[int, bytes]:
455
453
offset += len (out )
456
454
return data
457
455
458
- async def write (
459
- self , path : str , data : typing .Union [memoryview , bytes ], offset : int = 0 , * , truncate : bool = True
460
- ) -> int :
456
+ async def write (self , path : str , data : memoryview | bytes , offset : int = 0 , * , truncate : bool = True ) -> int :
461
457
"""
462
458
Proxy for ``uavcan.file.Write``.
463
459
@@ -479,7 +475,7 @@ async def write(
479
475
:returns: See ``uavcan.file.Error``
480
476
"""
481
477
482
- async def once (d : typing . Union [ memoryview , bytes ] ) -> int :
478
+ async def once (d : memoryview | bytes ) -> int :
483
479
res = await self ._call (
484
480
Write ,
485
481
Write .Request (offset , path = Path (path ), data = Unstructured (np .frombuffer (d , np .uint8 ))),
@@ -658,7 +654,13 @@ async def move(self, src: str, dst: str, overwrite: bool = False) -> None:
658
654
assert isinstance (res , Modify .Response )
659
655
_raise_on_error (res .error , f"{ src } ->{ dst } " )
660
656
661
- async def read (self , path : str , offset : int = 0 , size : typing .Optional [int ] = None ) -> bytes :
657
+ async def read (
658
+ self ,
659
+ path : str ,
660
+ offset : int = 0 ,
661
+ size : int | None = None ,
662
+ progress : typing .Callable [[int , int | None ], None ] | None = None ,
663
+ ) -> bytes :
662
664
"""
663
665
Proxy for ``uavcan.file.Read``.
664
666
@@ -674,6 +676,10 @@ async def read(self, path: str, offset: int = 0, size: typing.Optional[int] = No
674
676
If None (default), the entire file will be read (this may exhaust local memory).
675
677
If zero, this call is a no-op.
676
678
679
+ :param progress:
680
+ Optional callback function that receives (bytes_read, total_size)
681
+ total_size will be None if size parameter is None
682
+
677
683
:raises OSError: If the read operation failed; see ``uavcan.file.Error``
678
684
679
685
:returns:
@@ -686,20 +692,26 @@ async def once() -> bytes:
686
692
_raise_on_error (res .error , path )
687
693
return bytes (res .data .value .tobytes ())
688
694
689
- if size is None :
690
- size = 2 ** 64
691
695
data = b""
692
- while len (data ) < size :
696
+ while len (data ) < ( size or 2 ** 64 ) :
693
697
out = await once ()
694
698
assert isinstance (out , bytes )
695
699
if not out :
696
700
break
697
701
data += out
698
702
offset += len (out )
703
+ if progress :
704
+ progress (len (data ), size )
699
705
return data
700
706
701
707
async def write (
702
- self , path : str , data : typing .Union [memoryview , bytes ], offset : int = 0 , * , truncate : bool = True
708
+ self ,
709
+ path : str ,
710
+ data : memoryview | bytes ,
711
+ offset : int = 0 ,
712
+ * ,
713
+ truncate : bool = True ,
714
+ progress : typing .Callable [[int , int ], None ] | None = None ,
703
715
) -> None :
704
716
"""
705
717
Proxy for ``uavcan.file.Write``.
@@ -719,22 +731,30 @@ async def write(
719
731
If True, the rest of the file after ``offset + len(data)`` will be truncated.
720
732
This is done by sending an empty write request, as prescribed by the Specification.
721
733
734
+ :param progress:
735
+ Optional callback function that receives (bytes_written, total_size)
736
+
722
737
:raises OSError: If the write operation failed; see ``uavcan.file.Error``
723
738
"""
724
739
725
- async def once (d : typing . Union [ memoryview , bytes ] ) -> None :
740
+ async def once (d : memoryview | bytes ) -> None :
726
741
res = await self ._call (
727
742
Write ,
728
743
Write .Request (offset , path = Path (path ), data = Unstructured (np .frombuffer (d , np .uint8 ))),
729
744
)
730
745
assert isinstance (res , Write .Response )
731
746
_raise_on_error (res .error , path )
732
747
748
+ total_size = len (data )
749
+ bytes_written = 0
733
750
limit = self .data_transfer_capacity
734
751
while len (data ) > 0 :
735
752
frag , data = data [:limit ], data [limit :]
736
753
await once (frag )
737
754
offset += len (frag )
755
+ bytes_written += len (frag )
756
+ if progress :
757
+ progress (bytes_written , total_size )
738
758
if truncate :
739
759
await once (b"" )
740
760
0 commit comments