diff --git a/lib/src/home/models/drive_provider_model.dart b/lib/src/home/models/drive_provider_model.dart new file mode 100644 index 0000000..cd9912e --- /dev/null +++ b/lib/src/home/models/drive_provider_model.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'drive_provider_model.freezed.dart'; +part 'drive_provider_model.g.dart'; + +@freezed +class DriveProviderModel with _$DriveProviderModel { + const factory DriveProviderModel({ + required String accessToken, + required String refreshToken, + required String expiresIn, + }) = _DriveProviderModel; + + factory DriveProviderModel.fromJson(Map json) => + _$DriveProviderModelFromJson(json); +} diff --git a/lib/src/home/models/drive_provider_model.freezed.dart b/lib/src/home/models/drive_provider_model.freezed.dart new file mode 100644 index 0000000..1ae5f75 --- /dev/null +++ b/lib/src/home/models/drive_provider_model.freezed.dart @@ -0,0 +1,194 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'drive_provider_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +DriveProviderModel _$DriveProviderModelFromJson(Map json) { + return _DriveProviderModel.fromJson(json); +} + +/// @nodoc +mixin _$DriveProviderModel { + String get accessToken => throw _privateConstructorUsedError; + String get refreshToken => throw _privateConstructorUsedError; + String get expiresIn => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $DriveProviderModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DriveProviderModelCopyWith<$Res> { + factory $DriveProviderModelCopyWith( + DriveProviderModel value, $Res Function(DriveProviderModel) then) = + _$DriveProviderModelCopyWithImpl<$Res, DriveProviderModel>; + @useResult + $Res call({String accessToken, String refreshToken, String expiresIn}); +} + +/// @nodoc +class _$DriveProviderModelCopyWithImpl<$Res, $Val extends DriveProviderModel> + implements $DriveProviderModelCopyWith<$Res> { + _$DriveProviderModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accessToken = null, + Object? refreshToken = null, + Object? expiresIn = null, + }) { + return _then(_value.copyWith( + accessToken: null == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String, + refreshToken: null == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$DriveProviderModelImplCopyWith<$Res> + implements $DriveProviderModelCopyWith<$Res> { + factory _$$DriveProviderModelImplCopyWith(_$DriveProviderModelImpl value, + $Res Function(_$DriveProviderModelImpl) then) = + __$$DriveProviderModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String accessToken, String refreshToken, String expiresIn}); +} + +/// @nodoc +class __$$DriveProviderModelImplCopyWithImpl<$Res> + extends _$DriveProviderModelCopyWithImpl<$Res, _$DriveProviderModelImpl> + implements _$$DriveProviderModelImplCopyWith<$Res> { + __$$DriveProviderModelImplCopyWithImpl(_$DriveProviderModelImpl _value, + $Res Function(_$DriveProviderModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accessToken = null, + Object? refreshToken = null, + Object? expiresIn = null, + }) { + return _then(_$DriveProviderModelImpl( + accessToken: null == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String, + refreshToken: null == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DriveProviderModelImpl implements _DriveProviderModel { + const _$DriveProviderModelImpl( + {required this.accessToken, + required this.refreshToken, + required this.expiresIn}); + + factory _$DriveProviderModelImpl.fromJson(Map json) => + _$$DriveProviderModelImplFromJson(json); + + @override + final String accessToken; + @override + final String refreshToken; + @override + final String expiresIn; + + @override + String toString() { + return 'DriveProviderModel(accessToken: $accessToken, refreshToken: $refreshToken, expiresIn: $expiresIn)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DriveProviderModelImpl && + (identical(other.accessToken, accessToken) || + other.accessToken == accessToken) && + (identical(other.refreshToken, refreshToken) || + other.refreshToken == refreshToken) && + (identical(other.expiresIn, expiresIn) || + other.expiresIn == expiresIn)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, accessToken, refreshToken, expiresIn); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DriveProviderModelImplCopyWith<_$DriveProviderModelImpl> get copyWith => + __$$DriveProviderModelImplCopyWithImpl<_$DriveProviderModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$DriveProviderModelImplToJson( + this, + ); + } +} + +abstract class _DriveProviderModel implements DriveProviderModel { + const factory _DriveProviderModel( + {required final String accessToken, + required final String refreshToken, + required final String expiresIn}) = _$DriveProviderModelImpl; + + factory _DriveProviderModel.fromJson(Map json) = + _$DriveProviderModelImpl.fromJson; + + @override + String get accessToken; + @override + String get refreshToken; + @override + String get expiresIn; + @override + @JsonKey(ignore: true) + _$$DriveProviderModelImplCopyWith<_$DriveProviderModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/home/models/drive_provider_model.g.dart b/lib/src/home/models/drive_provider_model.g.dart new file mode 100644 index 0000000..055983f --- /dev/null +++ b/lib/src/home/models/drive_provider_model.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'drive_provider_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$DriveProviderModelImpl _$$DriveProviderModelImplFromJson( + Map json) => + _$DriveProviderModelImpl( + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + expiresIn: json['expiresIn'] as String, + ); + +Map _$$DriveProviderModelImplToJson( + _$DriveProviderModelImpl instance) => + { + 'accessToken': instance.accessToken, + 'refreshToken': instance.refreshToken, + 'expiresIn': instance.expiresIn, + }; diff --git a/lib/src/home/models/remote_folder_model.dart b/lib/src/home/models/remote_folder_model.dart new file mode 100644 index 0000000..d48cdfd --- /dev/null +++ b/lib/src/home/models/remote_folder_model.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:syncvault/src/home/services/rclone.dart'; + +part 'remote_folder_model.freezed.dart'; +part 'remote_folder_model.g.dart'; + +@freezed +class RemoteFolderModel with _$RemoteFolderModel { + const factory RemoteFolderModel({ + // required String email, + required DriveProvider provider, + // required String folderPath, + // required String folderName, + // required String folderId, + // required bool isAutoSync, + // required bool isDeletionEnabled, + // required bool isTwoWaySync, + // required List files, + }) = _RemoteFolderModel; + + factory RemoteFolderModel.fromJson(Map json) => + _$RemoteFolderModelFromJson(json); +} diff --git a/lib/src/home/models/remote_folder_model.freezed.dart b/lib/src/home/models/remote_folder_model.freezed.dart new file mode 100644 index 0000000..51cd21c --- /dev/null +++ b/lib/src/home/models/remote_folder_model.freezed.dart @@ -0,0 +1,156 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'remote_folder_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +RemoteFolderModel _$RemoteFolderModelFromJson(Map json) { + return _RemoteFolderModel.fromJson(json); +} + +/// @nodoc +mixin _$RemoteFolderModel { +// required String email, + DriveProvider get provider => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $RemoteFolderModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RemoteFolderModelCopyWith<$Res> { + factory $RemoteFolderModelCopyWith( + RemoteFolderModel value, $Res Function(RemoteFolderModel) then) = + _$RemoteFolderModelCopyWithImpl<$Res, RemoteFolderModel>; + @useResult + $Res call({DriveProvider provider}); +} + +/// @nodoc +class _$RemoteFolderModelCopyWithImpl<$Res, $Val extends RemoteFolderModel> + implements $RemoteFolderModelCopyWith<$Res> { + _$RemoteFolderModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? provider = null, + }) { + return _then(_value.copyWith( + provider: null == provider + ? _value.provider + : provider // ignore: cast_nullable_to_non_nullable + as DriveProvider, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RemoteFolderModelImplCopyWith<$Res> + implements $RemoteFolderModelCopyWith<$Res> { + factory _$$RemoteFolderModelImplCopyWith(_$RemoteFolderModelImpl value, + $Res Function(_$RemoteFolderModelImpl) then) = + __$$RemoteFolderModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DriveProvider provider}); +} + +/// @nodoc +class __$$RemoteFolderModelImplCopyWithImpl<$Res> + extends _$RemoteFolderModelCopyWithImpl<$Res, _$RemoteFolderModelImpl> + implements _$$RemoteFolderModelImplCopyWith<$Res> { + __$$RemoteFolderModelImplCopyWithImpl(_$RemoteFolderModelImpl _value, + $Res Function(_$RemoteFolderModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? provider = null, + }) { + return _then(_$RemoteFolderModelImpl( + provider: null == provider + ? _value.provider + : provider // ignore: cast_nullable_to_non_nullable + as DriveProvider, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$RemoteFolderModelImpl implements _RemoteFolderModel { + const _$RemoteFolderModelImpl({required this.provider}); + + factory _$RemoteFolderModelImpl.fromJson(Map json) => + _$$RemoteFolderModelImplFromJson(json); + +// required String email, + @override + final DriveProvider provider; + + @override + String toString() { + return 'RemoteFolderModel(provider: $provider)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RemoteFolderModelImpl && + (identical(other.provider, provider) || + other.provider == provider)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, provider); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RemoteFolderModelImplCopyWith<_$RemoteFolderModelImpl> get copyWith => + __$$RemoteFolderModelImplCopyWithImpl<_$RemoteFolderModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$RemoteFolderModelImplToJson( + this, + ); + } +} + +abstract class _RemoteFolderModel implements RemoteFolderModel { + const factory _RemoteFolderModel({required final DriveProvider provider}) = + _$RemoteFolderModelImpl; + + factory _RemoteFolderModel.fromJson(Map json) = + _$RemoteFolderModelImpl.fromJson; + + @override // required String email, + DriveProvider get provider; + @override + @JsonKey(ignore: true) + _$$RemoteFolderModelImplCopyWith<_$RemoteFolderModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/home/models/remote_folder_model.g.dart b/lib/src/home/models/remote_folder_model.g.dart new file mode 100644 index 0000000..3c3ef9c --- /dev/null +++ b/lib/src/home/models/remote_folder_model.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'remote_folder_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$RemoteFolderModelImpl _$$RemoteFolderModelImplFromJson( + Map json) => + _$RemoteFolderModelImpl( + provider: $enumDecode(_$DriveProviderEnumMap, json['provider']), + ); + +Map _$$RemoteFolderModelImplToJson( + _$RemoteFolderModelImpl instance) => + { + 'provider': _$DriveProviderEnumMap[instance.provider]!, + }; + +const _$DriveProviderEnumMap = { + DriveProvider.oneDrive: 'oneDrive', + DriveProvider.googleDrive: 'googleDrive', +}; diff --git a/lib/src/home/services/rclone.dart b/lib/src/home/services/rclone.dart new file mode 100644 index 0000000..5e60f78 --- /dev/null +++ b/lib/src/home/services/rclone.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:fpdart/fpdart.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:syncvault/errors.dart'; +import 'package:syncvault/src/home/models/drive_provider_model.dart'; +import 'package:syncvault/src/home/models/remote_folder_model.dart'; + +enum DriveProvider { + oneDrive('onedrive'), + googleDrive('drive'); + + const DriveProvider(this.providerName); + + final String providerName; +} + +class RCloneAuthService { + Future check() async { + final docDir = await getApplicationDocumentsDirectory(); + final rClonePath = + '${docDir.path}/SyncVault/RClone/rclone-v1.67.0-windows-amd64/rclone.exe'; + + final res = await Process.run(rClonePath, ['authorize', 'drive']); + print(res.stdout); + + return RemoteFolderModel(provider: DriveProvider.googleDrive); + } + + TaskEither authorize( + {required DriveProvider driveProvider}) { + return TaskEither.tryCatch(() async { + final docDir = await getApplicationDocumentsDirectory(); + final rClonePath = + '${docDir.path}/SyncVault/RClone/rclone-v1.67.0-windows-amd64/rclone.exe'; + + final res = await Process.run( + rClonePath, ['authorize', driveProvider.providerName]); + + final authJson = jsonDecode(RegExp(r'\{.+\}').stringMatch(res.stdout)!); + if (authJson + case { + 'access_token': String accessToken, + 'refresh_token': String refreshToken, + 'expiry': String expiresIn + }) { + return DriveProviderModel( + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: expiresIn); + } else { + throw const GeneralError('Authorization response invalid'); + } + }, (err, stackTrace) => err.segregateError()); + } +} + +class RCloneDriveService { + var rClonePath = + '${getApplicationDocumentsDirectory()}/SyncVault/RClone/rclone-v1.67.0-windows-amd64/rclone.exe'; +} diff --git a/lib/src/home/views/home_view.dart b/lib/src/home/views/home_view.dart index 494462a..ba017db 100644 --- a/lib/src/home/views/home_view.dart +++ b/lib/src/home/views/home_view.dart @@ -13,6 +13,7 @@ import 'package:syncvault/helpers.dart'; import 'package:syncvault/src/home/components/expandable_card_widget.dart'; import 'package:syncvault/src/home/components/new_folder_dialog_widget.dart'; import 'package:syncvault/src/home/components/tree_widget.dart'; +import 'package:syncvault/src/home/services/rclone.dart'; import 'package:system_tray/system_tray.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:watcher/watcher.dart'; @@ -105,6 +106,18 @@ class _HomeViewState extends ConsumerState { appBar: AppBar( title: const Text('SyncVault'), actions: [ + IconButton( + icon: const Icon(Icons.warning), + tooltip: 'Test RClone', + onPressed: () async { + final res = await RCloneAuthService() + .authorize( + driveProvider: DriveProvider.googleDrive, + ) + .run(); + print(res); + }, + ), if (Platform.isWindows) IconButton( icon: const Icon(Icons.arrow_downward), diff --git a/lib/src/introduction/services/intro_service.dart b/lib/src/introduction/services/intro_service.dart index e199c60..df43112 100644 --- a/lib/src/introduction/services/intro_service.dart +++ b/lib/src/introduction/services/intro_service.dart @@ -6,6 +6,8 @@ import 'package:archive/archive_io.dart'; import 'package:dio/dio.dart'; import 'package:fpdart/fpdart.dart'; import 'package:get_it/get_it.dart'; +import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:syncvault/errors.dart'; @@ -49,13 +51,18 @@ class IntroService { final progressStreamController = StreamController(); final dir = await getApplicationDocumentsDirectory(); - final path = '${dir.path}/SyncVault/RClone.zip'; + final downloadPath = switch (Platform.operatingSystem) { + 'windows' => '${dir.path}/SyncVault/RClone.zip', + 'android' => '${dir.path}/SyncVault/RClone.tgz', + _ => '', + }; final unzippedPath = '${dir.path}/SyncVault/RClone'; + final execPath = '${dir.path}/SyncVault'; try { _dio.download( url, - path, + downloadPath, onReceiveProgress: (received, total) { progressStreamController.add(((received / total) * 100).round()); }, @@ -66,10 +73,16 @@ class IntroService { return status! < 500; }, ), - ).then((_) { - final inputStream = InputFileStream(path); - final archive = ZipDecoder().decodeBuffer(inputStream); - extractArchiveToDisk(archive, unzippedPath); + ).then((_) async { + await extractFileToDisk(downloadPath, unzippedPath, asyncWrite: true); + await File(downloadPath).delete(); + + final rCloneExecutable = Glob('**/rclone{.exe,*}') + .listSync(root: unzippedPath) + .first as File; + await rCloneExecutable.copy(execPath); + + await File(unzippedPath).delete(recursive: true); }); yield* progressStreamController.stream.map((val) => Right(val)); diff --git a/pubspec.lock b/pubspec.lock index d943285..da25beb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -478,7 +478,7 @@ packages: source: hosted version: "7.7.0" glob: - dependency: transitive + dependency: "direct main" description: name: glob sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" diff --git a/pubspec.yaml b/pubspec.yaml index e767ead..3094dab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: path_provider: ^2.1.3 introduction_screen: ^3.1.14 archive: ^3.6.1 + glob: ^2.1.2 dev_dependencies: flutter_test: