Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: at_file_share demo #169

Merged
merged 6 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions at_file_share/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

pubspec.lock

at_file_share.iml
3 changes: 3 additions & 0 deletions at_file_share/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

- Initial version.
27 changes: 27 additions & 0 deletions at_file_share/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#TODO

Prerequisite:
1. Storj account and access grant
1. Login/signup to https://www.storj.io/
2. Create new project e.g acme_demo
3. Open the newly created project
4. Go to buckets https://us1.storj.io/buckets/management and create new bucket e.g demo-bucket
5. Go to access tab https://us1.storj.io/access-grants and click "create access grant"
6. Type --> "Access Grant" and give a name to identify the access e.g acme-access. Click continue
7. Under bucket select the bucket that you created i.e demo-bucket
8. Create/enter a passphrase and click "create access".Click confirm
9. Download the credentials file(acme-access.txt) by clicking "Download"
2. Configure uplink
1. Follow the instructions to install uplink tool - https://docs.storj.io/learn/tutorials/quickstart-uplink-cli
2. Run the below command with the credentials file downloaded
3. uplink access import main acme-access.txt
3. Run file sender on sending machine and file receiver on receiving machine. File receiver will receive notification through
atprotocol and download the storj file, decrypt using atClient SDK method.

Usage:
1. File sender
1. dart bin/at_file_share.dart -m send -a <sender_atsign> -r <receiver_atsign> -k <path_to_sender_atKeys_file> -f <path_of_file_to_send> -b <storj_bucket_name> -n <namespace>
2. e.g. dart bin/at_file_share.dart -m send -s @alice -r @bob -k /home/user/@alice.atKeys -f /home/user/test_file.txt -b demo-bucket -n acme
2. File receiver
1. dart bin/at_file_share.dart -m receive -k <path_to_receiver_atKeysFile> -a <current_atsign> -o <download_path> -n <namespace>
2. e.g dart bin/at_file_share.dart -m receive -k /home/user/@bob.atKeys -a @bob -o /home/user/downloads -n acme
30 changes: 30 additions & 0 deletions at_file_share/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.

include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types

# analyzer:
# exclude:
# - path/to/excluded/files/**

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
61 changes: 61 additions & 0 deletions at_file_share/bin/at_file_share.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:args/args.dart';
import 'package:at_client/at_client.dart';
import 'package:at_file_share/src/file_send_params.dart';
import 'package:at_file_share/src/service/file_receiver.dart';
import 'package:at_file_share/src/service/file_sender.dart';
import 'package:at_onboarding_cli/at_onboarding_cli.dart';
import 'package:at_cli_commons/at_cli_commons.dart';

const String version = '0.0.1';

ArgParser buildParser() {
return CLIBase.argsParser
..addOption('mode',
abbr: 'm', mandatory: true, help: 'File sharing mode - send or receive')
..addOption('receiver',
abbr: 'r', mandatory: true, help: 'atsign receiving the shared file')
..addOption('filePath',
abbr: 'f', mandatory: false, help: 'path of file in local to share')
..addOption('downloadDir',
abbr: 'o', mandatory: false, help: 'download dir when receiving files')
..addOption('bucketName',
abbr: 'b',
mandatory: false,
help: 'bucket name of storj to upload the file');
}

void printUsage(ArgParser argParser) {
print('Usage: dart at_file_share.dart <flags> [arguments]');
print(argParser.usage);
}

void main(List<String> arguments) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change the sender and receiver to use the CLIBase class instead, please? See https://github.com/atsign-foundation/at_demos/blob/trunk/at_rpc_demo/bin/arithmetic_client.dart for example

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Gary. done

final ArgParser argParser = buildParser();
try {
final ArgResults results = argParser.parse(arguments);
// Process the parsed arguments.
if (results.wasParsed('help')) {
printUsage(argParser);
return;
}
String mode = results['mode'];
var atClient = (await CLIBase.fromCommandLineArgs(arguments)).atClient;
print('mode: $mode');
if (mode == 'send') {
String receiver = results['receiver'];
var params = FileSendParams()
..receiverAtSign = receiver
..filePath = results['filePath']
..bucketName = results['bucketName'];

await FileSender(atClient).sendFile(params);
} else if (mode == 'receive') {
await FileReceiver(atClient).receiveFile(results['downloadDir']);
}
} on FormatException catch (e) {
// Print usage information if an invalid argument was provided.
print(e.message);
print('');
printUsage(argParser);
}
}
6 changes: 6 additions & 0 deletions at_file_share/lib/src/file_send_params.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class FileSendParams {
late String receiverAtSign;
int chunkSize = 1024;
late String filePath;
late String bucketName;
}
125 changes: 125 additions & 0 deletions at_file_share/lib/src/service/file_receiver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:http/http.dart' as http;

import 'package:at_client/at_client.dart';

class FileReceiver {
final AtClient _atClient;

FileReceiver(this._atClient);

Future<void> receiveFile(String downloadPath) async {
_atClient.notificationService
.subscribe()
.listen((AtNotification atNotification) async {
if (atNotification.id != '-1') {
var fileName;
try {
print('notification Received: ${atNotification.toString()}');
var key = atNotification.key;
var atValue = await _atClient.get(AtKey.fromString(key));
print(atValue.toString());
var valueJson = jsonDecode(atValue.value);
var storjUrl = valueJson['fileUrl'];
fileName = valueJson['fileName'];
await downloadFileWithProgress(
storjUrl, downloadPath, fileName, valueJson);
} on Exception catch (e, trace) {
print(e);
print(trace);
} on Error catch (e, trace) {
print(e);
print(trace);
}
}
});
}

Future<void> _decryptFile(
String downloadPath, String fileName, dynamic valueJson) async {
try {
var startTime = DateTime.now();
var encryptionService = _atClient.encryptionService!;
var encryptedFile =
File('$downloadPath${Platform.pathSeparator}encrypted_$fileName');
await encryptionService.decryptFileInChunks(
encryptedFile, valueJson['fileEncryptionKey'], valueJson['chunkSize'],
ivBase64: valueJson['iv']);
var endTime = DateTime.now();
print(
'Time taken to decrypt file: ${endTime.difference(startTime).inSeconds}');
var decryptedFile = File(
"$downloadPath${Platform.pathSeparator}decrypted_encrypted_$fileName");
if (decryptedFile.existsSync()) {
decryptedFile
.renameSync(downloadPath + Platform.pathSeparator + fileName);
} else {
throw Exception('could not decrypt downloaded file');
}
} on Exception catch (e, trace) {
print(e);
print(trace);
} on Error catch (e, trace) {
print(e);
print(trace);
} finally {
var encryptedFile =
File("$downloadPath${Platform.pathSeparator}encrypted_$fileName");
if (encryptedFile.existsSync()) {
encryptedFile.deleteSync();
}
}
}

Future<void> downloadFileWithProgress(String fileUrl, String downloadPath,
String fileName, dynamic valueJson) async {
var httpClient = http.Client();
var request = http.Request('GET', Uri.parse('$fileUrl?download=1'));
var response = httpClient.send(request);

List<List<int>> chunks = [];
int downloaded = 0;

response.asStream().listen((http.StreamedResponse r) {
r.stream.listen((List<int> chunk) {
if (r.contentLength == null) {
print('content length is null.');
return;
}
// Display percentage of completion
var percentage = downloaded / r.contentLength! * 100;
int roundedPercent = percentage.round();
updateProgress(roundedPercent);

chunks.add(chunk);
downloaded += chunk.length;
}, onDone: () async {
// Display percentage of completion
// print('downloadPercentage: ${downloaded / r.contentLength! * 100}');

// Save the file
File file =
File('$downloadPath${Platform.pathSeparator}encrypted_$fileName');
final Uint8List bytes = Uint8List(r.contentLength!);
int offset = 0;
for (List<int> chunk in chunks) {
bytes.setRange(offset, offset + chunk.length, chunk);
offset += chunk.length;
}
await file.writeAsBytes(bytes);
await _decryptFile(downloadPath, fileName, valueJson);
});
});
}

void updateProgress(int progress) {
// Move cursor to the beginning of the line
stdout.write('\r');

// Print the progress in percentage
stdout.write('Download progress: $progress%');
stdout.flush();
}
}
Loading
Loading