1+ import 'dart:io' ;
12import 'dart:math' ;
23
4+ import 'package:async/async.dart' ;
5+ import 'package:collection/collection.dart' ;
36import 'package:fixnum/fixnum.dart' ;
4- import 'package:viam_sdk/src/gen/google/protobuf/timestamp .pb.dart' ;
7+ import 'package:viam_sdk/src/gen/google/protobuf/any .pb.dart' ;
58
69import '../gen/app/data/v1/data.pbgrpc.dart' ;
10+ import '../gen/app/datasync/v1/data_sync.pbgrpc.dart' hide CaptureInterval;
11+ import '../gen/google/protobuf/timestamp.pb.dart' ;
12+ import '../media/image.dart' ;
713
814/// gRPC client for the [DataClient] . Used for retrieving stored data from app.viam.com.
915///
1016/// All calls must be authenticated.
1117class DataClient {
12- final DataServiceClient _client;
18+ final DataServiceClient _dataClient;
19+ final DataSyncServiceClient _dataSyncClient;
1320
14- DataClient (this ._client );
21+ DataClient (this ._dataClient, this ._dataSyncClient );
1522
1623 DataRequest _makeDataRequest (Filter ? filter, int ? limit, String ? last, Order ? sortOrder) {
1724 final dataRequest = DataRequest ();
@@ -37,7 +44,7 @@ class DataClient {
3744 final request = TabularDataByFilterRequest ()
3845 ..dataRequest = dataRequest
3946 ..countOnly = true ;
40- return await _client .tabularDataByFilter (request);
47+ return await _dataClient .tabularDataByFilter (request);
4148 }
4249
4350 final finalResponse = TabularDataByFilterResponse ();
@@ -49,7 +56,7 @@ class DataClient {
4956 ..dataRequest = dataRequest
5057 ..countOnly = false ;
5158
52- final response = await _client .tabularDataByFilter (request);
59+ final response = await _dataClient .tabularDataByFilter (request);
5360
5461 if (response.count == 0 ) {
5562 break ;
@@ -73,7 +80,7 @@ class DataClient {
7380 final request = BinaryDataByFilterRequest ()
7481 ..dataRequest = dataRequest
7582 ..countOnly = true ;
76- return await _client .binaryDataByFilter (request);
83+ return await _dataClient .binaryDataByFilter (request);
7784 }
7885
7986 final finalResponse = BinaryDataByFilterResponse ();
@@ -85,7 +92,7 @@ class DataClient {
8592 ..dataRequest = dataRequest
8693 ..countOnly = false ;
8794
88- final response = await _client .binaryDataByFilter (request);
95+ final response = await _dataClient .binaryDataByFilter (request);
8996
9097 if (response.count == 0 ) {
9198 break ;
@@ -102,9 +109,83 @@ class DataClient {
102109 /// Retrieve binary data by IDs
103110 Future <List <BinaryData >> binaryDataByIds (List <BinaryID > binaryIds) async {
104111 final request = BinaryDataByIDsRequest ()..binaryIds.addAll (binaryIds);
105- final response = await _client .binaryDataByIDs (request);
112+ final response = await _dataClient .binaryDataByIDs (request);
106113 return response.data;
107114 }
115+
116+ /// Upload an image to Viam's Data Manager
117+ ///
118+ /// If no name is provided, the current timestamp will be used as the filename.
119+ Future <String > uploadImage (ViamImage image, String partId,
120+ {String ? fileName,
121+ String ? componentType,
122+ String ? componentName,
123+ String ? methodName,
124+ Map <String , Any >? methodParameters,
125+ Iterable <String > tags = const []}) async {
126+ final metadata = UploadMetadata ()
127+ ..partId = partId
128+ ..type = DataType .DATA_TYPE_FILE
129+ ..fileName = fileName ?? DateTime .now ().toIso8601String ()
130+ ..fileExtension = '.${image .mimeType .type }'
131+ ..tags.addAll (tags);
132+ if (componentType != null ) metadata.componentType = componentType;
133+ if (componentName != null ) metadata.componentName = componentName;
134+ if (methodName != null ) metadata.methodName = methodName;
135+ if (methodParameters != null ) metadata.methodParameters.addAll (methodParameters);
136+ final metadataRequest = FileUploadRequest ()..metadata = metadata;
137+
138+ // Make requests that are at most 2MB large (max gRPC request size is 4MB)
139+ final dataRequests = image.raw.slices (2 * 1024 * 1024 ).map ((e) => FileUploadRequest ()..fileContents = (FileData ()..data = e));
140+
141+ final requestStream = Stream .fromIterable ([metadataRequest, ...dataRequests]);
142+ final response = await _dataSyncClient.fileUpload (requestStream);
143+ return response.fileId;
144+ }
145+
146+ /// Upload a file from its path to Viam's Data Manager
147+ ///
148+ /// The file name can be overridden by providing the [fileName] parameter.
149+ Future <String > uploadFile (String path, String partId,
150+ {String ? fileName,
151+ String ? componentType,
152+ String ? componentName,
153+ String ? methodName,
154+ Map <String , Any >? methodParameters,
155+ Iterable <String > tags = const []}) async {
156+ final fileNameAndExt = path.split (Platform .pathSeparator).last;
157+ String fName, ext;
158+ if (fileNameAndExt.contains ('.' )) {
159+ fName = (fileNameAndExt.split ('.' )..removeLast ()).join ('.' );
160+ ext = '.${fileNameAndExt .split ('.' ).last }' ;
161+ } else {
162+ fName = fileNameAndExt;
163+ ext = '' ;
164+ }
165+ final metadata = UploadMetadata ()
166+ ..partId = partId
167+ ..type = DataType .DATA_TYPE_FILE
168+ ..fileName = fileName ?? fName
169+ ..fileExtension = ext
170+ ..tags.addAll (tags);
171+ if (componentType != null ) metadata.componentType = componentType;
172+ if (componentName != null ) metadata.componentName = componentName;
173+ if (methodName != null ) metadata.methodName = methodName;
174+ if (methodParameters != null ) metadata.methodParameters.addAll (methodParameters);
175+ final metadataStream = Stream .value (FileUploadRequest ()..metadata = metadata);
176+
177+ final file = File (path);
178+ final reader = ChunkedStreamReader (file.openRead ());
179+ try {
180+ final fileDataStream =
181+ reader.readStream (file.lengthSync ()).map ((event) => FileUploadRequest ()..fileContents = (FileData ()..data = event));
182+ final requestStream = StreamGroup .merge ([metadataStream, fileDataStream]);
183+ final response = await _dataSyncClient.fileUpload (requestStream);
184+ return response.fileId;
185+ } finally {
186+ await reader.cancel ();
187+ }
188+ }
108189}
109190
110191extension FilterUtils on Filter {
0 commit comments