Skip to content

Commit 6f22cd6

Browse files
authored
Datasync (#140)
1 parent 5a70f1c commit 6f22cd6

File tree

6 files changed

+364
-87
lines changed

6 files changed

+364
-87
lines changed

lib/src/app/data.dart

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1+
import 'dart:io';
12
import 'dart:math';
23

4+
import 'package:async/async.dart';
5+
import 'package:collection/collection.dart';
36
import '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

69
import '../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.
1117
class 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

110191
extension FilterUtils on Filter {

lib/src/media/image.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class MimeType {
2323
/// If the MimeType is not supported, then this [name] is the string of the unsupported MimeType.
2424
String get name => _name;
2525

26+
/// The underlying type, e.g. 'jpeg', 'png', 'pcd'.
27+
/// If the MimeType is not supported, then this [type] is the string 'unsupported'.
28+
String get type => _type;
29+
2630
const MimeType._(this._type, this._name);
2731

2832
/// Viam's custom RGBA encoding (image/vnd.viam.rgba)

lib/src/viam_sdk_impl.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:grpc/grpc_connection_interface.dart';
2+
import 'package:viam_sdk/protos/app/data_sync.dart';
23

34
import './app/app.dart';
45
import './app/data.dart';
@@ -15,12 +16,12 @@ class ViamImpl implements Viam {
1516

1617
ViamImpl._withChannel(this._clientChannelBase) {
1718
_appClient = AppClient(AppServiceClient(_clientChannelBase));
18-
_dataClient = DataClient(DataServiceClient(_clientChannelBase));
19+
_dataClient = DataClient(DataServiceClient(_clientChannelBase), DataSyncServiceClient(_clientChannelBase));
1920
}
2021

2122
ViamImpl.withAccessToken(String accessToken) : _clientChannelBase = AuthenticatedChannel('app.viam.com', 443, accessToken, false) {
2223
_appClient = AppClient(AppServiceClient(_clientChannelBase));
23-
_dataClient = DataClient(DataServiceClient(_clientChannelBase));
24+
_dataClient = DataClient(DataServiceClient(_clientChannelBase), DataSyncServiceClient(_clientChannelBase));
2425
}
2526

2627
static Future<ViamImpl> withApiKey(String apiKeyId, String apiKey) async {

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies:
2020
collection: ^1.17.1
2121
intl: ^0.18.1
2222
bonsoir: ^2.2.0+1 # do not upgrade beyond 2.2.0+1 until this issue is resolved: https://github.com/Skyost/Bonsoir/issues/57
23+
async: ^2.11.0
2324

2425
dev_dependencies:
2526
flutter_test:

0 commit comments

Comments
 (0)