From 65777decb6ccb51ab9847c64a525f421f1ddf253 Mon Sep 17 00:00:00 2001 From: Ahmed Elshorbagy Date: Sun, 24 Apr 2022 11:29:22 +0200 Subject: [PATCH 1/5] FireStorage initial commit --- .../firebase_storage_dart/.gitignore | 10 + .../firebase_storage_dart/CHANGELOG.md | 3 + .../firebase_storage_dart/README.md | 39 ++++ .../analysis_options.yaml | 30 +++ .../firebase_storage_dart_example.dart | 3 + .../lib/firebase_storage_dart.dart | 23 ++ .../lib/src/data_models/full_metadata.dart | 98 +++++++++ .../lib/src/data_models/list_options.dart | 18 ++ .../src/data_models/put_string_format.dart | 15 ++ .../src/data_models/settable_metadata.dart | 52 +++++ .../lib/src/data_models/task_state.dart | 19 ++ .../lib/src/firebase_storage.dart | 180 ++++++++++++++++ .../lib/src/internal/pointer.dart | 67 ++++++ .../lib/src/list_result.dart | 42 ++++ .../platform_interface_list_result.dart | 37 ++++ .../lib/src/reference.dart | 204 ++++++++++++++++++ .../firebase_storage_dart/lib/src/task.dart | 91 ++++++++ .../lib/src/task_snapshot.dart | 48 +++++ .../firebase_storage_dart/lib/src/utils.dart | 91 ++++++++ .../firebase_storage_dart/pubspec.yaml | 18 ++ .../test/firebase_storage_dart_test.dart | 6 + .../firebase_storage_desktop/.gitignore | 29 +++ .../firebase_storage_desktop/.metadata | 10 + .../firebase_storage_desktop/CHANGELOG.md | 3 + .../firebase_storage_desktop/LICENSE | 1 + .../firebase_storage_desktop/README.md | 39 ++++ .../analysis_options.yaml | 4 + .../lib/firebase_storage_desktop.dart | 178 +++++++++++++++ .../firebase_storage_desktop/pubspec.yaml | 68 ++++++ .../test/firebase_storage_desktop_test.dart | 7 + 30 files changed, 1433 insertions(+) create mode 100644 packages/firebase_storage/firebase_storage_dart/.gitignore create mode 100644 packages/firebase_storage/firebase_storage_dart/CHANGELOG.md create mode 100644 packages/firebase_storage/firebase_storage_dart/README.md create mode 100644 packages/firebase_storage/firebase_storage_dart/analysis_options.yaml create mode 100644 packages/firebase_storage/firebase_storage_dart/example/firebase_storage_dart_example.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/data_models/full_metadata.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/data_models/list_options.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/data_models/put_string_format.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/data_models/settable_metadata.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/data_models/task_state.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/internal/pointer.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/task.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/pubspec.yaml create mode 100644 packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/.gitignore create mode 100644 packages/firebase_storage/firebase_storage_desktop/.metadata create mode 100644 packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md create mode 100644 packages/firebase_storage/firebase_storage_desktop/LICENSE create mode 100644 packages/firebase_storage/firebase_storage_desktop/README.md create mode 100644 packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/pubspec.yaml create mode 100644 packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart diff --git a/packages/firebase_storage/firebase_storage_dart/.gitignore b/packages/firebase_storage/firebase_storage_dart/.gitignore new file mode 100644 index 00000000..65c34dc8 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/.gitignore @@ -0,0 +1,10 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build outputs. +build/ + +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/firebase_storage/firebase_storage_dart/CHANGELOG.md b/packages/firebase_storage/firebase_storage_dart/CHANGELOG.md new file mode 100644 index 00000000..effe43c8 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/firebase_storage/firebase_storage_dart/README.md b/packages/firebase_storage/firebase_storage_dart/README.md new file mode 100644 index 00000000..8b55e735 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/firebase_storage/firebase_storage_dart/analysis_options.yaml b/packages/firebase_storage/firebase_storage_dart/analysis_options.yaml new file mode 100644 index 00000000..dee8927a --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/analysis_options.yaml @@ -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 diff --git a/packages/firebase_storage/firebase_storage_dart/example/firebase_storage_dart_example.dart b/packages/firebase_storage/firebase_storage_dart/example/firebase_storage_dart_example.dart new file mode 100644 index 00000000..9d6f760e --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/example/firebase_storage_dart_example.dart @@ -0,0 +1,3 @@ +import 'package:firebase_storage_dart/firebase_storage_dart.dart'; + +void main() {} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart new file mode 100644 index 00000000..c82879eb --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart @@ -0,0 +1,23 @@ +/// Support for doing something awesome. +/// +/// More dartdocs go here. +library firebase_storage_dart; + +import 'dart:async'; +import 'dart:convert' show utf8, base64; +import 'dart:io' show File; +import 'dart:typed_data' show Uint8List; + +import 'package:firebase_storage_dart/src/data_models/full_metadata.dart'; +import 'package:firebase_storage_dart/src/data_models/list_options.dart'; +import 'package:firebase_storage_dart/src/data_models/put_string_format.dart'; +import 'package:firebase_storage_dart/src/data_models/settable_metadata.dart'; +import 'package:firebase_storage_dart/src/firebase_storage.dart'; +import 'package:firebase_storage_dart/src/platform_interface/platform_interface_list_result.dart'; + +// TODO: Export any libraries intended for clients of this package. +// part 'src/utils.dart'; +part 'src/reference.dart'; +part 'src/list_result.dart'; +part 'src/task.dart'; +part 'src/task_snapshot.dart'; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/full_metadata.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/full_metadata.dart new file mode 100644 index 00000000..6a07daf0 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/full_metadata.dart @@ -0,0 +1,98 @@ +import 'package:meta/meta.dart' show protected; + +/// The result of calling [getMetadata] on a storage object reference. +class FullMetadata { + // ignore: public_member_api_docs + @protected + FullMetadata(this._metadata); + + final Map _metadata; + + /// The bucket this object is contained in. + String? get bucket { + return _metadata['bucket']; + } + + /// Served as the 'Cache-Control' header on object download. + String? get cacheControl { + return _metadata['cacheControl']; + } + + /// Served as the 'Content-Disposition' HTTP header on object download. + String? get contentDisposition { + return _metadata['contentDisposition']; + } + + /// Served as the 'Content-Encoding' header on object download. + String? get contentEncoding { + return _metadata['contentEncoding']; + } + + /// Served as the 'Content-Language' header on object download. + String? get contentLanguage { + return _metadata['contentLanguage']; + } + + /// Served as the 'Content-Type' header on object download. + String? get contentType { + return _metadata['contentType']; + } + + /// Custom metadata set on this storage object. + Map? get customMetadata { + return _metadata['customMetadata'] == null + ? null + : Map.from(_metadata['customMetadata']); + } + + /// The full path of this object. + String get fullPath { + return _metadata['fullPath']; + } + + /// The object's generation. + String? get generation { + return _metadata['generation']; + } + + /// The object's metadata generation. + String? get metadataGeneration { + return _metadata['metadataGeneration']; + } + + /// A Base64-encoded MD5 hash of the object being uploaded. + String? get md5Hash { + return _metadata['md5Hash']; + } + + /// The object's metageneration. + String? get metageneration { + return _metadata['metageneration']; + } + + /// The short name of this object, which is the last component of the full path. + /// + /// For example, if fullPath is 'full/path/image.png', name is 'image.png'. + String get name { + return _metadata['name']; + } + + /// The size of this object, in bytes. + int? get size { + return _metadata['size']; + } + + /// A DateTime representing when this object was created. + DateTime? get timeCreated { + return _metadata['creationTimeMillis'] == null + ? null + : DateTime.fromMillisecondsSinceEpoch(_metadata['creationTimeMillis']); + } + + /// A DateTime representing when this object was updated. + DateTime? get updated { + return _metadata['updatedTimeMillis'] == null + ? null + : DateTime.fromMillisecondsSinceEpoch(_metadata['updatedTimeMillis']); + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/list_options.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/list_options.dart new file mode 100644 index 00000000..5a578578 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/list_options.dart @@ -0,0 +1,18 @@ +/// The options [FirebaseStoragePlatform.list] accepts. +class ListOptions { + /// Creates a new [ListOptions] instance. + const ListOptions({ + this.maxResults, + this.pageToken, + }); + + /// If set, limits the total number of `prefixes` and `items` to return. + /// + /// The default and maximum maxResults is 1000. + final int? maxResults; + + /// The nextPageToken from a previous call to list(). + /// + /// If provided, listing is resumed from the previous position. + final String? pageToken; +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/put_string_format.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/put_string_format.dart new file mode 100644 index 00000000..985724db --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/put_string_format.dart @@ -0,0 +1,15 @@ +/// The format in which a string can be uploaded to the storage bucket via +/// [Reference.putString]. +enum PutStringFormat { + /// A raw string. It will be uploaded as a Base64 string. + raw, + + /// A Base64 encoded string. + base64, + + /// A Base64 URL encoded string. + base64Url, + + /// A data url string. + dataUrl, +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/settable_metadata.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/settable_metadata.dart new file mode 100644 index 00000000..dece5579 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/settable_metadata.dart @@ -0,0 +1,52 @@ +/// The settable metadata a storage object reference can be set with. +class SettableMetadata { + /// Creates a new [SettableMetadata] instance. + SettableMetadata({ + this.cacheControl, + this.contentDisposition, + this.contentEncoding, + this.contentLanguage, + this.contentType, + this.customMetadata, + }); + + /// Served as the 'Cache-Control' header on object download. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control. + final String? cacheControl; + + /// Served as the 'Content-Disposition' header on object download. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition. + final String? contentDisposition; + + /// Served as the 'Content-Encoding' header on object download. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding. + final String? contentEncoding; + + /// Served as the 'Content-Language' header on object download. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language. + final String? contentLanguage; + + /// Served as the 'Content-Type' header on object download. + /// + /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type. + final String? contentType; + + /// Additional user-defined custom metadata. + final Map? customMetadata; + + /// Returns the settable metadata as a [Map]. + Map asMap() { + return { + 'cacheControl': cacheControl, + 'contentDisposition': contentDisposition, + 'contentEncoding': contentEncoding, + 'contentLanguage': contentLanguage, + 'contentType': contentType, + 'customMetadata': customMetadata, + }; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/task_state.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/task_state.dart new file mode 100644 index 00000000..f09e69c3 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/data_models/task_state.dart @@ -0,0 +1,19 @@ +/// Represents the state of an on-going [Task]. +/// +/// The state can be accessed directly via a [TaskSnapshot]. +enum TaskState { + /// Indicates the task has been paused by the user. + paused, + + /// Indicates the task is currently in-progress. + running, + + /// Indicates the task has successfully completed. + success, + + /// Indicates the task was canceled. + canceled, + + /// Indicates the task failed with an error. + error, +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart new file mode 100644 index 00000000..6088b30d --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart @@ -0,0 +1,180 @@ +import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebase_storage_dart/firebase_storage_dart.dart'; +import 'package:firebase_storage_dart/src/utils.dart'; + +/// The entrypoint for [FirebaseStorage]. +class FirebaseStorage { + /// Creates Firebase Functions + // @visibleForTesting + FirebaseStorage({required this.app, required this.bucket}); + + /// The [FirebaseApp] for this current [FirebaseStorage] instance. + FirebaseApp app; + + /// The storage bucket of this instance. + String bucket; + + /// The maximum time to retry operations other than uploads or downloads in milliseconds. + Duration get maxOperationRetryTime { + throw UnimplementedError(); + } + + /// The maximum time to retry uploads in milliseconds. + Duration get maxUploadRetryTime { + throw UnimplementedError(); + } + + /// The maximum time to retry downloads in milliseconds. + Duration get maxDownloadRetryTime { + throw UnimplementedError(); + } + + static final Map _cachedInstances = {}; + + /// Returns an instance using the default [FirebaseApp]. + static FirebaseStorage get instance { + return FirebaseStorage.instanceFor( + app: Firebase.app(), + ); + } + + /// Returns an instance using a specified [FirebaseApp] and/or custom storage bucket. + /// + /// If [app] is not provided, the default Firebase app will be used. + /// If [bucket] is not provided, the default storage bucket will be used. + static FirebaseStorage instanceFor({ + FirebaseApp? app, + String? bucket, + }) { + app ??= Firebase.app(); + + if (bucket == null && app.options.storageBucket == null) { + if (app.name == defaultFirebaseAppName) { + _throwNoBucketError( + 'No default storage bucket could be found. Ensure you have correctly followed the Getting Started guide.'); + } else { + _throwNoBucketError( + "No storage bucket could be found for the app '${app.name}'. Ensure you have set the [storageBucket] on [FirebaseOptions] whilst initializing the secondary Firebase app."); + } + } + + String _bucket = bucket ?? app.options.storageBucket!; + + // Previous versions allow storage buckets starting with "gs://". + // Since we need to create a key using the bucket, it must not include "gs://" + // since native does not include it when requesting the bucket. This keeps + // the code backwards compatible but also works with the refactor. + if (_bucket.startsWith('gs://')) { + _bucket = _bucket.replaceFirst('gs://', ''); + } + + String key = '${app.name}|$_bucket'; + if (_cachedInstances.containsKey(key)) { + return _cachedInstances[key]!; + } + + FirebaseStorage newInstance = FirebaseStorage(app: app, bucket: _bucket); + _cachedInstances[key] = newInstance; + + return newInstance; + } + + /// Returns a new [Reference]. + /// + /// If the [path] is empty, the reference will point to the root of the + /// storage bucket. + Reference ref([String? path]) { + path ??= '/'; + throw UnimplementedError(); + } + + /// Returns a new [Reference] from a given URL. + /// + /// The [url] can either be a HTTP or Google Storage URL pointing to an object. + /// If the URL contains a storage bucket which is different to the current + /// [FirebaseStorage.bucket], a new [FirebaseStorage] instance for the + /// [Reference] will be used instead. + Reference refFromURL(String url) { + assert(url.startsWith('gs://') || url.startsWith('http'), + "'a url must start with 'gs://' or 'https://'"); + + String? bucket; + String? path; + + if (url.startsWith('http')) { + final parts = partsFromHttpUrl(url); + + assert(parts != null, + "url could not be parsed, ensure it's a valid storage url"); + + bucket = parts!['bucket']; + path = parts['path']; + } else { + bucket = bucketFromGoogleStorageUrl(url); + path = pathFromGoogleStorageUrl(url); + } + + return FirebaseStorage.instanceFor(app: app, bucket: 'gs://$bucket') + .ref(path); + } + + /// Sets the new maximum operation retry time. + void setMaxOperationRetryTime(Duration time) { + assert(!time.isNegative); + throw UnimplementedError(); + } + + /// Sets the new maximum upload retry time. + void setMaxUploadRetryTime(Duration time) { + assert(!time.isNegative); + throw UnimplementedError(); + } + + /// Sets the new maximum download retry time. + void setMaxDownloadRetryTime(Duration time) { + assert(!time.isNegative); + throw UnimplementedError(); + } + + /// Changes this instance to point to a Storage emulator running locally. + /// + /// Set the [host] (ex: localhost) and [port] (ex: 9199) of the local emulator. + /// + /// Note: Must be called immediately, prior to accessing storage methods. + /// Do not use with production credentials as emulator traffic is not encrypted. + @Deprecated( + 'Will be removed in future release. ' + 'Use useStorageEmulator().', + ) + Future useEmulator({required String host, required int port}) async { + assert(host.isNotEmpty); + assert(!port.isNegative); + + String mappedHost = host; + throw UnimplementedError(); + } + + /// Changes this instance to point to a Storage emulator running locally. + /// + /// Set the [host] of the local emulator, such as "localhost" + /// Set the [port] of the local emulator, such as "9199" (port 9199 is default for storage package) + /// + /// Note: Must be called immediately, prior to accessing storage methods. + /// Do not use with production credentials as emulator traffic is not encrypted. + Future useStorageEmulator(String host, int port) async { + assert(host.isNotEmpty); + assert(!port.isNegative); + + String mappedHost = host; + + throw UnimplementedError(); + } + + @override + String toString() => '$FirebaseStorage(app: ${app.name}, bucket: $bucket)'; +} + +void _throwNoBucketError(String message) { + throw FirebaseException( + plugin: 'firebase_storage', code: 'no-bucket', message: message); +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/internal/pointer.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/internal/pointer.dart new file mode 100644 index 00000000..2543b706 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/internal/pointer.dart @@ -0,0 +1,67 @@ +/// Internal helper class used to manage storage reference paths. +class Pointer { + /// Constructs a new [Pointer] with a given path. + Pointer(String? path) : _path = path ?? '/' { + if (_path.isEmpty) { + _path = '/'; + } else { + String _parsedPath = _path; + + // Remove trailing slashes + if (_path.length > 1 && _path.endsWith('/')) { + _parsedPath = _parsedPath.substring(0, _parsedPath.length - 1); + } + + // Remove starting slashes + if (_path.startsWith('/') && _path.length > 1) { + _parsedPath = _parsedPath.substring(1, _parsedPath.length); + } + + _path = _parsedPath; + } + } + + String _path; + + /// Returns whether the path points to the root of a bucket. + bool get isRoot { + return path == '/'; + } + + /// Returns the serialized path. + String get path { + return _path; + } + + /// Returns the name of the path. + /// + /// For example, a paths "foo/bar.jpg" name is "bar.jpg". + String get name { + return path.split('/').last; + } + + /// Returns the parent path. + /// + /// If the current path is root, `null` wil be returned. + String? get parent { + if (isRoot) { + return null; + } + + List chunks = path.split('/'); + chunks.removeLast(); + return chunks.join('/'); + } + + /// Returns a child path relative to the current path. + String child(String childPath) { + Pointer childPointer = Pointer(childPath); + + // If already at + if (isRoot) { + return childPointer.path; + } + + return '$path/${childPointer.path}'; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart new file mode 100644 index 00000000..f17a0e78 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart @@ -0,0 +1,42 @@ +part of firebase_storage_dart; + +/// Class returned as a result of calling a list method ([list] or [listAll]) +/// on a [Reference]. +class ListResult { + ListResult(this.storage, this._delegate) { + ListResultPlatform.verifyExtends(_delegate); + } + + ListResultPlatform _delegate; + + /// The [FirebaseStorage] instance for this result. + final FirebaseStorage storage; + + /// Objects in this directory. + /// + /// Returns a [List] of [Reference] instances. + List get items { + return _delegate.items + .map( + (referencePlatform) => Reference._(storage, referencePlatform)) + .toList(); + } + + /// If set, there might be more results for this list. + /// + /// Use this token to resume the list with [ListOptions]. + String? get nextPageToken => _delegate.nextPageToken; + + /// References to prefixes (sub-folders). You can call list() on them to get + /// its contents. + /// + /// Folders are implicit based on '/' in the object paths. For example, if a + /// bucket has two objects '/a/b/1' and '/a/b/2', list('/a') will return '/a/b' + /// as a prefix. + List get prefixes { + return _delegate.prefixes + .map( + (referencePlatform) => Reference._(storage, referencePlatform)) + .toList(); + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart new file mode 100644 index 00000000..8831f019 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart @@ -0,0 +1,37 @@ +/// Result returned by [list]. +abstract class ListResultPlatform extends PlatformInterface { + /// Creates a new [ListResultPlatform] instance. + ListResultPlatform(this.storage, this.nextPageToken) : super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [ReferencePlatform]. + /// + /// This is used by the app-facing [Reference] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verifyExtends(ListResultPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + } + + /// The [FirebaseStoragePlatform] used when fetching list items. + final FirebaseStoragePlatform? storage; + + /// Objects in this directory. You can call [getMetadata] and [getDownloadUrl] on them. + List get items { + throw UnimplementedError('items is not implemented'); + } + + /// If set, there might be more results for this list. Use this token to resume the list. + final String? nextPageToken; + + /// References to prefixes (sub-folders). You can call [list] on them to get its contents. + /// + /// Folders are implicit based on '/' in the object paths. For example, if a + /// bucket has two objects '/a/b/1' and '/a/b/2', list('/a') will + /// return '/a/b' as a prefix. + List get prefixes { + throw UnimplementedError('prefixes is not implemented'); + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart new file mode 100644 index 00000000..2feea95c --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart @@ -0,0 +1,204 @@ +part of firebase_storage_dart; + +class Reference { + Reference._(this.storage, this._delegate) { + ReferencePlatform.verifyExtends(_delegate); + } + + ReferencePlatform _delegate; + + /// The storage service associated with this reference. + final FirebaseStorage storage; + + /// The name of the bucket containing this reference's object. + String get bucket => _delegate.bucket; + + /// The full path of this object. + String get fullPath => _delegate.fullPath; + + /// The short name of this object, which is the last component of the full path. + /// + /// For example, if fullPath is 'full/path/image.png', name is 'image.png'. + String get name => _delegate.name; + + /// A reference pointing to the parent location of this reference, or `null` + /// if this reference is the root. + Reference? get parent { + ReferencePlatform? referenceParentPlatform = _delegate.parent; + + if (referenceParentPlatform == null) { + return null; + } + + return Reference._(storage, referenceParentPlatform); + } + + /// A reference to the root of this reference's bucket. + Reference get root => Reference._(storage, _delegate.root); + + /// Returns a reference to a relative path from this reference. + /// + /// [path] The relative path from this reference. Leading, trailing, and + /// consecutive slashes are removed. + Reference child(String path) { + return Reference._(storage, _delegate.child(path)); + } + + /// Deletes the object at this reference's location. + Future delete() => _delegate.delete(); + + /// Fetches a long lived download URL for this object. + Future getDownloadURL() => _delegate.getDownloadURL(); + + /// Fetches metadata for the object at this location, if one exists. + Future getMetadata() => _delegate.getMetadata(); + + /// List items (files) and prefixes (folders) under this storage reference. + /// + /// List API is only available for Firebase Rules Version 2. + /// + /// GCS is a key-blob store. Firebase Storage imposes the semantic of '/' + /// delimited folder structure. Refer to GCS's List API if you want to learn more. + /// + /// To adhere to Firebase Rules's Semantics, Firebase Storage does not support + /// objects whose paths end with "/" or contain two consecutive "/"s. Firebase + /// Storage List API will filter these unsupported objects. [list] may fail + /// if there are too many unsupported objects in the bucket. + Future list([ListOptions? options]) async { + assert(options == null || + options.maxResults == null || + options.maxResults! > 0 && options.maxResults! <= 1000); + return ListResult._(storage, await _delegate.list(options)); + } + + /// List all items (files) and prefixes (folders) under this storage reference. + /// + /// This is a helper method for calling [list] repeatedly until there are no + /// more results. The default pagination size is 1000. + /// + /// Note: The results may not be consistent if objects are changed while this + /// operation is running. + /// + /// Warning: [listAll] may potentially consume too many resources if there are + /// too many results. + Future listAll() async { + return ListResult._(storage, await _delegate.listAll()); + } + + /// Asynchronously downloads the object at the StorageReference to a list in memory. + /// + /// Returns a [Uint8List] of the data. + /// + /// If the [maxSize] (in bytes) is exceeded, the operation will be canceled. By + /// default the [maxSize] is 10mb (10485760 bytes). + Future getData([int maxSize = 10485760]) async { + assert(maxSize > 0); + return _delegate.getData(maxSize); + } + + /// Uploads data to this reference's location. + /// + /// Use this method to upload fixed sized data as a [Uint8List]. + /// + /// Optionally, you can also set metadata onto the uploaded object. + UploadTask putData(Uint8List data, [SettableMetadata? metadata]) { + return UploadTask._(storage, _delegate.putData(data, metadata)); + } + + /// Upload a [Blob]. Note; this is only supported on web platforms. + /// + /// Optionally, you can also set metadata onto the uploaded object. + UploadTask putBlob(dynamic blob, [SettableMetadata? metadata]) { + assert(blob != null); + return UploadTask._(storage, _delegate.putBlob(blob, metadata)); + } + + /// Upload a [File] from the filesystem. The file must exist. + /// + /// Optionally, you can also set metadata onto the uploaded object. + UploadTask putFile(File file, [SettableMetadata? metadata]) { + assert(file.absolute.existsSync()); + return UploadTask._(storage, _delegate.putFile(file, metadata)); + } + + /// Upload a [String] value as a storage object. + /// + /// Use [PutStringFormat] to correctly encode the string: + /// - [PutStringFormat.raw] the string will be encoded in a Base64 format. + /// - [PutStringFormat.dataUrl] the string must be in a data url format + /// (e.g. "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="). If no + /// [SettableMetadata.mimeType] is provided as part of the [metadata] + /// argument, the [mimeType] will be automatically set. + /// - [PutStringFormat.base64] will be encoded as a Base64 string. + /// - [PutStringFormat.base64Url] will be encoded as a Base64 string safe URL. + UploadTask putString( + String data, { + PutStringFormat format = PutStringFormat.raw, + SettableMetadata? metadata, + }) { + String _data = data; + PutStringFormat _format = format; + SettableMetadata? _metadata = metadata; + + // Convert any raw string values into a Base64 format + if (format == PutStringFormat.raw) { + _data = base64.encode(utf8.encode(_data)); + _format = PutStringFormat.base64; + } + + // Convert a data_url into a Base64 format + if (format == PutStringFormat.dataUrl) { + _format = PutStringFormat.base64; + UriData uri = UriData.fromUri(Uri.parse(data)); + assert(uri.isBase64); + _data = uri.contentText; + + if (_metadata == null && uri.mimeType.isNotEmpty) { + _metadata = SettableMetadata( + contentType: uri.mimeType, + ); + } + + // If the data_url contains a mime-type & the user has not provided it, + // set it + if ((_metadata!.contentType == null || _metadata.contentType!.isEmpty) && + uri.mimeType.isNotEmpty) { + _metadata = SettableMetadata( + cacheControl: metadata!.cacheControl, + contentDisposition: metadata.contentDisposition, + contentEncoding: metadata.contentEncoding, + contentLanguage: metadata.contentLanguage, + contentType: uri.mimeType, + ); + } + } + return UploadTask._( + storage, _delegate.putString(_data, _format, _metadata)); + } + + /// Updates the metadata on a storage object. + Future updateMetadata(SettableMetadata metadata) { + return _delegate.updateMetadata(metadata); + } + + /// Writes a remote storage object to the local filesystem. + /// + /// If a file already exists at the given location, it will be overwritten. + DownloadTask writeToFile(File file) { + return DownloadTask._(storage, _delegate.writeToFile(file)); + } + + @override + bool operator ==(Object other) => + other is Reference && + other.fullPath == fullPath && + other.storage == storage; + + @override + String toString() => + '$Reference(app: ${storage.app.name}, fullPath: $fullPath)'; + + @override + // TODO: implement hashCode + int get hashCode => super.hashCode; +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart new file mode 100644 index 00000000..97ce06f4 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart @@ -0,0 +1,91 @@ +part of firebase_storage_dart; + +/// A class representing an on-going storage task that additionally delegates to a [Future]. +abstract class Task implements Future { + Task._(this.storage, this._delegate) { + TaskPlatform.verifyExtends(_delegate); + } + + TaskPlatform _delegate; + + /// The [FirebaseStorage] instance associated with this task. + final FirebaseStorage storage; + + /// Returns a [Stream] of [TaskSnapshot] events. + /// + /// If the task is canceled or fails, the stream will send an error event. + /// See [TaskState] for more information of the different event types. + /// + /// If you do not need to know about on-going stream events, you can instead + /// await this [Task] directly. + Stream get snapshotEvents { + return _delegate.snapshotEvents + .map((snapshotDelegate) => TaskSnapshot._(storage, snapshotDelegate)); + } + + /// The latest [TaskSnapshot] for this task. + TaskSnapshot get snapshot { + return TaskSnapshot._(storage, _delegate.snapshot); + } + + /// Pauses the current task. + /// + /// Calling this method will trigger a snapshot event with a [TaskState.paused] + /// state. + Future pause() => _delegate.pause(); + + /// Resumes the current task. + /// + /// Calling this method will trigger a snapshot event with a [TaskState.running] + /// state. + Future resume() => _delegate.resume(); + + /// Cancels the current task. + /// + /// Calling this method will cause the task to fail. Both the delegating task Future + /// and stream ([snapshotEvents]) will trigger an error with a [FirebaseException]. + Future cancel() => _delegate.cancel(); + + @override + Stream asStream() => + _delegate.onComplete.asStream().map((_) => snapshot); + + @override + Future catchError(Function onError, + {bool Function(Object error)? test}) async { + await _delegate.onComplete.catchError(onError, test: test); + return snapshot; + } + + @override + Future then(FutureOr Function(TaskSnapshot) onValue, + {Function? onError}) => + _delegate.onComplete.then((_) { + return onValue(snapshot); + }, onError: onError); + + @override + Future whenComplete(FutureOr Function() action) async { + await _delegate.onComplete.whenComplete(action); + return snapshot; + } + + @override + Future timeout(Duration timeLimit, + {FutureOr Function()? onTimeout}) => + _delegate.onComplete + .then((_) => snapshot) + .timeout(timeLimit, onTimeout: onTimeout); +} + +/// A class which indicates an on-going upload task. +class UploadTask extends Task { + UploadTask._(FirebaseStorage storage, TaskPlatform delegate) + : super._(storage, delegate); +} + +/// A class which indicates an on-going download task. +class DownloadTask extends Task { + DownloadTask._(FirebaseStorage storage, TaskPlatform delegate) + : super._(storage, delegate); +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart new file mode 100644 index 00000000..361783ae --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart @@ -0,0 +1,48 @@ +part of firebase_storage_dart; + +/// A [TaskSnapshot] is returned as the result or on-going process of a [Task]. +class TaskSnapshot { + TaskSnapshot._(this.storage, this._delegate) { + TaskSnapshotPlatform.verifyExtends(_delegate); + } + + TaskSnapshotPlatform _delegate; + + /// The [FirebaseStorage] instance used to create the task. + final FirebaseStorage storage; + + /// The current transferred bytes of this task. + int get bytesTransferred => _delegate.bytesTransferred; + + /// The [FullMetadata] associated with this task. + /// + /// May be `null` if no metadata exists. + FullMetadata? get metadata => _delegate.metadata; + + /// The [Reference] for this snapshot. + Reference get ref { + return Reference._(storage, _delegate.ref); + } + + /// The current task snapshot state. + /// + /// The state indicates the current progress of the task, such as whether it + /// is running, paused or completed. + TaskState get state => _delegate.state; + + /// The total bytes of the task. + /// + /// Note; when performing a download task, the value of `-1` will be provided + /// whilst the total size of the remote file is being determined. + int get totalBytes => _delegate.totalBytes; + + @override + bool operator ==(Object other) => + other is TaskSnapshot && other.ref == ref && other.storage == storage; + + @override + int get hashCode => hashValues(storage, ref); + + @override + String toString() => '$TaskSnapshot(ref: $ref, state: $state)'; +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart new file mode 100644 index 00000000..5820bdd4 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart @@ -0,0 +1,91 @@ +/// Returns a bucket from a given `gs://` URL. +String bucketFromGoogleStorageUrl(String url) { + assert(url.startsWith('gs://')); + int stopIndex = url.indexOf('/', 5); + int stop = stopIndex == -1 ? url.length : stopIndex; + return url.substring(5, stop); +} + +/// Returns a path from a given `gs://` URL. +/// +/// If no path exists, the root path will be returned. +String pathFromGoogleStorageUrl(String url) { + assert(url.startsWith('gs://')); + int stopIndex = url.indexOf('/', 5); + if (stopIndex == -1) return '/'; + return url.substring(stopIndex + 1, url.length); +} + +const String _firebaseStorageHost = 'firebasestorage.googleapis.com'; +const String _cloudStorageHost = + '(?:storage.googleapis.com|storage.cloud.google.com)'; +const String _bucketDomain = r'([A-Za-z0-9.\-_]+)'; +const String _version = 'v[A-Za-z0-9_]+'; +const String _firebaseStoragePath = r'(/([^?#]*).*)?$'; +const String _cloudStoragePath = r'([^?#]*)*$'; +const String _optionalPort = r'(?::\d+)?'; + +/// Returns a path from a given `http://` or `https://` URL. +/// +/// If url fails to parse, null is returned +/// If no path exists, the root path will be returned. +Map? partsFromHttpUrl(String url) { + assert(url.startsWith('http')); + String? decodedUrl = _decodeHttpUrl(url); + if (decodedUrl == null) { + return null; + } + + // firebase storage url + if (decodedUrl.contains(_firebaseStorageHost) || + decodedUrl.contains('localhost')) { + String origin; + if (decodedUrl.contains('localhost')) { + Uri uri = Uri.parse(url); + origin = '^http?://${uri.host}:${uri.port}'; + } else { + origin = '^https?://$_firebaseStorageHost'; + } + + RegExp firebaseStorageRegExp = RegExp( + '$origin$_optionalPort/$_version/b/$_bucketDomain/o$_firebaseStoragePath', + caseSensitive: false, + ); + + RegExpMatch? match = firebaseStorageRegExp.firstMatch(decodedUrl); + + if (match == null) { + return null; + } + + return { + 'bucket': match.group(1), + 'path': match.group(3), + }; + // google cloud storage url + } else { + RegExp cloudStorageRegExp = RegExp( + '^https?://$_cloudStorageHost$_optionalPort/$_bucketDomain/$_cloudStoragePath', + caseSensitive: false, + ); + + RegExpMatch? match = cloudStorageRegExp.firstMatch(decodedUrl); + + if (match == null) { + return null; + } + + return { + 'bucket': match.group(1), + 'path': match.group(2), + }; + } +} + +String? _decodeHttpUrl(String url) { + try { + return Uri.decodeFull(url); + } catch (_) { + return null; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/pubspec.yaml b/packages/firebase_storage/firebase_storage_dart/pubspec.yaml new file mode 100644 index 00000000..fa9ae7bf --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/pubspec.yaml @@ -0,0 +1,18 @@ +name: firebase_storage_dart +description: A starting point for Dart libraries or applications. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.16.2 <3.0.0' + + +dependencies: + firebase_auth_dart: ^0.1.1 + firebase_core_dart: ^0.1.1 + firebase_storage_dart: + path: ../firebase_storage_dart + +dev_dependencies: + lints: ^1.0.0 + test: ^1.16.0 diff --git a/packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart b/packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart new file mode 100644 index 00000000..7c134017 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart @@ -0,0 +1,6 @@ +import 'package:firebase_storage_dart/firebase_storage_dart.dart'; +import 'package:test/test.dart'; + +void main() { + group('A group of tests', () {}); +} diff --git a/packages/firebase_storage/firebase_storage_desktop/.gitignore b/packages/firebase_storage/firebase_storage_desktop/.gitignore new file mode 100644 index 00000000..9be145fd --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/firebase_storage/firebase_storage_desktop/.metadata b/packages/firebase_storage/firebase_storage_desktop/.metadata new file mode 100644 index 00000000..50694c14 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: c860cba910319332564e1e9d470a17074c1f2dfd + channel: stable + +project_type: package diff --git a/packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md b/packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/firebase_storage/firebase_storage_desktop/LICENSE b/packages/firebase_storage/firebase_storage_desktop/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/firebase_storage/firebase_storage_desktop/README.md b/packages/firebase_storage/firebase_storage_desktop/README.md new file mode 100644 index 00000000..8b55e735 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml b/packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart b/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart new file mode 100644 index 00000000..e8241805 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart @@ -0,0 +1,178 @@ +library firebase_storage_desktop; + +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_dart/firebase_core_dart.dart' as core_dart; + +class FirebaseStorageDesktop extends FirebaseStoragePlatform { + FirebaseStorageDesktop({required FirebaseApp app, required String bucket}) + : _app = core_dart.Firebase.app(app.name), + super(appInstance: app, bucket: bucket); + + /// Stub initializer to allow creating an instance without + /// registering delegates or listeners. + /// + // ignore: prefer_constructors_over_static_methods + static FirebaseStorageDesktop get instance { + return FirebaseStorageDesktop.instanceFor( + app: Firebase.app(), + ); + } + + /// Returns an instance using a specified [FirebaseApp] and/or custom storage bucket. + /// + /// If [app] is not provided, the default Firebase app will be used. + /// If [bucket] is not provided, the default storage bucket will be used. + static FirebaseStorageDesktop instanceFor({ + FirebaseApp? app, + String? bucket, + }) { + app ??= Firebase.app(); + + if (bucket == null && app.options.storageBucket == null) { + if (app.name == defaultFirebaseAppName) { + _throwNoBucketError( + 'No default storage bucket could be found. Ensure you have correctly followed the Getting Started guide.'); + } else { + _throwNoBucketError( + "No storage bucket could be found for the app '${app.name}'. Ensure you have set the [storageBucket] on [FirebaseOptions] whilst initializing the secondary Firebase app."); + } + } + + String _bucket = bucket ?? app.options.storageBucket!; + + // Previous versions allow storage buckets starting with "gs://". + // Since we need to create a key using the bucket, it must not include "gs://" + // since native does not include it when requesting the bucket. This keeps + // the code backwards compatible but also works with the refactor. + if (_bucket.startsWith('gs://')) { + _bucket = _bucket.replaceFirst('gs://', ''); + } + + String key = '${app.name}|$_bucket'; + if (_cachedInstances.containsKey(key)) { + return _cachedInstances[key]!; + } + + FirebaseStorageDesktop newInstance = + FirebaseStorageDesktop(app: app, bucket: _bucket); + _cachedInstances[key] = newInstance; + + return newInstance; + } + + static final Map _cachedInstances = {}; + final core_dart.FirebaseApp? _app; + FirebaseStoragePlatform? _delegatePackingProperty; + FirebaseStoragePlatform get _delegate { + return _delegatePackingProperty ??= FirebaseStoragePlatform.instanceFor( + app: app, + bucket: bucket, + ); + } + + /// The maximum time to retry operations other than uploads or downloads in milliseconds. + @override + int get maxOperationRetryTime { + return _delegate.maxOperationRetryTime; + } + + /// The maximum time to retry uploads in milliseconds. + @override + int get maxUploadRetryTime { + return _delegate.maxUploadRetryTime; + } + + /// The maximum time to retry downloads in milliseconds. + @override + int get maxDownloadRetryTime { + return _delegate.maxDownloadRetryTime; + } + + /// Enables delegates to create new instances of themselves if a none default + /// [FirebaseApp] instance is required by the user. + @override + FirebaseStoragePlatform delegateFor( + {required FirebaseApp app, required String bucket}) { + if (app.options.storageBucket == null) { + if (app.name == defaultFirebaseAppName) { + _throwNoBucketError( + 'No default storage bucket could be found. Ensure you have correctly followed the Getting Started guide.'); + } else { + _throwNoBucketError( + "No storage bucket could be found for the app '${app.name}'. Ensure you have set the [storageBucket] on [FirebaseOptions] whilst initializing the secondary Firebase app."); + } + } + + String _bucket = bucket; + + // Previous versions allow storage buckets starting with "gs://". + // Since we need to create a key using the bucket, it must not include "gs://" + // since native does not include it when requesting the bucket. This keeps + // the code backwards compatible but also works with the refactor. + if (bucket.startsWith('gs://')) { + _bucket = _bucket.replaceFirst('gs://', ''); + } + + String key = '${app.name}|$_bucket'; + if (_cachedInstances.containsKey(key)) { + return _cachedInstances[key]!; + } + + FirebaseStorageDesktop newInstance = + FirebaseStorageDesktop(app: app, bucket: _bucket); + _cachedInstances[key] = newInstance; + + return newInstance; + } + + /// Returns a reference for the given path in the default bucket. + /// + /// [path] A relative path to initialize the reference with, for example + /// `path/to/image.jpg`. If not passed, the returned reference points to + /// the bucket root. + @override + ReferencePlatform ref(String path) { + throw UnimplementedError('ref() is not implemented'); + } + + /// Changes this instance to point to a Storage emulator running locally. + /// + /// Set the [host] (ex: localhost) and [port] (ex: 9199) of the local emulator. + /// + /// Note: Must be called immediately, prior to accessing storage methods. + /// Do not use with production credentials as emulator traffic is not encrypted. + /// + /// Note: storage emulator is not supported for web yet. firebase-js-sdk does not support + /// storage.useStorageEmulator until v9 + @override + Future useStorageEmulator(String host, int port) { + throw UnimplementedError('useStorageEmulator() is not implemented'); + } + + /// The new maximum operation retry time in milliseconds. + @override + void setMaxOperationRetryTime(int time) { + assert(!time.isNegative); + return _delegate.setMaxOperationRetryTime(time); + } + + /// The new maximum upload retry time in milliseconds. + @override + void setMaxUploadRetryTime(int time) { + assert(!time.isNegative); + return _delegate.setMaxUploadRetryTime(time); + } + + /// The new maximum download retry time in milliseconds. + @override + void setMaxDownloadRetryTime(int time) { + assert(!time.isNegative); + return _delegate.setMaxDownloadRetryTime(time); + } +} + +void _throwNoBucketError(String message) { + throw FirebaseException( + plugin: 'firebase_storage', code: 'no-bucket', message: message); +} diff --git a/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml b/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml new file mode 100644 index 00000000..8d2deef4 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml @@ -0,0 +1,68 @@ +name: firebase_storage_desktop +description: A new Flutter package project. +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.16.2 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + firebase_storage_platform_interface: ^4.1.4 + firebase_core_dart: ^0.1.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + plugin: + implements: firebase_storage + platforms: + macos: + dartPluginClass: FirebaseStorageDesktop + pluginClass: none + linux: + dartPluginClass: FirebaseStorageDesktop + pluginClass: none + windows: + dartPluginClass: FirebaseStorageDesktop + pluginClass: none + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart b/packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart new file mode 100644 index 00000000..0070a8b3 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart @@ -0,0 +1,7 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:firebase_storage_desktop/firebase_storage_desktop.dart'; + +void main() { + test('adds one to input values', () {}); +} From b97a732be3e54759b5925acc7af39c2b2501d5fc Mon Sep 17 00:00:00 2001 From: Ahmed Elshorbagy Date: Tue, 26 Apr 2022 14:20:06 +0200 Subject: [PATCH 2/5] desktop restructuring /dart start --- .vscode/settings.json | 118 ++++++- .../firebase_storage_dart/LICENSE | 29 ++ .../lib/firebase_storage_dart.dart | 13 +- .../lib/src/api/api.dart | 75 ++++ .../lib/src/api/emulator.dart | 23 ++ .../lib/src/api/errors.dart | 90 +++++ .../lib/src/firebase_storage.dart | 32 +- .../lib/src/implementations/constants.dart | 29 ++ .../lib/src/implementations/location.dart | 21 ++ .../lib/src/implementations/paths.dart | 36 ++ .../lib/src/implementations/urls.dart | 7 + .../lib/src/list_result.dart | 2 +- .../platform_interface_list_result.dart | 37 -- .../lib/src/reference.dart | 82 ++++- .../firebase_storage_dart/lib/src/task.dart | 34 +- .../firebase_storage_dart/pubspec.yaml | 6 +- .../firebase_storage_desktop/LICENSE | 30 +- .../lib/firebase_storage_desktop.dart | 3 + .../lib/src/desktop_firebase_storage.dart | 188 ++++++++++ .../lib/src/desktop_list_result.dart | 34 ++ .../lib/src/desktop_reference.dart | 232 +++++++++++++ .../lib/src/desktop_task.dart | 326 ++++++++++++++++++ .../lib/src/desktop_task_snapshot.dart | 24 ++ .../lib/src/utils/exceptions.dart | 63 ++++ .../firebase_storage_desktop/pubspec.yaml | 4 +- 25 files changed, 1458 insertions(+), 80 deletions(-) create mode 100644 packages/firebase_storage/firebase_storage_dart/LICENSE create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/implementations/location.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/implementations/paths.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart delete mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_list_result.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart create mode 100644 packages/firebase_storage/firebase_storage_desktop/lib/src/utils/exceptions.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 134352eb..8beab843 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,120 @@ { "dart.lineLength": 80, - "dart.runPubGetOnPubspecChanges": false + "dart.runPubGetOnPubspecChanges": false, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "all_lint_rules.yaml": true, + "tests": true, + ".gitignore": true, + "analysis_options.yaml": true, + "melos.yaml": true, + "README.md": true, + "packages/firebase_auth/firebase_auth_desktop/.dart_tool": true, + "packages/firebase_auth/firebase_auth_desktop/pubspec.lock": true, + "packages/firebase_auth/firebase_auth_desktop/linux": true, + "packages/firebase_auth/firebase_auth_desktop/macos": true, + "packages/firebase_auth/firebase_auth_desktop/test": true, + "packages/firebase_auth/firebase_auth_desktop/windows": true, + "packages/firebase_auth/firebase_auth_desktop/.gitignore": true, + "packages/firebase_auth/firebase_auth_desktop/.metadata": true, + "packages/firebase_auth/firebase_auth_desktop/.packages": true, + "packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md": true, + "packages/firebase_auth/firebase_auth_desktop/LICENSE": true, + "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml": true, + "packages/firebase_auth/firebase_auth_desktop/README.md": true, + ".vscode": true, + ".dart_tool/melos_tool": true, + ".github": true, + "packages/firebase_auth/firebase_auth_dart/.dart_tool": true, + "packages/firebase_auth/firebase_auth_dart/example": true, + "packages/firebase_auth/firebase_auth_dart/pubspec.lock": true, + "packages/firebase_auth/firebase_auth_dart/test": true, + "packages/firebase_auth/firebase_auth_dart/.gitignore": true, + "packages/firebase_auth/firebase_auth_dart/.packages": true, + "packages/firebase_auth/firebase_auth_dart/CHANGELOG.md": true, + "packages/firebase_auth/firebase_auth_dart/LICENSE": true, + "packages/firebase_functions/firebase_functions_dart/test": true, + "packages/firebase_functions/firebase_functions_dart/.dart_tool": true, + "packages/firebase_functions/firebase_functions_dart/.gitignore": true, + "packages/firebase_functions/firebase_functions_dart/.packages": true, + "packages/firebase_functions/firebase_functions_dart/CHANGELOG.md": true, + "packages/firebase_functions/firebase_functions_dart/LICENSE": true, + "packages/firebase_functions/firebase_functions_dart/pubspec.lock": true, + "packages/firebase_functions/firebase_functions_dart/pubspec.yaml": true, + "packages/firebase_functions/firebase_functions_dart/README.md": true, + "packages/firebase_functions/firebase_functions_desktop/macos": true, + "packages/firebase_functions/firebase_functions_desktop/.dart_tool": true, + "packages/firebase_functions/firebase_functions_desktop/example": true, + "packages/firebase_functions/firebase_functions_desktop/linux": true, + "packages/firebase_functions/firebase_functions_desktop/test": true, + "packages/firebase_functions/firebase_functions_desktop/windows": true, + "packages/firebase_functions/firebase_functions_desktop/.gitignore": true, + "packages/firebase_functions/firebase_functions_desktop/.metadata": true, + "packages/firebase_functions/firebase_functions_desktop/.packages": true, + "packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md": true, + "packages/firebase_functions/firebase_functions_desktop/LICENSE": true, + "packages/firebase_functions/firebase_functions_desktop/pubspec.lock": true, + "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml": true, + "packages/firebase_functions/firebase_functions_desktop/README.md": true + }, + "hide-files.files": [ + "all_lint_rules.yaml", + "tests", + ".gitignore", + "analysis_options.yaml", + "melos.yaml", + "README.md", + "packages/firebase_auth/firebase_auth_desktop/.dart_tool", + "packages/firebase_auth/firebase_auth_desktop/pubspec.lock", + "packages/firebase_auth/firebase_auth_desktop/linux", + "packages/firebase_auth/firebase_auth_desktop/macos", + "packages/firebase_auth/firebase_auth_desktop/test", + "packages/firebase_auth/firebase_auth_desktop/windows", + "packages/firebase_auth/firebase_auth_desktop/.gitignore", + "packages/firebase_auth/firebase_auth_desktop/.metadata", + "packages/firebase_auth/firebase_auth_desktop/.packages", + "packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md", + "packages/firebase_auth/firebase_auth_desktop/LICENSE", + "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml", + "packages/firebase_auth/firebase_auth_desktop/README.md", + ".vscode", + ".dart_tool/melos_tool", + ".github", + "packages/firebase_auth/firebase_auth_dart/.dart_tool", + "packages/firebase_auth/firebase_auth_dart/example", + "packages/firebase_auth/firebase_auth_dart/pubspec.lock", + "packages/firebase_auth/firebase_auth_dart/test", + "packages/firebase_auth/firebase_auth_dart/.gitignore", + "packages/firebase_auth/firebase_auth_dart/.packages", + "packages/firebase_auth/firebase_auth_dart/CHANGELOG.md", + "packages/firebase_auth/firebase_auth_dart/LICENSE", + "packages/firebase_functions/firebase_functions_dart/test", + "packages/firebase_functions/firebase_functions_dart/.dart_tool", + "packages/firebase_functions/firebase_functions_dart/.gitignore", + "packages/firebase_functions/firebase_functions_dart/.packages", + "packages/firebase_functions/firebase_functions_dart/CHANGELOG.md", + "packages/firebase_functions/firebase_functions_dart/LICENSE", + "packages/firebase_functions/firebase_functions_dart/pubspec.lock", + "packages/firebase_functions/firebase_functions_dart/pubspec.yaml", + "packages/firebase_functions/firebase_functions_dart/README.md", + "packages/firebase_functions/firebase_functions_desktop/macos", + "packages/firebase_functions/firebase_functions_desktop/.dart_tool", + "packages/firebase_functions/firebase_functions_desktop/example", + "packages/firebase_functions/firebase_functions_desktop/linux", + "packages/firebase_functions/firebase_functions_desktop/test", + "packages/firebase_functions/firebase_functions_desktop/windows", + "packages/firebase_functions/firebase_functions_desktop/.gitignore", + "packages/firebase_functions/firebase_functions_desktop/.metadata", + "packages/firebase_functions/firebase_functions_desktop/.packages", + "packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md", + "packages/firebase_functions/firebase_functions_desktop/LICENSE", + "packages/firebase_functions/firebase_functions_desktop/pubspec.lock", + "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml", + "packages/firebase_functions/firebase_functions_desktop/README.md" + ] } diff --git a/packages/firebase_storage/firebase_storage_dart/LICENSE b/packages/firebase_storage/firebase_storage_dart/LICENSE new file mode 100644 index 00000000..5841c4de --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/LICENSE @@ -0,0 +1,29 @@ +BSD-3-Clause +------------ + +Copyright (c) 2016-present Invertase Limited & Contributors + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited & Contributors + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the BSD-3-Clause License (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart index c82879eb..e2301986 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart @@ -7,16 +7,23 @@ import 'dart:async'; import 'dart:convert' show utf8, base64; import 'dart:io' show File; import 'dart:typed_data' show Uint8List; - +import 'package:firebase_storage_dart/src/api/api.dart'; +import 'package:firebase_storage_dart/src/implementations/location.dart'; +import 'package:firebase_storage_dart/src/implementations/paths.dart' as paths; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; +import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebase_storage_dart/src/data_models/full_metadata.dart'; import 'package:firebase_storage_dart/src/data_models/list_options.dart'; import 'package:firebase_storage_dart/src/data_models/put_string_format.dart'; import 'package:firebase_storage_dart/src/data_models/settable_metadata.dart'; -import 'package:firebase_storage_dart/src/firebase_storage.dart'; -import 'package:firebase_storage_dart/src/platform_interface/platform_interface_list_result.dart'; +import 'package:firebase_storage_dart/src/data_models/task_state.dart'; + +import 'package:firebase_storage_dart/src/utils.dart'; // TODO: Export any libraries intended for clients of this package. // part 'src/utils.dart'; +part 'src/firebase_storage.dart'; part 'src/reference.dart'; part 'src/list_result.dart'; part 'src/task.dart'; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart new file mode 100644 index 00000000..32438149 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart @@ -0,0 +1,75 @@ +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. + +// ignore_for_file: implementation_imports + +library api; + +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; +import 'package:googleapis_auth/auth_io.dart' + if (dart.library.html) 'package:googleapis_auth/auth_browser.dart'; +part 'emulator.dart'; + +/// Configurations necessary for making all idp requests. +@protected +class APIConfig { + /// Construct [APIConfig]. + APIConfig(this.apiKey, this.projectId); + + /// The API Key associated with the Firebase project used for initialization. + final String apiKey; + + /// The project Id associated with the Firebase project used for initialization. + final String projectId; + + EmulatorConfig? _emulator; + + /// Get the current [EmulatorConfig] or null. + EmulatorConfig? get emulator => _emulator; + + /// Set a new [EmulatorConfig] or null. + void setEmulator(String host, int port) { + _emulator = EmulatorConfig.use(host, port); + } +} + +/// Pure Dart service layer to perform all requests +/// with the underlying Identity Toolkit API. +/// +/// See: https://cloud.google.com/identity-platform/docs/use-rest-api +@protected +class API { + API._(this.apiConfig, {http.Client? client}) { + _client = client ?? clientViaApiKey(apiConfig.apiKey); + } + + /// Construct new or existing [API] instance for a given [APIConfig]. + factory API.instanceOf(APIConfig apiConfig, {http.Client? client}) { + return _instances.putIfAbsent( + apiConfig, + () => API._(apiConfig, client: client), + ); + } + + /// The API configurations of this instance. + final APIConfig apiConfig; + + static final Map _instances = {}; + + http.Client? _client; + + String? _languageCode; + + /// The current languageCode sent in the headers of all API requests. + /// If `null`, the default Firebase Console language will be used. + String? get languageCode => _languageCode; + + /// Change the HTTP client for the purpose of testing. + @internal + // ignore: avoid_setters_without_getters + set client(http.Client client) { + _client = client; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart new file mode 100644 index 00000000..80d65160 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart @@ -0,0 +1,23 @@ +part of api; + +/// A type to hold the Auth Emulator configurations. +class EmulatorConfig { + EmulatorConfig._({ + required this.port, + required this.host, + }); + + /// Initialize the Emulator Config using the host and port printed once running `firebase emulators:start`. + factory EmulatorConfig.use(String host, int port) { + return EmulatorConfig._(port: port, host: host); + } + + /// The port on which the emulator suite is running. + final String host; + + /// The port on which the emulator suite is running. + final int port; + + /// The root URL used to make requests to the locally running emulator. + String get rootUrl => 'http://$host:$port/www.googleapis.com/'; +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart new file mode 100644 index 00000000..d1b6f4b7 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart @@ -0,0 +1,90 @@ +enum StorageErrorCode { + // Shared between all platforms + UNKNOWN, + OBJECT_NOT_FOUND, + BUCKET_NOT_FOUND, + PROJECT_NOT_FOUND, + QUOTA_EXCEEDED, + UNAUTHENTICATED, + UNAUTHORIZED, + UNAUTHORIZED_APP, + RETRY_LIMIT_EXCEEDED, + INVALID_CHECKSUM, + CANCELED, + // JS specific + INVALID_EVENT_NAME, + INVALID_URL, + INVALID_DEFAULT_BUCKET, + NO_DEFAULT_BUCKET, + CANNOT_SLICE_BLOB, + SERVER_FILE_WRONG_SIZE, + NO_DOWNLOAD_URL, + INVALID_ARGUMENT, + INVALID_ARGUMENT_COUNT, + APP_DELETED, + INVALID_ROOT_OPERATION, + INVALID_FORMAT, + INTERNAL_ERROR, + UNSUPPORTED_ENVIRONMENT, +} + +extension ToString on StorageErrorCode { + String get asString { + return name.toLowerCase().replaceAll('_', '-'); + } +} + +class StorageError { + late String _baseMessage; + String message; + final StorageErrorCode code; + /** + * Stores custom error data unque to StorageError. + */ + String? _serverResponse; + + /// + /// @param code - A StorageErrorCode string to be prefixed with 'storage/' and + /// added to the end of the message. + /// @param message - Error message. + /// + StorageError(this.code, this.message) { + _baseMessage = message; + } + + /// Compares a StorageErrorCode against this error's code, filtering out the prefix. + + bool _codeEquals(StorageErrorCode code) { + return prependCode(code) == this.code; + } + + /** + * Optional response message that was added by the server. + */ + String? get serverResponse { + return _serverResponse; + } + + set serverResponse(String? serverResponse) { + _serverResponse = serverResponse; + if (_serverResponse != null) { + message = '${_baseMessage}\n${_serverResponse}'; + } else { + message = _baseMessage; + } + } +} + +String prependCode(StorageErrorCode code) { + return 'storage/' + code.name; +} + +StorageError invalidDefaultBucket(String bucket) { + return StorageError(StorageErrorCode.INVALID_DEFAULT_BUCKET, + "Invalid default bucket '" + bucket + "'."); +} + +StorageError invalidUrl(String url) { + return StorageError( + StorageErrorCode.INVALID_URL, "Invalid URL '" + url + "'."); +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart index 6088b30d..32ecb4ab 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart @@ -1,16 +1,38 @@ -import 'package:firebase_core_dart/firebase_core_dart.dart'; -import 'package:firebase_storage_dart/firebase_storage_dart.dart'; -import 'package:firebase_storage_dart/src/utils.dart'; +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. +// ignore_for_file: require_trailing_commas + +part of firebase_storage_dart; + +/// Pure Dart FirebaseAuth implementation. /// The entrypoint for [FirebaseStorage]. class FirebaseStorage { /// Creates Firebase Functions // @visibleForTesting - FirebaseStorage({required this.app, required this.bucket}); + FirebaseStorage({required this.app, required this.bucket}) { + _api = API.instanceOf( + APIConfig( + app.options.apiKey, + app.options.projectId, + ), + ); + } /// The [FirebaseApp] for this current [FirebaseStorage] instance. FirebaseApp app; + /// Initialized [API] instance linked to this instance. + late final API _api; + + /// Change the HTTP client for the purpose of testing. + @visibleForTesting + // ignore: avoid_setters_without_getters + set client(http.Client client) { + _api.client = client; + } + /// The storage bucket of this instance. String bucket; @@ -85,7 +107,7 @@ class FirebaseStorage { /// storage bucket. Reference ref([String? path]) { path ??= '/'; - throw UnimplementedError(); + return Reference.fromPath(storage: this, path: path); } /// Returns a new [Reference] from a given URL. diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart new file mode 100644 index 00000000..bc6d39ed --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart @@ -0,0 +1,29 @@ +/// +///Domain name for firebase storage. +/// +const DEFAULT_HOST = 'firebasestorage.googleapis.com'; + +/// +///The key in Firebase config json for the storage bucket. +/// +const CONFIG_STORAGE_BUCKET_KEY = 'storageBucket'; + +/// +///2 minutes +/// +///The timeout for all operations except upload. +/// +const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000; + +/// +///10 minutes +/// +///The timeout for upload. +/// +const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000; + +/// +///This is the value of Number.MIN_SAFE_INTEGER, which is not well supported +///enough for us to use it directly. +/// +const MIN_SAFE_INTEGER = -9007199254740991; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/location.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/location.dart new file mode 100644 index 00000000..4187a5b9 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/location.dart @@ -0,0 +1,21 @@ +class Location { + Location(String path, this.bucket) { + _path = path; + } + final String bucket; + late String _path; + String get path => _path; + + bool get isRoot => path.isEmpty; + + String fullServerUrl() { + return '/b/' + + Uri.encodeComponent(bucket) + + '/o/' + + Uri.encodeComponent(path); + } + + String bucketOnlyServerUrl() { + return '/b/' + Uri.encodeComponent(bucket) + '/o'; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/paths.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/paths.dart new file mode 100644 index 00000000..f246f6ec --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/paths.dart @@ -0,0 +1,36 @@ +String? parent(String path) { + if (path.isEmpty) { + return null; + } + final index = path.lastIndexOf('/'); + if (index == -1) { + return ''; + } + return path.substring(0, index); +} + +String child(String path, String childPath) { + final canonicalChildPath = childPath.split('/') + ..retainWhere((element) => element.isNotEmpty); + final joinedCanonicalChildPath = canonicalChildPath.join('/'); + if (path.isEmpty) { + return joinedCanonicalChildPath; + } else { + return path + '/' + joinedCanonicalChildPath; + } +} + +/// +///Returns the last component of a path. +///'/foo/bar' -> 'bar' +///'/foo/bar/baz/' -> 'baz/' +///'/a' -> 'a' +/// +String lastComponent(String path) { + final index = path.lastIndexOf('/', path.length - 2); + if (index == -1) { + return path; + } else { + return path.substring(index + 1); + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart new file mode 100644 index 00000000..c557d706 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart @@ -0,0 +1,7 @@ +String makeUrl(String urlPart, String host, String protocol) { + var origin = host; + if (protocol == null) { + origin = 'https://${host}'; + } + return '${protocol}://${origin}/v0${urlPart}'; +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart index f17a0e78..0ec9c9a7 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart @@ -3,7 +3,7 @@ part of firebase_storage_dart; /// Class returned as a result of calling a list method ([list] or [listAll]) /// on a [Reference]. class ListResult { - ListResult(this.storage, this._delegate) { + ListResult._(this.storage, this._delegate) { ListResultPlatform.verifyExtends(_delegate); } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart deleted file mode 100644 index 8831f019..00000000 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/platform_interface/platform_interface_list_result.dart +++ /dev/null @@ -1,37 +0,0 @@ -/// Result returned by [list]. -abstract class ListResultPlatform extends PlatformInterface { - /// Creates a new [ListResultPlatform] instance. - ListResultPlatform(this.storage, this.nextPageToken) : super(token: _token); - - static final Object _token = Object(); - - /// Throws an [AssertionError] if [instance] does not extend - /// [ReferencePlatform]. - /// - /// This is used by the app-facing [Reference] to ensure that - /// the object in which it's going to delegate calls has been - /// constructed properly. - static void verifyExtends(ListResultPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - } - - /// The [FirebaseStoragePlatform] used when fetching list items. - final FirebaseStoragePlatform? storage; - - /// Objects in this directory. You can call [getMetadata] and [getDownloadUrl] on them. - List get items { - throw UnimplementedError('items is not implemented'); - } - - /// If set, there might be more results for this list. Use this token to resume the list. - final String? nextPageToken; - - /// References to prefixes (sub-folders). You can call [list] on them to get its contents. - /// - /// Folders are implicit based on '/' in the object paths. For example, if a - /// bucket has two objects '/a/b/1' and '/a/b/2', list('/a') will - /// return '/a/b' as a prefix. - List get prefixes { - throw UnimplementedError('prefixes is not implemented'); - } -} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart index 2feea95c..3041b753 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart @@ -1,54 +1,102 @@ part of firebase_storage_dart; class Reference { - Reference._(this.storage, this._delegate) { - ReferencePlatform.verifyExtends(_delegate); - } - - ReferencePlatform _delegate; + Reference.fromPath({required this.storage,required String path}):location=Location(path,storage.bucket); + Reference._(this.storage, this.location); /// The storage service associated with this reference. final FirebaseStorage storage; + ///An fbs.location, or the URL at + /// which to base this object, in one of the following forms: + /// gs:/// + /// http[s]://firebasestorage.googleapis.com/ + /// /b//o/ + /// Any query or fragment strings will be ignored in the http[s] + /// format. If no value is passed, the storage object will use a URL based on + /// the project ID of the base firebase.App instance. + /// + final Location location; + /// The name of the bucket containing this reference's object. - String get bucket => _delegate.bucket; + String get bucket => location.bucket; /// The full path of this object. - String get fullPath => _delegate.fullPath; + String get fullPath => location.path; /// The short name of this object, which is the last component of the full path. /// /// For example, if fullPath is 'full/path/image.png', name is 'image.png'. - String get name => _delegate.name; + String get name => paths.lastComponent(location.path); /// A reference pointing to the parent location of this reference, or `null` /// if this reference is the root. Reference? get parent { - ReferencePlatform? referenceParentPlatform = _delegate.parent; - - if (referenceParentPlatform == null) { + final newPath = paths.parent(this.location.path); + if (newPath == null) { return null; } - - return Reference._(storage, referenceParentPlatform); + final newLocation = Location( + newPath, + location.bucket, + ); + return Reference._(storage, newLocation); } /// A reference to the root of this reference's bucket. - Reference get root => Reference._(storage, _delegate.root); + Reference get root { + final newLocation = Location( + '', + location.bucket, + ); + return Reference._(storage, newLocation); + } /// Returns a reference to a relative path from this reference. /// /// [path] The relative path from this reference. Leading, trailing, and /// consecutive slashes are removed. Reference child(String path) { - return Reference._(storage, _delegate.child(path)); + final newPath = paths.child(location.path, path); + final newLocation = Location( + newPath, + location.bucket, + ); + return Reference._(storage, newLocation); } /// Deletes the object at this reference's location. - Future delete() => _delegate.delete(); + Future delete() { + throw UnimplementedError(); + // final urlPart = location.fullServerUrl(); + // final url = makeUrl(urlPart, service.host, service._protocol); + // const method = 'DELETE'; + // final timeout = storage.maxOperationRetryTime; + + // void handler(Connection _xhr,String _text) {} + // final requestInfo = RequestInfo(url, method, handler, timeout); + // requestInfo.successCodes = [200, 204]; + // requestInfo.errorHandler = objectErrorHandler(location); + // return requestInfo; + } /// Fetches a long lived download URL for this object. - Future getDownloadURL() => _delegate.getDownloadURL(); + Future getDownloadURL() { + // ref._throwIfRoot('getDownloadURL'); + const requestInfo = requestsGetDownloadUrl( + storage, + location, + getMappings() + ); + return ref.storage + .makeRequestWithTokens(requestInfo, newTextConnection) + .then(url => { + if (url === null) { + throw noDownloadURL(); + } + return url; + }); + } /// Fetches metadata for the object at this location, if one exists. Future getMetadata() => _delegate.getMetadata(); diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart index 97ce06f4..3893d4a4 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart @@ -2,11 +2,9 @@ part of firebase_storage_dart; /// A class representing an on-going storage task that additionally delegates to a [Future]. abstract class Task implements Future { - Task._(this.storage, this._delegate) { - TaskPlatform.verifyExtends(_delegate); - } - - TaskPlatform _delegate; + Task._( + this.storage, + ); /// The [FirebaseStorage] instance associated with this task. final FirebaseStorage storage; @@ -32,7 +30,18 @@ abstract class Task implements Future { /// /// Calling this method will trigger a snapshot event with a [TaskState.paused] /// state. - Future pause() => _delegate.pause(); + Future pause() async { + if (snapshot.state == TaskState.paused) { + return true; + } + + final paused = _task.pause(); + // Wait until the snapshot is paused, then return the value of paused... + return snapshotEvents + .takeWhile((snapshot) => snapshot.state != TaskState.paused) + .last + .then((_) => paused); + } /// Resumes the current task. /// @@ -80,12 +89,17 @@ abstract class Task implements Future { /// A class which indicates an on-going upload task. class UploadTask extends Task { - UploadTask._(FirebaseStorage storage, TaskPlatform delegate) - : super._(storage, delegate); + UploadTask._( + FirebaseStorage storage, + ) : super._( + storage, + ); } /// A class which indicates an on-going download task. class DownloadTask extends Task { - DownloadTask._(FirebaseStorage storage, TaskPlatform delegate) - : super._(storage, delegate); + DownloadTask._(FirebaseStorage storage) + : super._( + storage, + ); } diff --git a/packages/firebase_storage/firebase_storage_dart/pubspec.yaml b/packages/firebase_storage/firebase_storage_dart/pubspec.yaml index fa9ae7bf..e6083008 100644 --- a/packages/firebase_storage/firebase_storage_dart/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_storage_dart description: A starting point for Dart libraries or applications. -version: 1.0.0 +version: 0.1.0-dev.0 # homepage: https://www.example.com environment: @@ -10,9 +10,7 @@ environment: dependencies: firebase_auth_dart: ^0.1.1 firebase_core_dart: ^0.1.1 - firebase_storage_dart: - path: ../firebase_storage_dart - + firebaseapis: ^0.1.1 dev_dependencies: lints: ^1.0.0 test: ^1.16.0 diff --git a/packages/firebase_storage/firebase_storage_desktop/LICENSE b/packages/firebase_storage/firebase_storage_desktop/LICENSE index ba75c69f..5841c4de 100644 --- a/packages/firebase_storage/firebase_storage_desktop/LICENSE +++ b/packages/firebase_storage/firebase_storage_desktop/LICENSE @@ -1 +1,29 @@ -TODO: Add your license here. +BSD-3-Clause +------------ + +Copyright (c) 2016-present Invertase Limited & Contributors + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited & Contributors + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the BSD-3-Clause License (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart b/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart index e8241805..4266c2e3 100644 --- a/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart +++ b/packages/firebase_storage/firebase_storage_desktop/lib/firebase_storage_desktop.dart @@ -1,3 +1,6 @@ +// Copyright 2022 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. library firebase_storage_desktop; import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart new file mode 100644 index 00000000..a9d50a00 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart @@ -0,0 +1,188 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_dart/firebase_core_dart.dart' as core_dart; +import 'package:firebase_storage_desktop/src/desktop_reference.dart'; +import 'package:firebase_storage_desktop/src/desktop_task_snapshot.dart'; +import 'package:firebase_storage_desktop/src/utils/exceptions.dart'; +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; +import 'package:firebase_storage_dart/firebase_storage_dart.dart' + as storage_dart; +import 'package:flutter/services.dart'; + +/// Method Channel delegate for [FirebaseStoragePlatform]. +class DesktopFirebaseStorage extends FirebaseStoragePlatform { + /// Creates a new [DesktopFirebaseStorage] instance with an [app] and/or + /// [bucket]. + DesktopFirebaseStorage({required FirebaseApp app, required String bucket}) + : _app = core_dart.Firebase.app(app.name), + super(appInstance: app, bucket: bucket) { + // The channel setMethodCallHandler callback is not app specific, so there + // is no need to register the caller more than once. + if (_initialized) return; + + _initialized = true; + } + + /// Internal stub class initializer. + /// + /// When the user code calls an storage method, the real instance is + /// then initialized via the [delegateFor] method. + DesktopFirebaseStorage._() + : _app = null, + super(appInstance: null, bucket: ''); + + /// Keep an internal reference to whether the [DesktopFirebaseStorage] + /// class has already been initialized. + static bool _initialized = false; + + /// Returns a unique key to identify the instance by [FirebaseApp] name and + /// any custom storage buckets. + static String _getInstanceKey(String /*!*/ appName, String bucket) { + return '$appName|$bucket'; + } + + /// Instance of storage from Identity Provider API service. + storage_dart.FirebaseStorage? get _delegate => _app == null + ? null + : storage_dart.FirebaseStorage.instanceFor(app: _app!); + + final core_dart.FirebaseApp? _app; + + static Map _desktopFirebaseStorageInstances = + {}; + + ///Method call handleer + ///Todo need restructring + Future handleMethodCalls(MethodCall call) async { + Map arguments = call.arguments; + + switch (call.method) { + case 'Task#onProgress': + return _handleTaskStateChange(TaskState.running, arguments); + case 'Task#onPaused': + return _handleTaskStateChange(TaskState.paused, arguments); + case 'Task#onSuccess': + return _handleTaskStateChange(TaskState.success, arguments); + case 'Task#onCanceled': + return _sendTaskException( + arguments['handle'], + FirebaseException( + plugin: 'firebase_storage', + code: 'canceled', + message: 'User canceled the upload/download.', + )); + case 'Task#onFailure': + Map errorMap = + Map.from(arguments['error']); + return _sendTaskException( + arguments['handle'], + FirebaseException( + plugin: 'firebase_storage', + code: errorMap['code'], + message: errorMap['message'], + )); + } + } + + /// Returns a stub instance to allow the platform interface to access + /// the class instance statically. + static DesktopFirebaseStorage get instance { + return DesktopFirebaseStorage._(); + } + + static int _methodChannelHandleId = 0; + + /// Increments and returns the next channel ID handler for Storage. + static int get nextDesktopHandleId => _methodChannelHandleId++; + + /// A map containing all Task stream observers, keyed by their handle. + static final Map> taskObservers = + >{}; + + @override + int maxOperationRetryTime = const Duration(minutes: 2).inMilliseconds; + + @override + int maxUploadRetryTime = const Duration(minutes: 10).inMilliseconds; + + @override + int maxDownloadRetryTime = const Duration(minutes: 10).inMilliseconds; + + Future _handleTaskStateChange( + TaskState taskState, Map arguments) async { + // Get & cast native snapshot data to a Map + Map snapshotData = + Map.from(arguments['snapshot']); + + // Get the cached Storage instance. + FirebaseStoragePlatform storage = _desktopFirebaseStorageInstances[ + _getInstanceKey(arguments['appName'], arguments['bucket'])]!; + + // Create a snapshot. + TaskSnapshotPlatform snapshot = + DesktopTaskSnapshot(storage, taskState, snapshotData); + + // Fire a snapshot event. + taskObservers[arguments['handle']]!.add(snapshot); + } + + void _sendTaskException(int handle, FirebaseException exception) { + taskObservers[handle]!.addError(exception); + } + + @override + FirebaseStoragePlatform delegateFor( + {required FirebaseApp app, required String bucket}) { + String key = _getInstanceKey(app.name, bucket); + + return _desktopFirebaseStorageInstances[key] ??= + DesktopFirebaseStorage(app: app, bucket: bucket); + } + + @override + ReferencePlatform ref(String path) { + return DesktopReference(this, path); + } + + @override + Future useStorageEmulator(String host, int port) async { + emulatorHost = host; + emulatorPort = port; + try { + await _delegate!.useStorageEmulator(host, port); + // await DesktopFirebaseStorage.channel + // .invokeMethod('Storage#useEmulator', { + // 'appName': app.name, + // 'maxOperationRetryTime': maxOperationRetryTime, + // 'maxUploadRetryTime': maxUploadRetryTime, + // 'maxDownloadRetryTime': maxDownloadRetryTime, + // 'bucket': bucket, + // 'host': emulatorHost, + // 'port': emulatorPort + // }); + } catch (e, s) { + convertPlatformException(e, s); + } + } + + @override + void setMaxOperationRetryTime(int time) { + maxOperationRetryTime = time; + } + + @override + void setMaxUploadRetryTime(int time) { + maxUploadRetryTime = time; + } + + @override + Future setMaxDownloadRetryTime(int time) async { + maxDownloadRetryTime = time; + } +} diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_list_result.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_list_result.dart new file mode 100644 index 00000000..d51a9354 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_list_result.dart @@ -0,0 +1,34 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_storage_desktop/src/desktop_reference.dart'; +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; + +/// Implementation for a [ListResultPlatform]. +class DesktopListResult extends ListResultPlatform { + // ignore: public_member_api_docs + DesktopListResult( + FirebaseStoragePlatform storage, { + String? nextPageToken, + List? items, + List? prefixes, + }) : _items = items ?? [], + _prefixes = prefixes ?? [], + super(storage, nextPageToken); + + List _items; + + List _prefixes; + + @override + List get items { + return _items.map((path) => DesktopReference(storage!, path)).toList(); + } + + @override + List get prefixes { + return _prefixes.map((path) => DesktopReference(storage!, path)).toList(); + } +} diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart new file mode 100644 index 00000000..37d119f3 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart @@ -0,0 +1,232 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:firebase_storage_desktop/src/desktop_firebase_storage.dart'; +import 'package:firebase_storage_desktop/src/desktop_list_result.dart'; +import 'package:firebase_storage_desktop/src/desktop_task.dart'; +import 'package:firebase_storage_desktop/src/utils/exceptions.dart'; +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; + +/// An implementation of [ReferencePlatform] that uses [Desktop] to +/// communicate with Firebase plugins. +class DesktopReference extends ReferencePlatform { + /// Creates a [ReferencePlatform] that is implemented using [Desktop]. + DesktopReference(FirebaseStoragePlatform storage, String path) + : super(storage, path); + + @override + Future delete() async { + throw UnimplementedError('delete() is not implemented'); + // try { + // await DesktopFirebaseStorage.channel + // .invokeMethod('Reference#delete', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // }); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + Future getDownloadURL() async { + throw UnimplementedError('getDownloadURL() is not implemented'); + // try { + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod( + // 'Reference#getDownloadURL', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // }); + + // return data!['downloadURL']; + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + Future getMetadata() async { + throw UnimplementedError('getMetadata() is not implemented'); + // try { + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod( + // 'Reference#getMetadata', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // }); + + // return FullMetadata(data!); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + Future list([ListOptions? options]) async { + throw UnimplementedError('getMetadata() is not implemented'); + // try { + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod('Reference#list', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // 'options': { + // 'maxResults': options?.maxResults ?? 1000, + // 'pageToken': options?.pageToken, + // }, + // }); + + // return DesktopListResult( + // storage, + // nextPageToken: data!['nextPageToken'], + // items: List.from(data['items']), + // prefixes: List.from(data['prefixes']), + // ); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + Future listAll() async { + throw UnimplementedError('listAll() is not implemented'); + // try { + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod( + // 'Reference#listAll', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // }); + // return DesktopListResult( + // storage, + // nextPageToken: data!['nextPageToken'], + // items: List.from(data['items']), + // prefixes: List.from(data['prefixes']), + // ); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + Future getData(int maxSize) { + throw UnimplementedError('getData(int maxSize) is not implemented'); + // try { + // return DesktopFirebaseStorage.channel + // .invokeMethod('Reference#getData', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // 'maxSize': maxSize, + // }); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + TaskPlatform putData(Uint8List data, [SettableMetadata? metadata]) { + int handle = DesktopFirebaseStorage.nextDesktopHandleId; + DesktopFirebaseStorage.taskObservers[handle] = + StreamController.broadcast(); + return DesktopPutTask(handle, storage, fullPath, data, metadata); + } + + @override + TaskPlatform putBlob(dynamic data, [SettableMetadata? metadata]) { + throw UnimplementedError( + 'putBlob() is not supported on native platforms. Use [put], [putFile] or [putString] instead.'); + } + + @override + TaskPlatform putFile(File file, [SettableMetadata? metadata]) { + int handle = DesktopFirebaseStorage.nextDesktopHandleId; + DesktopFirebaseStorage.taskObservers[handle] = + StreamController.broadcast(); + return DesktopPutFileTask(handle, storage, fullPath, file, metadata); + } + + @override + TaskPlatform putString(String data, PutStringFormat format, + [SettableMetadata? metadata]) { + int handle = DesktopFirebaseStorage.nextDesktopHandleId; + DesktopFirebaseStorage.taskObservers[handle] = + StreamController.broadcast(); + return DesktopPutStringTask( + handle, storage, fullPath, data, format, metadata); + } + + @override + Future updateMetadata(SettableMetadata metadata) async { + throw UnimplementedError( + 'updateMetadata(SettableMetadata metadata) is not implemented'); + // try { + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod( + // 'Reference#updateMetadata', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'path': fullPath, + // 'metadata': metadata.asMap(), + // }); + + // return FullMetadata(data!); + // } catch (e, stack) { + // convertPlatformException(e, stack); + // } + } + + @override + TaskPlatform writeToFile(File file) { + int handle = DesktopFirebaseStorage.nextDesktopHandleId; + DesktopFirebaseStorage.taskObservers[handle] = + StreamController.broadcast(); + return DesktopDownloadTask(handle, storage, fullPath, file); + } +} diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task.dart new file mode 100644 index 00000000..f4952e4a --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task.dart @@ -0,0 +1,326 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_storage_desktop/src/desktop_firebase_storage.dart'; +import 'package:firebase_storage_desktop/src/desktop_task_snapshot.dart'; +import 'package:firebase_storage_desktop/src/utils/exceptions.dart'; +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; + +/// Implementation for a [TaskPlatform]. +/// +/// Other implementations for specific tasks should extend this class. +class DesktopTask extends TaskPlatform { + /// Creates a new [DesktopTask] with a given task. + DesktopTask( + this._handle, + this.storage, + String path, + this._initialTask, + ) : super() { + // Keep reference to whether the initial "start" task has completed. + _initialTaskCompleter = Completer(); + _snapshot = DesktopTaskSnapshot(storage, TaskState.running, { + 'path': path, + 'bytesTransferred': 0, + 'totalBytes': 1, + }); + _initialTask().then((_) { + _initialTaskCompleter.complete(); + }).catchError((Object e, StackTrace stackTrace) { + _initialTaskCompleter.completeError(e, stackTrace); + _didComplete = true; + _exception = e; + _stackTrace = stackTrace; + if (_completer != null) { + catchFuturePlatformException(e, stackTrace) + .catchError(_completer!.completeError); + } + }); + + // Get the task stream. + _stream = DesktopFirebaseStorage.taskObservers[_handle]!.stream + as Stream; + late StreamSubscription _subscription; + + // Listen for stream events. + _subscription = _stream.listen((TaskSnapshotPlatform snapshot) async { + if (_snapshot.state != TaskState.canceled) { + _snapshot = snapshot; + } + + // If the stream event is complete, trigger the + // completer to resolve with the snapshot. + if (snapshot.state == TaskState.success) { + _didComplete = true; + _completer?.complete(snapshot); + await _subscription.cancel(); + } + }, onError: (Object e, StackTrace stackTrace) { + if (e is FirebaseException && e.code == 'canceled') { + _snapshot = DesktopTaskSnapshot(storage, TaskState.canceled, { + 'path': path, + 'bytesTransferred': _snapshot.bytesTransferred, + 'totalBytes': _snapshot.totalBytes, + }); + } else { + _snapshot = DesktopTaskSnapshot(storage, TaskState.error, { + 'path': path, + 'bytesTransferred': _snapshot.bytesTransferred, + 'totalBytes': _snapshot.totalBytes, + }); + } + _didComplete = true; + _exception = e; + _stackTrace = stackTrace; + if (_completer != null) { + catchFuturePlatformException(e, stackTrace) + .catchError(_completer!.completeError); + } + }, cancelOnError: true); + } + + Object? _exception; + + late StackTrace _stackTrace; + + bool _didComplete = false; + + Completer? _completer; + + late Stream _stream; + + late Completer _initialTaskCompleter; + + Future Function() _initialTask; + + final int _handle; + + /// The [FirebaseStoragePlatform] used to create the task. + final FirebaseStoragePlatform storage; + + late TaskSnapshotPlatform _snapshot; + + @override + Stream get snapshotEvents { + return DesktopFirebaseStorage.taskObservers[_handle]!.stream + as Stream; + } + + @override + TaskSnapshotPlatform get snapshot => _snapshot; + + @override + Future get onComplete async { + if (_didComplete && _exception == null) { + return Future.value(snapshot); + } else if (_didComplete && _exception != null) { + return catchFuturePlatformException(_exception!, _stackTrace); + } else { + _completer ??= Completer(); + return _completer!.future; + } + } + + @override + Future pause() async { + throw UnimplementedError('pause() is not implemented'); + // try { + // if (!_initialTaskCompleter.isCompleted) { + // await _initialTaskCompleter.future; + // } + + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod('Task#pause', { + // 'handle': _handle, + // }); + + // bool success = data!['status']; + // if (success) { + // _snapshot = DesktopTaskSnapshot(storage, TaskState.paused, + // Map.from(data['snapshot'])); + // } + // return success; + // } catch (e, stack) { + // return catchFuturePlatformException(e, stack); + // } + } + + @override + Future resume() async { + throw UnimplementedError('resume() is not implemented'); + // try { + // if (!_initialTaskCompleter.isCompleted) { + // await _initialTaskCompleter.future; + // } + + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod('Task#resume', { + // 'handle': _handle, + // }); + + // bool success = data!['status']; + // if (success) { + // _snapshot = DesktopTaskSnapshot(storage, TaskState.running, + // Map.from(data['snapshot'])); + // } + // return success; + // } catch (e, stack) { + // return catchFuturePlatformException(e, stack); + } +} + +@override +Future cancel() async { + throw UnimplementedError('cancel() is not implemented'); + // try { + // if (!_initialTaskCompleter.isCompleted) { + // await _initialTaskCompleter.future; + // } + + // Map? data = await DesktopFirebaseStorage.channel + // .invokeMapMethod('Task#cancel', { + // 'handle': _handle, + // }); + + // bool success = data!['status']; + // if (success) { + // _snapshot = DesktopTaskSnapshot(storage, TaskState.canceled, + // Map.from(data['snapshot'])); + // } + // return success; + // } catch (e, stack) { + // return catchFuturePlatformException(e, stack); + // } + // } +} + +/// Implementation for [putFile] tasks. +class DesktopPutFileTask extends DesktopTask { + // ignore: public_member_api_docs + DesktopPutFileTask(int handle, FirebaseStoragePlatform storage, String path, + File file, SettableMetadata? metadata) + : super(handle, storage, path, + _getTask(handle, storage, path, file, metadata)); + + static Future Function() _getTask( + int handle, + FirebaseStoragePlatform storage, + String path, + File file, + SettableMetadata? metadata) { + throw UnimplementedError('_geTask is not implemented'); + // return () => DesktopFirebaseStorage.channel + // .invokeMethod('Task#startPutFile', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'bucket': storage.bucket, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'handle': handle, + // 'path': path, + // 'filePath': file.absolute.path, + // 'metadata': metadata?.asMap(), + // }); + } +} + +/// Implementation for [putString] tasks. +class DesktopPutStringTask extends DesktopTask { + // ignore: public_member_api_docs + DesktopPutStringTask(int handle, FirebaseStoragePlatform storage, String path, + String data, PutStringFormat format, SettableMetadata? metadata) + : super(handle, storage, path, + _getTask(handle, storage, path, data, format, metadata)); + + static Future Function() _getTask( + int handle, + FirebaseStoragePlatform storage, + String path, + String data, + PutStringFormat format, + SettableMetadata? metadata) { + throw UnimplementedError('_geTask is not implemented'); + // return () => DesktopFirebaseStorage.channel + // .invokeMethod('Task#startPutString', { + // 'appName': storage.app.name, + // 'bucket': storage.bucket, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'handle': handle, + // 'path': path, + // 'data': data, + // 'format': format.index, + // 'metadata': metadata?.asMap(), + // }); + } +} + +/// Implementation for [put] tasks. +class DesktopPutTask extends DesktopTask { + // ignore: public_member_api_docs + DesktopPutTask(int handle, FirebaseStoragePlatform storage, String path, + Uint8List data, SettableMetadata? metadata) + : super(handle, storage, path, + _getTask(handle, storage, path, data, metadata)); + + static Future Function() _getTask( + int handle, + FirebaseStoragePlatform storage, + String path, + Uint8List data, + SettableMetadata? metadata) { + throw UnimplementedError('_geTask is not implemented'); + // return () => DesktopFirebaseStorage.channel + // .invokeMethod('Task#startPutData', { + // 'appName': storage.app.name, + // 'bucket': storage.bucket, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'handle': handle, + // 'path': path, + // 'data': data, + // 'metadata': metadata?.asMap(), + // }); + } +} + +/// Implementation for [writeToFile] tasks. +class DesktopDownloadTask extends DesktopTask { + // ignore: public_member_api_docs + DesktopDownloadTask( + int handle, FirebaseStoragePlatform storage, String path, File file) + : super(handle, storage, path, _getTask(handle, storage, path, file)); + + static Future Function() _getTask( + int handle, FirebaseStoragePlatform storage, String path, File file) { + throw UnimplementedError('_geTask is not implemented'); + // return () => DesktopFirebaseStorage.channel + // .invokeMethod('Task#writeToFile', { + // 'appName': storage.app.name, + // 'maxOperationRetryTime': storage.maxOperationRetryTime, + // 'maxUploadRetryTime': storage.maxUploadRetryTime, + // 'maxDownloadRetryTime': storage.maxDownloadRetryTime, + // 'host': storage.emulatorHost, + // 'port': storage.emulatorPort, + // 'bucket': storage.bucket, + // 'handle': handle, + // 'path': path, + // 'filePath': file.path, + // }); + } +} diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart new file mode 100644 index 00000000..f87f857f --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart @@ -0,0 +1,24 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_storage_desktop/src/desktop_reference.dart'; +import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; + +/// Implementation for a [TaskSnapshotPlatform]. +class DesktopTaskSnapshot extends TaskSnapshotPlatform { + // ignore: public_member_api_docs + DesktopTaskSnapshot(this.storage, TaskState state, this._data) + : super(state, _data); + + /// The [FirebaseStoragePlatform] used to create the task. + final FirebaseStoragePlatform storage; + + final Map _data; + + @override + ReferencePlatform get ref { + return DesktopReference(storage, _data['path']); + } +} diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/utils/exceptions.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/utils/exceptions.dart new file mode 100644 index 00000000..55bf2db7 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/utils/exceptions.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +/// Catches a [PlatformException] and returns an [Exception]. +/// +/// If the [Exception] is a [PlatformException], a [FirebaseException] is returned. +Never convertPlatformException( + dynamic exception, + StackTrace stackTrace, +) { + if (exception is! Exception || exception is! PlatformException) { + Error.throwWithStackTrace(exception, stackTrace); + } + + Error.throwWithStackTrace( + platformExceptionToFirebaseException(exception, stackTrace), + stackTrace, + ); +} + +/// Catches a [PlatformException] and converts it into a [FirebaseException] if +/// it was intentionally caught on the native platform. +Future catchFuturePlatformException( + Object exception, + StackTrace stackTrace, +) { + if (exception is! Exception || exception is! PlatformException) { + return Future.error(exception, stackTrace); + } + + return Future.error( + platformExceptionToFirebaseException(exception, stackTrace), + stackTrace, + ); +} + +/// Converts a [PlatformException] into a [FirebaseException]. +/// +/// A [PlatformException] can only be converted to a [FirebaseException] if the +/// `details` of the exception exist. Firebase returns specific codes and messages +/// which can be converted into user friendly exceptions. +FirebaseException platformExceptionToFirebaseException( + PlatformException platformException, + StackTrace stackTrace, +) { + Map? details = platformException.details != null + ? Map.from(platformException.details) + : null; + + String code = 'unknown'; + String message = platformException.message ?? ''; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + // TODO(ehesp): Add stack trace support when it lands + return FirebaseException( + plugin: 'firebase_storage', code: code, message: message); +} diff --git a/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml b/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml index 8d2deef4..3efc72ea 100644 --- a/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage_desktop/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_storage_desktop description: A new Flutter package project. -version: 0.0.1 +version: 0.1.0-dev.0 homepage: environment: @@ -12,6 +12,8 @@ dependencies: sdk: flutter firebase_storage_platform_interface: ^4.1.4 firebase_core_dart: ^0.1.1 + firebase_storage_dart: + path: ../firebase_storage_dart dev_dependencies: flutter_test: From e776fb5dc041332782e5e8fcf26b8440d7af73c1 Mon Sep 17 00:00:00 2001 From: Ahmed Elshorbagy Date: Tue, 26 Apr 2022 15:29:05 +0200 Subject: [PATCH 3/5] api edit --- .vscode/settings.json | 62 ++++++++++++++++- .../lib/src/api/api.dart | 68 +++++++++++++++++++ .../lib/src/api/errors.dart | 30 ++++++++ .../lib/src/firebase_storage_exception.dart | 18 +++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage_exception.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 8beab843..9d6d8682 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,7 +60,36 @@ "packages/firebase_functions/firebase_functions_desktop/LICENSE": true, "packages/firebase_functions/firebase_functions_desktop/pubspec.lock": true, "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml": true, - "packages/firebase_functions/firebase_functions_desktop/README.md": true + "packages/firebase_functions/firebase_functions_desktop/README.md": true, + "packages/firebase_storage/firebase_storage_dart/pubspec.lock": true, + "packages/firebase_storage/firebase_storage_dart/test": true, + "packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart": true, + "packages/firebase_storage/firebase_storage_dart/.gitignore": true, + "packages/firebase_storage/firebase_storage_dart/.packages": true, + "packages/firebase_storage/firebase_storage_dart/analysis_options.yaml": true, + "packages/firebase_storage/firebase_storage_dart/CHANGELOG.md": true, + "packages/firebase_storage/firebase_storage_dart/LICENSE": true, + "packages/firebase_storage/firebase_storage_dart/pubspec.yaml": true, + "packages/firebase_storage/firebase_storage_dart/README.md": true, + "packages/firebase_storage/firebase_storage_desktop/firebase_storage_desktop.iml": true, + "packages/firebase_storage/firebase_storage_desktop/test": true, + "packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart": true, + "packages/firebase_storage/firebase_storage_desktop/.gitignore": true, + "packages/firebase_storage/firebase_storage_desktop/.metadata": true, + "packages/firebase_storage/firebase_storage_desktop/.packages": true, + "packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml": true, + "packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md": true, + "packages/firebase_storage/firebase_storage_desktop/LICENSE": true, + "packages/firebase_storage/firebase_storage_desktop/pubspec.lock": true, + "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml": true, + "packages/firebase_storage/firebase_storage_desktop/README.md": true, + "LICENSE": true, + "packages/firebase_storage/firebase_storage_desktop/.idea": true, + "packages/firebase_storage/firebase_storage_desktop/.dart_tool": true, + "packages/firebase_auth/firebase_auth_dart/pubspec.yaml": true, + "packages/firebase_auth/firebase_auth_dart/README.md": true, + "packages/firebase_storage/firebase_storage_dart/example": true, + "packages/firebase_storage/firebase_storage_dart/.dart_tool": true }, "hide-files.files": [ "all_lint_rules.yaml", @@ -115,6 +144,35 @@ "packages/firebase_functions/firebase_functions_desktop/LICENSE", "packages/firebase_functions/firebase_functions_desktop/pubspec.lock", "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml", - "packages/firebase_functions/firebase_functions_desktop/README.md" + "packages/firebase_functions/firebase_functions_desktop/README.md", + "packages/firebase_storage/firebase_storage_dart/pubspec.lock", + "packages/firebase_storage/firebase_storage_dart/test", + "packages/firebase_storage/firebase_storage_dart/test/firebase_storage_dart_test.dart", + "packages/firebase_storage/firebase_storage_dart/.gitignore", + "packages/firebase_storage/firebase_storage_dart/.packages", + "packages/firebase_storage/firebase_storage_dart/analysis_options.yaml", + "packages/firebase_storage/firebase_storage_dart/CHANGELOG.md", + "packages/firebase_storage/firebase_storage_dart/LICENSE", + "packages/firebase_storage/firebase_storage_dart/pubspec.yaml", + "packages/firebase_storage/firebase_storage_dart/README.md", + "packages/firebase_storage/firebase_storage_desktop/firebase_storage_desktop.iml", + "packages/firebase_storage/firebase_storage_desktop/test", + "packages/firebase_storage/firebase_storage_desktop/test/firebase_storage_desktop_test.dart", + "packages/firebase_storage/firebase_storage_desktop/.gitignore", + "packages/firebase_storage/firebase_storage_desktop/.metadata", + "packages/firebase_storage/firebase_storage_desktop/.packages", + "packages/firebase_storage/firebase_storage_desktop/analysis_options.yaml", + "packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md", + "packages/firebase_storage/firebase_storage_desktop/LICENSE", + "packages/firebase_storage/firebase_storage_desktop/pubspec.lock", + "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml", + "packages/firebase_storage/firebase_storage_desktop/README.md", + "LICENSE", + "packages/firebase_storage/firebase_storage_desktop/.idea", + "packages/firebase_storage/firebase_storage_desktop/.dart_tool", + "packages/firebase_auth/firebase_auth_dart/pubspec.yaml", + "packages/firebase_auth/firebase_auth_dart/README.md", + "packages/firebase_storage/firebase_storage_dart/example", + "packages/firebase_storage/firebase_storage_dart/.dart_tool" ] } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart index 32438149..b78fb603 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart @@ -6,12 +6,60 @@ library api; +import 'package:firebase_storage_dart/src/api/errors.dart'; +import 'package:firebase_storage_dart/src/firebase_storage_exception.dart'; +import 'package:firebaseapis/firebasestorage/v1beta.dart'; +import 'package:firebaseapis/src/user_agent.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; import 'package:googleapis_auth/auth_io.dart' if (dart.library.html) 'package:googleapis_auth/auth_browser.dart'; part 'emulator.dart'; +/// All API classes calling to IDP API must extend this template. +abstract class APIDelegate { + /// Construct a new [APIDelegate]. + const APIDelegate(this.api); + + /// The [API] instance containing required configurations to make the requests. + final API api; + + /// Convert [DetailedApiRequestError] thrown by idp to [FirebaseStorageException]. + FirebaseStorageException makeAuthException(DetailedApiRequestError apiError) { + try { + final json = apiError.jsonResponse; + var serverErrorCode = apiError.message ?? ''; + + String? customMessage; + + if (json != null) { + if (json['error'] != null && + // ignore: avoid_dynamic_calls + json['error']['status'] != null) { + // ignore: avoid_dynamic_calls + serverErrorCode = apiError.jsonResponse!['error']['status']; + customMessage = apiError.message; + } + + // Solves a problem with incosistent error codes coming from the server. + if (serverErrorCode.contains(' ')) { + serverErrorCode = serverErrorCode.split(' ').first; + } + } + + final storageErrorCode = StorageErrorCode.values + .firstWhere((code) => code.name == serverErrorCode); + + return FirebaseStorageException( + storageErrorCode, + message: customMessage, + ); + } catch (e) { + rethrow; + } + } +} + /// Configurations necessary for making all idp requests. @protected class APIConfig { @@ -72,4 +120,24 @@ class API { set client(http.Client client) { _client = client; } + + /// Updates the [languageCode] for this instance. + void setLanguageCode(String? languageCode) { + _languageCode = languageCode; + requestHeaders.addAll({'X-Firebase-Locale': languageCode ?? ''}); + } + + /// Identity platform [ProjectsResource] initialized with this instance [APIConfig]. + @internal + ProjectsResource get firebaseStorage { + if (apiConfig.emulator != null) { + return FirebasestorageApi( + _client!, + rootUrl: apiConfig.emulator!.rootUrl, + ).projects; + } + return FirebasestorageApi( + _client!, + ).projects; + } } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart index d1b6f4b7..be055f7e 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart @@ -34,6 +34,36 @@ extension ToString on StorageErrorCode { } } +extension GetMessage on StorageErrorCode { + String get message { + switch (this) { + case StorageErrorCode.OBJECT_NOT_FOUND: + return "No object exists at the desired reference."; + case StorageErrorCode.BUCKET_NOT_FOUND: + return "No bucket is configured for Firebase Storage."; + case StorageErrorCode.PROJECT_NOT_FOUND: + return "No project is configured for Firebase Storage."; + case StorageErrorCode.QUOTA_EXCEEDED: + return "Quota on your Firebase Storage bucket has been exceeded."; + case StorageErrorCode.UNAUTHENTICATED: + return "User is unauthenticated. Authenticate and try again."; + case StorageErrorCode.UNAUTHORIZED: + return "User is not authorized to perform the desired action."; + case StorageErrorCode.RETRY_LIMIT_EXCEEDED: + return "The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded."; + case StorageErrorCode.INVALID_CHECKSUM: + return "File on the client does not match the checksum of the file received by the server."; + case StorageErrorCode.CANCELED: + return "User cancelled the operation."; + case StorageErrorCode.UNKNOWN: + default: + { + return "An unknown error occurred"; + } + } + } +} + class StorageError { late String _baseMessage; String message; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage_exception.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage_exception.dart new file mode 100644 index 00000000..ca8f05b9 --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage_exception.dart @@ -0,0 +1,18 @@ +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. + +import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebase_storage_dart/src/api/errors.dart'; + +/// Wrap the errors from the Identity Platform REST API, usually of type `DetailedApiRequestError` +/// in a in a Firebase-friendly format to users. +class FirebaseStorageException extends FirebaseException implements Exception { + // ignore: public_member_api_docs + FirebaseStorageException(StorageErrorCode errorCode, {String? message}) + : super( + plugin: 'firebase_auth', + code: errorCode.asString, + message: message ?? errorCode.message, + ); +} From 9b059a8812289e19f02b3a468850f0fee3f67714 Mon Sep 17 00:00:00 2001 From: Ahmed Elshorbagy Date: Wed, 27 Apr 2022 00:31:55 +0200 Subject: [PATCH 4/5] Finished FireStorage class implementations --- .../lib/firebase_storage_dart.dart | 1 + .../lib/src/api/api.dart | 5 ++ .../lib/src/api/emulator.dart | 39 +++++++++++ .../lib/src/firebase_storage.dart | 70 ++++++++++++------- .../lib/src/implementations/constants.dart | 14 ---- .../lib/src/desktop_firebase_storage.dart | 2 +- .../lib/src/desktop_reference.dart | 2 - 7 files changed, 91 insertions(+), 42 deletions(-) diff --git a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart index e2301986..46a2477e 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart @@ -8,6 +8,7 @@ import 'dart:convert' show utf8, base64; import 'dart:io' show File; import 'dart:typed_data' show Uint8List; import 'package:firebase_storage_dart/src/api/api.dart'; +import 'package:firebase_storage_dart/src/implementations/constants.dart'; import 'package:firebase_storage_dart/src/implementations/location.dart'; import 'package:firebase_storage_dart/src/implementations/paths.dart' as paths; import 'package:http/http.dart' as http; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart index b78fb603..e2ae7c0e 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart @@ -6,6 +6,7 @@ library api; +import 'dart:convert'; import 'package:firebase_storage_dart/src/api/errors.dart'; import 'package:firebase_storage_dart/src/firebase_storage_exception.dart'; import 'package:firebaseapis/firebasestorage/v1beta.dart'; @@ -140,4 +141,8 @@ class API { _client!, ).projects; } + + /// A delegate getter used to perform all requests + /// for Identity platform profile related operations. + StorageEmulator get emulator => StorageEmulator(apiConfig); } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart index 80d65160..ca2eaf86 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/emulator.dart @@ -21,3 +21,42 @@ class EmulatorConfig { /// The root URL used to make requests to the locally running emulator. String get rootUrl => 'http://$host:$port/www.googleapis.com/'; } + +/// Call the local auth emulator. +/// If found, set the instance API to use it for all future requests. +class StorageEmulator { + // ignore: public_member_api_docs + StorageEmulator(this._config); + + final APIConfig _config; + + /// Try to connect to the local runnning emulator by getting emulator-specific + /// configuration for the specified project. If the connection was successful, + /// set local emulator in API configurations to be used for all future requests. + Future> useEmulator(String host, int port) async { + // 1. Get the emulator project configs, it must be initialized first. + // http://localhost:9099/emulator/v1/projects/{project-id}/config + final localEmulator = Uri( + scheme: 'http', + host: host, + port: port, + path: '/emulator/v1/projects/${_config.projectId}/config', + ); + + http.Response response; + + try { + response = await http.get(localEmulator); + } catch (e) { + return {}; + } + + final Map emulatorProjectConfig = + json.decode(response.body); + + // set the the emulator config for this instance. + _config.setEmulator(host, port); + + return emulatorProjectConfig; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart index 32ecb4ab..416a5467 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart @@ -36,20 +36,13 @@ class FirebaseStorage { /// The storage bucket of this instance. String bucket; - /// The maximum time to retry operations other than uploads or downloads in milliseconds. - Duration get maxOperationRetryTime { - throw UnimplementedError(); - } - - /// The maximum time to retry uploads in milliseconds. - Duration get maxUploadRetryTime { - throw UnimplementedError(); - } + // Same default as the method channel implementation + int _maxDownloadRetryTime = const Duration(minutes: 10).inMilliseconds; - /// The maximum time to retry downloads in milliseconds. - Duration get maxDownloadRetryTime { - throw UnimplementedError(); - } + // Same default as the method channel implementation + int _maxOperationRetryTime = const Duration(minutes: 2).inMilliseconds; + // Same default as the method channel implementation + int _maxUploadRetryTime = const Duration(minutes: 10).inMilliseconds; static final Map _cachedInstances = {}; @@ -101,6 +94,16 @@ class FirebaseStorage { return newInstance; } + /// The Storage emulator host this instance is configured to use. This + /// was required since iOS does not persist these settings on instances and + /// they need to be set every time when getting a `FIRStorage` instance. + String? emulatorHost; + + /// The Storage emulator port this instance is configured to use. This + /// was required since iOS does not persist these settings on instances and + /// they need to be set every time when getting a `FIRStorage` instance. + int? emulatorPort; + /// Returns a new [Reference]. /// /// If the [path] is empty, the reference will point to the root of the @@ -140,22 +143,37 @@ class FirebaseStorage { .ref(path); } + /// The maximum time to retry operations other than uploads or downloads in milliseconds. + int get maxOperationRetryTime { + return _maxOperationRetryTime; + } + + /// The maximum time to retry uploads in milliseconds. + int get maxUploadRetryTime { + return _maxUploadRetryTime; + } + + /// The maximum time to retry downloads in milliseconds. + int get maxDownloadRetryTime { + return _maxDownloadRetryTime; + } + /// Sets the new maximum operation retry time. - void setMaxOperationRetryTime(Duration time) { + void setMaxOperationRetryTime(int time) { assert(!time.isNegative); - throw UnimplementedError(); + _maxOperationRetryTime = time; } /// Sets the new maximum upload retry time. - void setMaxUploadRetryTime(Duration time) { + void setMaxUploadRetryTime(int time) { assert(!time.isNegative); - throw UnimplementedError(); + _maxUploadRetryTime = time; } /// Sets the new maximum download retry time. - void setMaxDownloadRetryTime(Duration time) { + void setMaxDownloadRetryTime(int time) { assert(!time.isNegative); - throw UnimplementedError(); + _maxDownloadRetryTime = time; } /// Changes this instance to point to a Storage emulator running locally. @@ -172,8 +190,7 @@ class FirebaseStorage { assert(host.isNotEmpty); assert(!port.isNegative); - String mappedHost = host; - throw UnimplementedError(); + await useStorageEmulator(host: host, port: port); } /// Changes this instance to point to a Storage emulator running locally. @@ -183,13 +200,16 @@ class FirebaseStorage { /// /// Note: Must be called immediately, prior to accessing storage methods. /// Do not use with production credentials as emulator traffic is not encrypted. - Future useStorageEmulator(String host, int port) async { + Future useStorageEmulator( + {String host = 'localhost', int port = 9199}) async { assert(host.isNotEmpty); assert(!port.isNegative); - String mappedHost = host; - - throw UnimplementedError(); + try { + return await _api.emulator.useEmulator(host, port); + } catch (e) { + rethrow; + } } @override diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart index bc6d39ed..978324c3 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/constants.dart @@ -8,20 +8,6 @@ const DEFAULT_HOST = 'firebasestorage.googleapis.com'; /// const CONFIG_STORAGE_BUCKET_KEY = 'storageBucket'; -/// -///2 minutes -/// -///The timeout for all operations except upload. -/// -const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000; - -/// -///10 minutes -/// -///The timeout for upload. -/// -const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000; - /// ///This is the value of Number.MIN_SAFE_INTEGER, which is not well supported ///enough for us to use it directly. diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart index a9d50a00..81f55d6c 100644 --- a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_firebase_storage.dart @@ -155,7 +155,7 @@ class DesktopFirebaseStorage extends FirebaseStoragePlatform { emulatorHost = host; emulatorPort = port; try { - await _delegate!.useStorageEmulator(host, port); + await _delegate!.useStorageEmulator(host: host, port: port); // await DesktopFirebaseStorage.channel // .invokeMethod('Storage#useEmulator', { // 'appName': app.name, diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart index 37d119f3..b73deff3 100644 --- a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_reference.dart @@ -8,9 +8,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:firebase_storage_desktop/src/desktop_firebase_storage.dart'; -import 'package:firebase_storage_desktop/src/desktop_list_result.dart'; import 'package:firebase_storage_desktop/src/desktop_task.dart'; -import 'package:firebase_storage_desktop/src/utils/exceptions.dart'; import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; /// An implementation of [ReferencePlatform] that uses [Desktop] to From 3107687a703c845281cfd923be30aecbce1b6532 Mon Sep 17 00:00:00 2001 From: Ahmed Elshorbagy Date: Thu, 5 May 2022 19:48:07 +0200 Subject: [PATCH 5/5] Build reference api architecture --- .vscode/settings.json | 146 ++++-- .../lib/firebase_storage_dart.dart | 5 +- .../lib/src/api/api.dart | 57 ++- .../lib/src/api/errors.dart | 36 ++ .../lib/src/api/ref_api.dart | 415 ++++++++++++++++++ .../lib/src/api/reference_api.dart | 35 ++ .../lib/src/firebase_storage.dart | 10 + .../lib/src/implementations/urls.dart | 10 +- .../lib/src/list_result.dart | 27 +- .../lib/src/reference.dart | 100 +++-- .../firebase_storage_dart/lib/src/task.dart | 68 +-- .../lib/src/task_snapshot.dart | 19 +- .../firebase_storage_dart/lib/src/utils.dart | 10 +- .../lib/src/desktop_task_snapshot.dart | 2 +- 14 files changed, 807 insertions(+), 133 deletions(-) create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/api/ref_api.dart create mode 100644 packages/firebase_storage/firebase_storage_dart/lib/src/api/reference_api.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d6d8682..ef4b8cb0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,45 +8,28 @@ "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, - "all_lint_rules.yaml": true, - "tests": true, - ".gitignore": true, - "analysis_options.yaml": true, - "melos.yaml": true, - "README.md": true, - "packages/firebase_auth/firebase_auth_desktop/.dart_tool": true, - "packages/firebase_auth/firebase_auth_desktop/pubspec.lock": true, - "packages/firebase_auth/firebase_auth_desktop/linux": true, "packages/firebase_auth/firebase_auth_desktop/macos": true, - "packages/firebase_auth/firebase_auth_desktop/test": true, "packages/firebase_auth/firebase_auth_desktop/windows": true, "packages/firebase_auth/firebase_auth_desktop/.gitignore": true, "packages/firebase_auth/firebase_auth_desktop/.metadata": true, "packages/firebase_auth/firebase_auth_desktop/.packages": true, "packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md": true, "packages/firebase_auth/firebase_auth_desktop/LICENSE": true, - "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml": true, - "packages/firebase_auth/firebase_auth_desktop/README.md": true, ".vscode": true, ".dart_tool/melos_tool": true, ".github": true, - "packages/firebase_auth/firebase_auth_dart/.dart_tool": true, - "packages/firebase_auth/firebase_auth_dart/example": true, "packages/firebase_auth/firebase_auth_dart/pubspec.lock": true, "packages/firebase_auth/firebase_auth_dart/test": true, "packages/firebase_auth/firebase_auth_dart/.gitignore": true, "packages/firebase_auth/firebase_auth_dart/.packages": true, "packages/firebase_auth/firebase_auth_dart/CHANGELOG.md": true, "packages/firebase_auth/firebase_auth_dart/LICENSE": true, - "packages/firebase_functions/firebase_functions_dart/test": true, "packages/firebase_functions/firebase_functions_dart/.dart_tool": true, "packages/firebase_functions/firebase_functions_dart/.gitignore": true, "packages/firebase_functions/firebase_functions_dart/.packages": true, "packages/firebase_functions/firebase_functions_dart/CHANGELOG.md": true, "packages/firebase_functions/firebase_functions_dart/LICENSE": true, "packages/firebase_functions/firebase_functions_dart/pubspec.lock": true, - "packages/firebase_functions/firebase_functions_dart/pubspec.yaml": true, - "packages/firebase_functions/firebase_functions_dart/README.md": true, "packages/firebase_functions/firebase_functions_desktop/macos": true, "packages/firebase_functions/firebase_functions_desktop/.dart_tool": true, "packages/firebase_functions/firebase_functions_desktop/example": true, @@ -59,7 +42,6 @@ "packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md": true, "packages/firebase_functions/firebase_functions_desktop/LICENSE": true, "packages/firebase_functions/firebase_functions_desktop/pubspec.lock": true, - "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml": true, "packages/firebase_functions/firebase_functions_desktop/README.md": true, "packages/firebase_storage/firebase_storage_dart/pubspec.lock": true, "packages/firebase_storage/firebase_storage_dart/test": true, @@ -69,7 +51,6 @@ "packages/firebase_storage/firebase_storage_dart/analysis_options.yaml": true, "packages/firebase_storage/firebase_storage_dart/CHANGELOG.md": true, "packages/firebase_storage/firebase_storage_dart/LICENSE": true, - "packages/firebase_storage/firebase_storage_dart/pubspec.yaml": true, "packages/firebase_storage/firebase_storage_dart/README.md": true, "packages/firebase_storage/firebase_storage_desktop/firebase_storage_desktop.iml": true, "packages/firebase_storage/firebase_storage_desktop/test": true, @@ -81,56 +62,83 @@ "packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md": true, "packages/firebase_storage/firebase_storage_desktop/LICENSE": true, "packages/firebase_storage/firebase_storage_desktop/pubspec.lock": true, - "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml": true, "packages/firebase_storage/firebase_storage_desktop/README.md": true, "LICENSE": true, - "packages/firebase_storage/firebase_storage_desktop/.idea": true, "packages/firebase_storage/firebase_storage_desktop/.dart_tool": true, - "packages/firebase_auth/firebase_auth_dart/pubspec.yaml": true, - "packages/firebase_auth/firebase_auth_dart/README.md": true, "packages/firebase_storage/firebase_storage_dart/example": true, - "packages/firebase_storage/firebase_storage_dart/.dart_tool": true + "packages/firebase_storage/firebase_storage_dart/.dart_tool": true, + "packages/firebase_functions/firebase_functions_dart/README.md": true, + ".dart_tool": true, + "packages/firebase_auth/firebase_auth_dart/example": true, + "packages/firebase_auth/firebase_auth_dart/.dart_tool": true, + "packages/firebase_auth/firebase_auth_dart/README.md": true, + "packages/firebase_auth/firebase_auth_desktop/linux": true, + "packages/firebase_auth/firebase_auth_desktop/.dart_tool": true, + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/package_config_subset": true, + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/package_config.json": true, + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/version": true, + "packages/firebase_auth/firebase_auth_desktop/example": true, + "packages/firebase_auth/firebase_auth_desktop/pubspec.lock": true, + "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml": true, + "packages/firebase_auth/firebase_auth_desktop/README.md": true, + "README.md": true, + "tests": true, + ".gitignore": true, + "all_lint_rules.yaml": true, + "analysis_options.yaml": true, + "melos.yaml": true, + "packages/firebase_functions/firebase_functions_dart/pubspec.yaml": true, + "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml": true, + "packages/firebase_storage/firebase_storage_desktop/.idea": true, + "packages/firebase_storage/firebase_storage_dart/pubspec.yaml": true, + "packages/firebase_core/firebase_core_desktop/.dart_tool": true, + "packages/firebase_core/firebase_core_desktop/example": true, + "packages/firebase_core/firebase_core_desktop/linux": true, + "packages/firebase_core/firebase_core_desktop/macos": true, + "packages/firebase_core/firebase_core_desktop/test": true, + "packages/firebase_core/firebase_core_desktop/windows": true, + "packages/firebase_core/firebase_core_desktop/.gitignore": true, + "packages/firebase_core/firebase_core_desktop/.packages": true, + "packages/firebase_core/firebase_core_desktop/CHANGELOG.md": true, + "packages/firebase_core/firebase_core_desktop/LICENSE": true, + "packages/firebase_core/firebase_core_desktop/pubspec.lock": true, + "packages/firebase_core/firebase_core_desktop/pubspec.yaml": true, + "packages/firebase_core/firebase_core_desktop/README.md": true, + "packages/firebase_auth/firebase_auth_dart/pubspec.yaml": true, + "packages/firebase_core/firebase_core_dart/.dart_tool": true, + "packages/firebase_core/firebase_core_dart/.packages": true, + "packages/firebase_core/firebase_core_dart/test": true, + "packages/firebase_core/firebase_core_dart/.gitignore": true, + "packages/firebase_core/firebase_core_dart/CHANGELOG.md": true, + "packages/firebase_core/firebase_core_dart/LICENSE": true, + "packages/firebase_core/firebase_core_dart/pubspec.lock": true, + "packages/firebase_core/firebase_core_dart/pubspec.yaml": true, + "packages/firebase_core/firebase_core_dart/README.md": true, + "packages/firebase_functions/firebase_functions_dart/test": true }, "hide-files.files": [ - "all_lint_rules.yaml", - "tests", - ".gitignore", - "analysis_options.yaml", - "melos.yaml", - "README.md", - "packages/firebase_auth/firebase_auth_desktop/.dart_tool", - "packages/firebase_auth/firebase_auth_desktop/pubspec.lock", - "packages/firebase_auth/firebase_auth_desktop/linux", "packages/firebase_auth/firebase_auth_desktop/macos", - "packages/firebase_auth/firebase_auth_desktop/test", "packages/firebase_auth/firebase_auth_desktop/windows", "packages/firebase_auth/firebase_auth_desktop/.gitignore", "packages/firebase_auth/firebase_auth_desktop/.metadata", "packages/firebase_auth/firebase_auth_desktop/.packages", "packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md", "packages/firebase_auth/firebase_auth_desktop/LICENSE", - "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml", - "packages/firebase_auth/firebase_auth_desktop/README.md", ".vscode", ".dart_tool/melos_tool", ".github", - "packages/firebase_auth/firebase_auth_dart/.dart_tool", - "packages/firebase_auth/firebase_auth_dart/example", "packages/firebase_auth/firebase_auth_dart/pubspec.lock", "packages/firebase_auth/firebase_auth_dart/test", "packages/firebase_auth/firebase_auth_dart/.gitignore", "packages/firebase_auth/firebase_auth_dart/.packages", "packages/firebase_auth/firebase_auth_dart/CHANGELOG.md", "packages/firebase_auth/firebase_auth_dart/LICENSE", - "packages/firebase_functions/firebase_functions_dart/test", "packages/firebase_functions/firebase_functions_dart/.dart_tool", "packages/firebase_functions/firebase_functions_dart/.gitignore", "packages/firebase_functions/firebase_functions_dart/.packages", "packages/firebase_functions/firebase_functions_dart/CHANGELOG.md", "packages/firebase_functions/firebase_functions_dart/LICENSE", "packages/firebase_functions/firebase_functions_dart/pubspec.lock", - "packages/firebase_functions/firebase_functions_dart/pubspec.yaml", - "packages/firebase_functions/firebase_functions_dart/README.md", "packages/firebase_functions/firebase_functions_desktop/macos", "packages/firebase_functions/firebase_functions_desktop/.dart_tool", "packages/firebase_functions/firebase_functions_desktop/example", @@ -143,7 +151,6 @@ "packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md", "packages/firebase_functions/firebase_functions_desktop/LICENSE", "packages/firebase_functions/firebase_functions_desktop/pubspec.lock", - "packages/firebase_functions/firebase_functions_desktop/pubspec.yaml", "packages/firebase_functions/firebase_functions_desktop/README.md", "packages/firebase_storage/firebase_storage_dart/pubspec.lock", "packages/firebase_storage/firebase_storage_dart/test", @@ -153,7 +160,6 @@ "packages/firebase_storage/firebase_storage_dart/analysis_options.yaml", "packages/firebase_storage/firebase_storage_dart/CHANGELOG.md", "packages/firebase_storage/firebase_storage_dart/LICENSE", - "packages/firebase_storage/firebase_storage_dart/pubspec.yaml", "packages/firebase_storage/firebase_storage_dart/README.md", "packages/firebase_storage/firebase_storage_desktop/firebase_storage_desktop.iml", "packages/firebase_storage/firebase_storage_desktop/test", @@ -165,14 +171,58 @@ "packages/firebase_storage/firebase_storage_desktop/CHANGELOG.md", "packages/firebase_storage/firebase_storage_desktop/LICENSE", "packages/firebase_storage/firebase_storage_desktop/pubspec.lock", - "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml", "packages/firebase_storage/firebase_storage_desktop/README.md", "LICENSE", - "packages/firebase_storage/firebase_storage_desktop/.idea", "packages/firebase_storage/firebase_storage_desktop/.dart_tool", - "packages/firebase_auth/firebase_auth_dart/pubspec.yaml", - "packages/firebase_auth/firebase_auth_dart/README.md", "packages/firebase_storage/firebase_storage_dart/example", - "packages/firebase_storage/firebase_storage_dart/.dart_tool" + "packages/firebase_storage/firebase_storage_dart/.dart_tool", + "packages/firebase_functions/firebase_functions_dart/README.md", + ".dart_tool", + "packages/firebase_auth/firebase_auth_dart/example", + "packages/firebase_auth/firebase_auth_dart/.dart_tool", + "packages/firebase_auth/firebase_auth_dart/README.md", + "packages/firebase_auth/firebase_auth_desktop/linux", + "packages/firebase_auth/firebase_auth_desktop/.dart_tool", + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/package_config_subset", + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/package_config.json", + "packages/firebase_auth/firebase_auth_desktop/.dart_tool/version", + "packages/firebase_auth/firebase_auth_desktop/example", + "packages/firebase_auth/firebase_auth_desktop/pubspec.lock", + "packages/firebase_auth/firebase_auth_desktop/pubspec.yaml", + "packages/firebase_auth/firebase_auth_desktop/README.md", + "README.md", + "tests", + ".gitignore", + "all_lint_rules.yaml", + "analysis_options.yaml", + "melos.yaml", + "packages/firebase_functions/firebase_functions_dart/pubspec.yaml", + "packages/firebase_storage/firebase_storage_desktop/pubspec.yaml", + "packages/firebase_storage/firebase_storage_desktop/.idea", + "packages/firebase_storage/firebase_storage_dart/pubspec.yaml", + "packages/firebase_core/firebase_core_desktop/.dart_tool", + "packages/firebase_core/firebase_core_desktop/example", + "packages/firebase_core/firebase_core_desktop/linux", + "packages/firebase_core/firebase_core_desktop/macos", + "packages/firebase_core/firebase_core_desktop/test", + "packages/firebase_core/firebase_core_desktop/windows", + "packages/firebase_core/firebase_core_desktop/.gitignore", + "packages/firebase_core/firebase_core_desktop/.packages", + "packages/firebase_core/firebase_core_desktop/CHANGELOG.md", + "packages/firebase_core/firebase_core_desktop/LICENSE", + "packages/firebase_core/firebase_core_desktop/pubspec.lock", + "packages/firebase_core/firebase_core_desktop/pubspec.yaml", + "packages/firebase_core/firebase_core_desktop/README.md", + "packages/firebase_auth/firebase_auth_dart/pubspec.yaml", + "packages/firebase_core/firebase_core_dart/.dart_tool", + "packages/firebase_core/firebase_core_dart/.packages", + "packages/firebase_core/firebase_core_dart/test", + "packages/firebase_core/firebase_core_dart/.gitignore", + "packages/firebase_core/firebase_core_dart/CHANGELOG.md", + "packages/firebase_core/firebase_core_dart/LICENSE", + "packages/firebase_core/firebase_core_dart/pubspec.lock", + "packages/firebase_core/firebase_core_dart/pubspec.yaml", + "packages/firebase_core/firebase_core_dart/README.md", + "packages/firebase_functions/firebase_functions_dart/test" ] } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart index 46a2477e..20de88b7 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/firebase_storage_dart.dart @@ -4,13 +4,14 @@ library firebase_storage_dart; import 'dart:async'; -import 'dart:convert' show utf8, base64; +import 'dart:convert' show base64, jsonDecode, utf8; import 'dart:io' show File; import 'dart:typed_data' show Uint8List; +import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_storage_dart/src/api/api.dart'; -import 'package:firebase_storage_dart/src/implementations/constants.dart'; import 'package:firebase_storage_dart/src/implementations/location.dart'; import 'package:firebase_storage_dart/src/implementations/paths.dart' as paths; +import 'package:firebase_storage_dart/src/implementations/urls.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart index e2ae7c0e..87f73603 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/api.dart @@ -6,16 +6,30 @@ library api; +import 'dart:async'; import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:firebase_auth_dart/firebase_auth_dart.dart'; +import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebase_storage_dart/firebase_storage_dart.dart'; import 'package:firebase_storage_dart/src/api/errors.dart'; +import 'package:firebase_storage_dart/src/data_models/list_options.dart'; +import 'package:firebase_storage_dart/src/data_models/put_string_format.dart'; +import 'package:firebase_storage_dart/src/data_models/settable_metadata.dart'; import 'package:firebase_storage_dart/src/firebase_storage_exception.dart'; +import 'package:firebase_storage_dart/src/implementations/location.dart'; +import 'package:firebase_storage_dart/src/implementations/urls.dart'; import 'package:firebaseapis/firebasestorage/v1beta.dart'; import 'package:firebaseapis/src/user_agent.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; import 'package:googleapis_auth/auth_io.dart' if (dart.library.html) 'package:googleapis_auth/auth_browser.dart'; +import 'package:_discoveryapis_commons/_discoveryapis_commons.dart' as commons; part 'emulator.dart'; +part 'reference_api.dart'; +part 'ref_api.dart'; /// All API classes calling to IDP API must extend this template. abstract class APIDelegate { @@ -26,7 +40,8 @@ abstract class APIDelegate { final API api; /// Convert [DetailedApiRequestError] thrown by idp to [FirebaseStorageException]. - FirebaseStorageException makeAuthException(DetailedApiRequestError apiError) { + FirebaseStorageException makeStorageException( + DetailedApiRequestError apiError) { try { final json = apiError.jsonResponse; var serverErrorCode = apiError.message ?? ''; @@ -59,6 +74,35 @@ abstract class APIDelegate { rethrow; } } + + FirebaseStorageException handleResponseErrorCodes( + int errorStatus, String ErrorText, StorageErrorCode error) { + StorageErrorCode storageErrorCode; + if (errorStatus == 401) { + if ( + // This exact message string is the only consistent part of the + // server's error response that identifies it as an App Check error. + ErrorText.contains('Firebase App Check token is invalid')) { + storageErrorCode = StorageErrorCode.UNAUTHORIZED_APP; + } else { + storageErrorCode = StorageErrorCode.UNAUTHENTICATED; + } + } else { + if (errorStatus == 402) { + storageErrorCode = StorageErrorCode.QUOTA_EXCEEDED; + } else { + if (errorStatus == 403) { + storageErrorCode = StorageErrorCode.UNAUTHORIZED; + } else { + storageErrorCode = error; + } + } + } + return FirebaseStorageException( + storageErrorCode, + message: ErrorText, + ); + } } /// Configurations necessary for making all idp requests. @@ -145,4 +189,15 @@ class API { /// A delegate getter used to perform all requests /// for Identity platform profile related operations. StorageEmulator get emulator => StorageEmulator(apiConfig); + + commons.ApiRequester get _requester { + if (apiConfig.emulator != null) { + return commons.ApiRequester( + _client!, apiConfig.emulator!.rootUrl, '', requestHeaders); + } + return commons.ApiRequester(_client!, + 'https://firebasestorage.googleapis.com/', '', requestHeaders); + } + + RefApi get refernceApi => RefApi(this); } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart index be055f7e..d55dd7e5 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/errors.dart @@ -118,3 +118,39 @@ StorageError invalidUrl(String url) { return StorageError( StorageErrorCode.INVALID_URL, "Invalid URL '" + url + "'."); } + +StorageError invalidArgument(String message) { + return StorageError(StorageErrorCode.INVALID_ARGUMENT, message); +} + +String codeForHTTPStatus(int status) { + switch (status) { + case 0: + // This can happen if the server returns 500. + return 'internal'; + case 400: + return 'invalid-argument'; + case 401: + return 'unauthenticated'; + case 403: + return 'permission-denied'; + case 404: + return 'not-found'; + case 409: + return 'aborted'; + case 429: + return 'resource-exhausted'; + case 499: + return 'cancelled'; + case 500: + return 'internal'; + case 501: + return 'unimplemented'; + case 503: + return 'unavailable'; + case 504: + return 'deadline-exceeded'; + default: + return 'unknown'; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/ref_api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/ref_api.dart new file mode 100644 index 00000000..96ab1ffa --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/ref_api.dart @@ -0,0 +1,415 @@ +part of api; + +/// Class wrapping methods that calls to the following endpoints: +/// - `securetoken.googleapis.com`: refresh a Firebase ID token. +@internal +class RefApi extends APIDelegate { + // ignore: public_member_api_docs + RefApi(API api) : super(api); + + /// Refresh a user's IdToken using a `refreshToken`, + /// will refresh even if the token hasn't expired. + /// + /// Common error codes: + /// - `TOKEN_EXPIRED`: The user's credential is no longer valid. The user must sign in again. + /// - `USER_DISABLED`: The user account has been disabled by an administrator. + /// - `USER_NOT_FOUND`: The user corresponding to the refresh token was not found. It is likely the user was deleted. + /// - `INVALID_REFRESH_TOKEN`: An invalid refresh token is provided. + /// - `INVALID_GRANT_TYPE`: the grant type specified is invalid. + /// - `MISSING_REFRESH_TOKEN`: no refresh token provided. + /// - API key not valid. Please pass a valid API key. (invalid API key provided) + Future refreshIdToken(String? refreshToken) async { + try { + return await _exchangeRefreshWithIdToken(refreshToken); + } catch (_) { + rethrow; + } + } + + Future _exchangeRefreshWithIdToken(String? refreshToken) async { + final baseUri = api.apiConfig.emulator != null + ? 'http://${api.apiConfig.emulator!.host}:${api.apiConfig.emulator!.port}' + '/securetoken.googleapis.com/v1/' + : 'https://securetoken.googleapis.com/v1/'; + + final _response = await http.post( + Uri.parse( + '${baseUri}token?key=${api.apiConfig.apiKey}', + ), + body: { + 'grant_type': 'refresh_token', + 'refresh_token': refreshToken, + }, + headers: {'Content-Typ': 'application/json'}, + ); + + final Map _data = json.decode(_response.body); + + return _data['access_token']; + } + + Future getRefListData( + {ListOptions? options, required Reference reference}) async { + final urlPart = reference.location.bucketOnlyServerUrl(); + final url = makeUrl(urlPart, reference.storage.host, "https"); + final urlparams = ReferenceListRessultRequestArgument( + location: reference.location, + delimiter: '/', + pageToken: options?.pageToken, + maxResults: options?.maxResults) + .toJson(); + final requestOptions =HttpsCallableOptions(method: RequestMethod.get, + timeout: reference.storage.maxOperationRetryTime, + url:url, + queryParam:urlparams ); + + listHandler(service, location.bucket), + + + requestInfo.errorHandler = sharedErrorHandler(location); + // return requestInfo; + // if (!this._deleted) { + // if (1 == 1) { + // //parse respone + // final auth = FirebaseAuth.instanceFor(app: storage.app); + // String? authToken; + // if (auth.currentUser != null) { + // authToken = await auth.currentUser!.getIdToken(); + // } + // final response = await http.get(Uri.parse('url'), headers: { + // // if (appCheckToken != null) + // // 'X-Firebase-AppCheck': '$appCheckToken' + // 'X-Firebase-GMPID': storage.app.options.appId, + // 'X-Firebase-Storage-Version': + // 'webjs/' + (firebaseVersion ?? 'AppManager'), + // if (authToken != null && authToken.isNotEmpty) + // 'Authorization': 'Firebase ' + authToken + // }).timeout(Duration(milliseconds: storage.maxOperationRetryTime), + // onTimeout: () { + // throw Exception(); + // }); + // if (response.statusCode == 200) { + // // print(response.body); + // try { + // final data = jsonDecode(response.body); + + // return ListResult( + // storage, + // data['nextPageToken'], + // (data['items'] as List).map((e) => e['name'] as String).toList(), + // (data['prefixes'] as List).map((String e) { + // final s = e.endsWith('/'); + // if (s) { + // return e.substring(0, e.length - 1); + // } + // return e; + // }).toList()); + // } on Exception catch (e) { + // throw Exception(); + // } + // } else { + // throw Exception(); + // } + // } else { + // return FailRequest(appDeleted()); + // } + } + + Future delete({required Reference reference}) { + throw UnimplementedError(); + } + + Future getDownloadURL({required Reference reference}) { + throw UnimplementedError(); + } + + Future getMetaData({required Reference reference}) { + final urlPart = reference.location.fullServerUrl(); + final url = makeUrl(urlPart, reference.storage.host, reference.storage.protocol); + final requestInfo =HttpsCallableOptions(method: RequestMethod.get, + timeout: reference.storage.maxOperationRetryTime, + url:url, ); + throw UnimplementedError(); + } + + Future getData({required Reference reference, required maxSize}) { + throw UnimplementedError(); + } + + putData( + {required Reference reference, + required Uint8List data, + SettableMetadata? metadata}) { + throw UnimplementedError(); + } + + putFile( + {required Reference reference, + required File file, + SettableMetadata? metadata}) { + throw UnimplementedError(); + } + + putBlob( + {required Reference reference, + required dynamic blob, + SettableMetadata? metadata}) { + throw UnimplementedError(); + } + + putString( + {required Reference reference, + required String data, + required PutStringFormat format, + SettableMetadata? metadata}) { + throw UnimplementedError(); + } + + updateMetadata( + {required Reference reference, required SettableMetadata metadata}) { + throw UnimplementedError(); + } + + writeToFile({required Reference reference, required File file}) { + throw UnimplementedError(); + } +} + +/// A [StorageRequest] instance that lets you call a cloud function. +class StorageRequest { + /// Creates an StorageRequest + @visibleForTesting + StorageRequest({ + required this.app, + required this.origin, + required this.options, + required http.Client client, + }) : _client = client; + + /// The [FirebaseApp] this function belongs to + final FirebaseApp app; + + /// Configuration options for timeout + final HttpsCallableOptions options; + + /// Origin specifies a different origin in the case of emulators. + @visibleForTesting + final String? origin; + + /// The Http Client used for making requests + final http.Client _client; + + Future makeRequest() async { + switch (options.method) { + case RequestMethod.get: + return get(); + case RequestMethod.post: + return post(); + case RequestMethod.delete: + return delete(); + case RequestMethod.patch: + return patch(); + } + } + + Future callRequest() async { + await addHeaders(); + await makeRequest(); + } + + Future addHeaders() async { + options.headers ??= {}; + // add auth headers + final auth = FirebaseAuth.instanceFor(app: app); + if (auth.currentUser != null) { + final authToken = await auth.currentUser!.getIdToken(); + options.headers!['Authorization'] = authToken; + } + // add version headers + options.headers!['X-Firebase-Storage-Version'] = + 'webjs/' + (firebaseVersion ?? 'AppManager'); + // add APP ID header + options.headers!.addAll({'X-Firebase-GMPID': app.options.appId}); + // add app check token header //TODO + options.headers!.addAll({'X-Firebase-AppCheck': app.options.appId}); + } + + /// Calls the function with the given data. + Future> call([dynamic data]) async { + assert(_debugIsValidParameterType(data), 'data must be json serialized'); + String encodedData; + try { + encodedData = json.encode({'data': data}); + } catch (e, st) { + throw FirebaseStorageException( + //TODO + StorageErrorCode.APP_DELETED, + message: 'Data was not json encodeable', + // code: 'internal', + // details: + // '${options.timeout} millisecond timeout occurred on request to $_url with $data', + // stackTrace: st, + ); + } + + try { + final response = await makeRequest(); + if (response.statusCode >= 200 && response.statusCode < 300) { + Map body; + try { + body = json.decode(response.body.isEmpty ? '{}' : response.body) + as Map; + } catch (e, st) { + throw FirebaseStorageException( + //TODO + + StorageErrorCode.APP_DELETED, + message: 'Failed to parse json response', + // code: 'internal', + // details: 'Result body from http call was ${response.body}', + // stackTrace: st, + ); + } + if (!body.containsKey('data') && !body.containsKey('result')) { + throw FirebaseStorageException( + //TODO + StorageErrorCode.APP_DELETED, + message: 'Response is missing data field', + // code: 'internal', + // details: 'Result body from http call was ${response.body}', + ); + } + // Result is for backwards compatibility + final result = body['data'] ?? body['result']; + + return HttpsCallableResult(result); + } else { + Map details; + try { + details = json.decode(response.body); + } catch (e) { + // fine if we can't parse explicit error data + details = {'details': response.body}; + } + throw _errorForResponse( + response.statusCode, + details, + ); + } + } on TimeoutException catch (e, st) { + throw FirebaseStorageException( + //TODO + StorageErrorCode.APP_DELETED, + message: 'Firebase functions timeout', + // code: 'timeout', + // details: + // '${options.timeout} millisecond timeout occurred on request to $_url with $encodedData', + // stackTrace: st, + ); + } + } + + Future post() async { + return await _client + .post( + Uri.parse(options.url).replace(queryParameters: options.queryParam), + headers: options.headers, + body: options.body) + .timeout(Duration(milliseconds: options.timeout)); + } + + Future get() async { + return await _client + .get( + Uri.parse(options.url).replace(queryParameters: options.queryParam), + headers: options.headers, + ) + .timeout(Duration(milliseconds: options.timeout)); + } + + Future patch() async { + return await _client + .patch( + Uri.parse(options.url).replace(queryParameters: options.queryParam), + headers: options.headers, + body: options.body) + .timeout(Duration(milliseconds: options.timeout)); + } + + Future delete() async { + return await _client + .delete( + Uri.parse(options.url).replace(queryParameters: options.queryParam), + headers: options.headers, + body: options.body) + .timeout(Duration(milliseconds: options.timeout)); + } +} + +/// The result of calling a HttpsCallable function. +class HttpsCallableResult { + /// Creates a new [HttpsCallableResult] + HttpsCallableResult(this._data); + + final T _data; + + /// Returns the data that was returned from the Callable HTTPS trigger. + T get data { + return _data; + } +} + +/// Options for configuring the behavior of a firebase cloud function +class HttpsCallableOptions { + /// Options for configuring the behavior of a firebase cloud function + HttpsCallableOptions( + {required this.url, + this.successCodes, + required this.method, + this.headers, + this.queryParam, + this.body, + required this.timeout}); + + /// The timeout for the function call + final int timeout; + final Map? queryParam; + Map? headers; + List? successCodes; + final RequestMethod method; + final String url; + final dynamic body; +} + +/// Whether a given call parameter is a valid type. +bool _debugIsValidParameterType(dynamic parameter, [bool isRoot = true]) { + if (parameter is List) { + for (final element in parameter) { + if (!_debugIsValidParameterType(element, false)) { + return false; + } + } + return true; + } + + if (parameter is Map) { + for (final key in parameter.keys) { + if (key is! String) { + return false; + } + } + for (final value in parameter.values) { + if (!_debugIsValidParameterType(value, false)) { + return false; + } + } + return true; + } + + return parameter == null || + parameter is String || + parameter is num || + parameter is bool; +} + +enum RequestMethod { get, post, delete, patch } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/api/reference_api.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/api/reference_api.dart new file mode 100644 index 00000000..4248953f --- /dev/null +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/api/reference_api.dart @@ -0,0 +1,35 @@ +part of api; + +/// Request to verify a custom token +class ReferenceListRessultRequestArgument { + Location location; + String? delimiter; + String? pageToken; + int? maxResults; + ReferenceListRessultRequestArgument({ + required this.location, + this.delimiter, + this.pageToken, + this.maxResults, + }); + + Map toJson() { + Map urlParams = {}; + if (location.isRoot) { + urlParams['prefix'] = ''; + } else { + urlParams['prefix'] = location.path + '/'; + } + final s = delimiter?.isNotEmpty; + if (delimiter != null && s == true) { + urlParams['delimiter'] = delimiter; + } + if (pageToken != null) { + urlParams['pageToken'] = pageToken; + } + if (maxResults != null) { + urlParams['maxResults'] = maxResults; + } + return urlParams; + } +} diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart index 416a5467..bd25e005 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/firebase_storage.dart @@ -22,6 +22,14 @@ class FirebaseStorage { /// The [FirebaseApp] for this current [FirebaseStorage] instance. FirebaseApp app; + final firebaseVesion = "0.6.30"; + String _host = 'firebasestorage.googleapis.com'; + String get host { + if (emulatorHost != null) { + return emulatorHost!; + } + return _host; + } /// Initialized [API] instance linked to this instance. late final API _api; @@ -134,6 +142,7 @@ class FirebaseStorage { bucket = parts!['bucket']; path = parts['path']; + _host = parts['host'] ?? _host; } else { bucket = bucketFromGoogleStorageUrl(url); path = pathFromGoogleStorageUrl(url); @@ -206,6 +215,7 @@ class FirebaseStorage { assert(!port.isNegative); try { + emulatorHost = host; return await _api.emulator.useEmulator(host, port); } catch (e) { rethrow; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart index c557d706..60a2bf77 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/implementations/urls.dart @@ -1,7 +1,11 @@ -String makeUrl(String urlPart, String host, String protocol) { +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +String makeUrl(String urlPart, String host, String? protocol) { var origin = host; if (protocol == null) { - origin = 'https://${host}'; + origin = 'https://$host'; } - return '${protocol}://${origin}/v0${urlPart}'; + return '$protocol://$origin/v0$urlPart'; } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart index 0ec9c9a7..e6bab30f 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/list_result.dart @@ -3,29 +3,32 @@ part of firebase_storage_dart; /// Class returned as a result of calling a list method ([list] or [listAll]) /// on a [Reference]. class ListResult { - ListResult._(this.storage, this._delegate) { - ListResultPlatform.verifyExtends(_delegate); - } - - ListResultPlatform _delegate; + ListResult(this.storage, String? nextPageToken, List items, + List prefixes) + : _nextPageToken = nextPageToken, + _items = items, + _prefixes = prefixes; /// The [FirebaseStorage] instance for this result. final FirebaseStorage storage; + final String? _nextPageToken; + final List _items; + + final List _prefixes; /// Objects in this directory. /// /// Returns a [List] of [Reference] instances. List get items { - return _delegate.items - .map( - (referencePlatform) => Reference._(storage, referencePlatform)) + return _items + .map((path) => Reference._(storage, Location(path, storage.bucket))) .toList(); } /// If set, there might be more results for this list. /// /// Use this token to resume the list with [ListOptions]. - String? get nextPageToken => _delegate.nextPageToken; + String? get nextPageToken => _nextPageToken; /// References to prefixes (sub-folders). You can call list() on them to get /// its contents. @@ -33,10 +36,10 @@ class ListResult { /// Folders are implicit based on '/' in the object paths. For example, if a /// bucket has two objects '/a/b/1' and '/a/b/2', list('/a') will return '/a/b' /// as a prefix. + List get prefixes { - return _delegate.prefixes - .map( - (referencePlatform) => Reference._(storage, referencePlatform)) + return _prefixes + .map((path) => Reference._(storage, Location(path, storage.bucket))) .toList(); } } diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart index 3041b753..02f3008b 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/reference.dart @@ -1,7 +1,8 @@ part of firebase_storage_dart; class Reference { - Reference.fromPath({required this.storage,required String path}):location=Location(path,storage.bucket); + Reference.fromPath({required this.storage, required String path}) + : location = Location(path, storage.bucket); Reference._(this.storage, this.location); /// The storage service associated with this reference. @@ -81,25 +82,25 @@ class Reference { } /// Fetches a long lived download URL for this object. - Future getDownloadURL() { + Future getDownloadURL() async { // ref._throwIfRoot('getDownloadURL'); - const requestInfo = requestsGetDownloadUrl( - storage, - location, - getMappings() - ); - return ref.storage - .makeRequestWithTokens(requestInfo, newTextConnection) - .then(url => { - if (url === null) { - throw noDownloadURL(); - } - return url; - }); + try { + return await storage._api.refernceApi.getDownloadURL(reference: this); + } catch (_) { + rethrow; + } } /// Fetches metadata for the object at this location, if one exists. - Future getMetadata() => _delegate.getMetadata(); + Future getMetadata() async { + try { + return await storage._api.refernceApi.getMetaData( + reference: this, + ); + } catch (_) { + rethrow; + } + } /// List items (files) and prefixes (folders) under this storage reference. /// @@ -116,7 +117,12 @@ class Reference { assert(options == null || options.maxResults == null || options.maxResults! > 0 && options.maxResults! <= 1000); - return ListResult._(storage, await _delegate.list(options)); + try { + return await storage._api.refernceApi + .getRefListData(reference: this, options: options); + } catch (_) { + rethrow; + } } /// List all items (files) and prefixes (folders) under this storage reference. @@ -130,7 +136,15 @@ class Reference { /// Warning: [listAll] may potentially consume too many resources if there are /// too many results. Future listAll() async { - return ListResult._(storage, await _delegate.listAll()); + ListResult result = await list(); + List _items = result._items.toList(); + List _prefixes = result._prefixes.toList(); + while (result.nextPageToken != null) { + result = await list(ListOptions(pageToken: result.nextPageToken)); + _items.addAll(result._items); + _prefixes.addAll(result._prefixes); + } + return ListResult(storage, null, _items, _prefixes); } /// Asynchronously downloads the object at the StorageReference to a list in memory. @@ -141,7 +155,12 @@ class Reference { /// default the [maxSize] is 10mb (10485760 bytes). Future getData([int maxSize = 10485760]) async { assert(maxSize > 0); - return _delegate.getData(maxSize); + try { + return await storage._api.refernceApi + .getData(reference: this, maxSize: maxSize); + } catch (_) { + rethrow; + } } /// Uploads data to this reference's location. @@ -150,7 +169,12 @@ class Reference { /// /// Optionally, you can also set metadata onto the uploaded object. UploadTask putData(Uint8List data, [SettableMetadata? metadata]) { - return UploadTask._(storage, _delegate.putData(data, metadata)); + try { + return storage._api.refernceApi + .putData(reference: this, data: data, metadata: metadata); + } catch (_) { + rethrow; + } } /// Upload a [Blob]. Note; this is only supported on web platforms. @@ -158,7 +182,12 @@ class Reference { /// Optionally, you can also set metadata onto the uploaded object. UploadTask putBlob(dynamic blob, [SettableMetadata? metadata]) { assert(blob != null); - return UploadTask._(storage, _delegate.putBlob(blob, metadata)); + try { + return storage._api.refernceApi + .putBlob(reference: this, blob: blob, metadata: metadata); + } catch (_) { + rethrow; + } } /// Upload a [File] from the filesystem. The file must exist. @@ -166,7 +195,12 @@ class Reference { /// Optionally, you can also set metadata onto the uploaded object. UploadTask putFile(File file, [SettableMetadata? metadata]) { assert(file.absolute.existsSync()); - return UploadTask._(storage, _delegate.putFile(file, metadata)); + try { + return storage._api.refernceApi + .putFile(reference: this, file: file, metadata: metadata); + } catch (_) { + rethrow; + } } /// Upload a [String] value as a storage object. @@ -220,20 +254,34 @@ class Reference { ); } } - return UploadTask._( - storage, _delegate.putString(_data, _format, _metadata)); + + try { + return storage._api.refernceApi.putString( + reference: this, data: _data, format: _format, metadata: metadata); + } catch (_) { + rethrow; + } } /// Updates the metadata on a storage object. Future updateMetadata(SettableMetadata metadata) { - return _delegate.updateMetadata(metadata); + try { + return storage._api.refernceApi + .updateMetadata(reference: this, metadata: metadata); + } catch (_) { + rethrow; + } } /// Writes a remote storage object to the local filesystem. /// /// If a file already exists at the given location, it will be overwritten. DownloadTask writeToFile(File file) { - return DownloadTask._(storage, _delegate.writeToFile(file)); + try { + return storage._api.refernceApi.writeToFile(reference: this, file: file); + } catch (_) { + rethrow; + } } @override diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart index 3893d4a4..249e30b4 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/task.dart @@ -17,13 +17,15 @@ abstract class Task implements Future { /// If you do not need to know about on-going stream events, you can instead /// await this [Task] directly. Stream get snapshotEvents { - return _delegate.snapshotEvents - .map((snapshotDelegate) => TaskSnapshot._(storage, snapshotDelegate)); + throw UnimplementedError(); + // return _delegate.snapshotEvents + // .map((snapshotDelegate) => TaskSnapshot._(storage, snapshotDelegate)); } /// The latest [TaskSnapshot] for this task. TaskSnapshot get snapshot { - return TaskSnapshot._(storage, _delegate.snapshot); + throw UnimplementedError(); + // return TaskSnapshot._(storage, _delegate.snapshot); } /// Pauses the current task. @@ -34,57 +36,71 @@ abstract class Task implements Future { if (snapshot.state == TaskState.paused) { return true; } - - final paused = _task.pause(); - // Wait until the snapshot is paused, then return the value of paused... - return snapshotEvents - .takeWhile((snapshot) => snapshot.state != TaskState.paused) - .last - .then((_) => paused); + throw UnimplementedError(); + // final paused = _task.pause(); + // // Wait until the snapshot is paused, then return the value of paused... + // return snapshotEvents + // .takeWhile((snapshot) => snapshot.state != TaskState.paused) + // .last + // .then((_) => paused); } /// Resumes the current task. /// /// Calling this method will trigger a snapshot event with a [TaskState.running] /// state. - Future resume() => _delegate.resume(); + Future resume() { + throw UnimplementedError(); + // _delegate.resume(); + } /// Cancels the current task. /// /// Calling this method will cause the task to fail. Both the delegating task Future /// and stream ([snapshotEvents]) will trigger an error with a [FirebaseException]. - Future cancel() => _delegate.cancel(); + Future cancel() { + throw UnimplementedError(); + // _delegate.cancel(); + } @override - Stream asStream() => - _delegate.onComplete.asStream().map((_) => snapshot); + Stream asStream() { + throw UnimplementedError(); + // _delegate.onComplete.asStream().map((_) => snapshot); + } @override Future catchError(Function onError, {bool Function(Object error)? test}) async { - await _delegate.onComplete.catchError(onError, test: test); - return snapshot; + throw UnimplementedError(); + // await _delegate.onComplete.catchError(onError, test: test); + // return snapshot; } @override Future then(FutureOr Function(TaskSnapshot) onValue, - {Function? onError}) => - _delegate.onComplete.then((_) { - return onValue(snapshot); - }, onError: onError); + {Function? onError}) { + throw UnimplementedError(); + // _delegate.onComplete.then((_) { + // return onValue(snapshot); + // }, onError: onError); + } @override Future whenComplete(FutureOr Function() action) async { - await _delegate.onComplete.whenComplete(action); - return snapshot; + throw UnimplementedError(); + // await _delegate.onComplete.whenComplete(action); + // return snapshot; } @override Future timeout(Duration timeLimit, - {FutureOr Function()? onTimeout}) => - _delegate.onComplete - .then((_) => snapshot) - .timeout(timeLimit, onTimeout: onTimeout); + {FutureOr Function()? onTimeout}) { + throw UnimplementedError(); + // _delegate.onComplete + // .then((_) => snapshot) + // .timeout(timeLimit, onTimeout: onTimeout); + } } /// A class which indicates an on-going upload task. diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart index 361783ae..3adcce3d 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/task_snapshot.dart @@ -2,46 +2,43 @@ part of firebase_storage_dart; /// A [TaskSnapshot] is returned as the result or on-going process of a [Task]. class TaskSnapshot { - TaskSnapshot._(this.storage, this._delegate) { - TaskSnapshotPlatform.verifyExtends(_delegate); - } - - TaskSnapshotPlatform _delegate; + TaskSnapshot._(this.storage, this._data); /// The [FirebaseStorage] instance used to create the task. final FirebaseStorage storage; + final Map _data; /// The current transferred bytes of this task. - int get bytesTransferred => _delegate.bytesTransferred; + int get bytesTransferred => _data['bytesTransferred']; /// The [FullMetadata] associated with this task. /// /// May be `null` if no metadata exists. - FullMetadata? get metadata => _delegate.metadata; + FullMetadata? get metadata => _data['metadata']; /// The [Reference] for this snapshot. Reference get ref { - return Reference._(storage, _delegate.ref); + return Reference._(storage, _data['path']); } /// The current task snapshot state. /// /// The state indicates the current progress of the task, such as whether it /// is running, paused or completed. - TaskState get state => _delegate.state; + TaskState get state => _data['state']; /// The total bytes of the task. /// /// Note; when performing a download task, the value of `-1` will be provided /// whilst the total size of the remote file is being determined. - int get totalBytes => _delegate.totalBytes; + int get totalBytes => _data['totalBytes']; @override bool operator ==(Object other) => other is TaskSnapshot && other.ref == ref && other.storage == storage; @override - int get hashCode => hashValues(storage, ref); + int get hashCode => Object.hashAll([storage, ref]); @override String toString() => '$TaskSnapshot(ref: $ref, state: $state)'; diff --git a/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart b/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart index 5820bdd4..e0d13cc8 100644 --- a/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart +++ b/packages/firebase_storage/firebase_storage_dart/lib/src/utils.dart @@ -37,6 +37,7 @@ Map? partsFromHttpUrl(String url) { } // firebase storage url + String? storageHost; if (decodedUrl.contains(_firebaseStorageHost) || decodedUrl.contains('localhost')) { String origin; @@ -45,6 +46,7 @@ Map? partsFromHttpUrl(String url) { origin = '^http?://${uri.host}:${uri.port}'; } else { origin = '^https?://$_firebaseStorageHost'; + storageHost = _firebaseStorageHost; } RegExp firebaseStorageRegExp = RegExp( @@ -61,11 +63,12 @@ Map? partsFromHttpUrl(String url) { return { 'bucket': match.group(1), 'path': match.group(3), + if (storageHost != null) 'host': storageHost, }; // google cloud storage url } else { RegExp cloudStorageRegExp = RegExp( - '^https?://$_cloudStorageHost$_optionalPort/$_bucketDomain/$_cloudStoragePath', + '^https?://($_cloudStorageHost)$_optionalPort/$_bucketDomain/$_cloudStoragePath', caseSensitive: false, ); @@ -76,8 +79,9 @@ Map? partsFromHttpUrl(String url) { } return { - 'bucket': match.group(1), - 'path': match.group(2), + 'host': match.group(1), + 'bucket': match.group(2), + 'path': match.group(3), }; } } diff --git a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart index f87f857f..33f51088 100644 --- a/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart +++ b/packages/firebase_storage/firebase_storage_desktop/lib/src/desktop_task_snapshot.dart @@ -10,7 +10,7 @@ import 'package:firebase_storage_platform_interface/firebase_storage_platform_in class DesktopTaskSnapshot extends TaskSnapshotPlatform { // ignore: public_member_api_docs DesktopTaskSnapshot(this.storage, TaskState state, this._data) - : super(state, _data); + : super(state, _data..addAll({'state': state})); /// The [FirebaseStoragePlatform] used to create the task. final FirebaseStoragePlatform storage;