diff --git a/.github/recipe.yaml b/.github/recipe.yaml
index 3d89ef2..f1d1272 100644
--- a/.github/recipe.yaml
+++ b/.github/recipe.yaml
@@ -1,5 +1,5 @@
plugins:
firebase_core: ["tv-7.0"]
firebase_database: ["tv-7.0"]
- firebase_storage: []
+ firebase_storage: ["tv-7.0"]
cloud_functions: ["tv-7.0"]
diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml
index 216a9d2..c5a4ee0 100644
--- a/.github/workflows/integration_test.yml
+++ b/.github/workflows/integration_test.yml
@@ -10,6 +10,7 @@ on:
jobs:
integration_test:
runs-on: [self-hosted]
+ timeout-minutes: 30
if: ${{ github.repository_owner == 'flutter-tizen' }}
steps:
- uses: actions/checkout@v3
diff --git a/README.md b/README.md
index 61015a6..a3d56de 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ FlutterFire for Tizen is a set of plugins that enable Flutter apps to use Fireba
| Cloud Functions | REPLACEME | [π](https://firebase.google.com/products/functions) | [π](https://firebase.flutter.dev/docs/functions/overview) | [`cloud_functions`](packages/cloud_functions) |
| Core | REPLACEME | [π](https://firebase.google.com) | [π](https://firebase.flutter.dev/docs/core/usage) | [`firebase_core`](packages/firebase_core) |
| Realtime Database | REPLACEME | [π](https://firebase.google.com/products/database) | [π](https://firebase.flutter.dev/docs/database/overview) | [`firebase_database`](packages/firebase_database) |
+| Storage | REPLACEME | [π](https://firebase.google.com/products/storage) | [π](https://firebase.flutter.dev/docs/storage/overview) | [`firebase_storage`](packages/firebase_storage) |
Please note that the plugins are now in an early stage of development, as they're based on the experimental implementation of the Firebase C++ SDK for Linux desktop ([v10.4.0](https://github.com/firebase/firebase-cpp-sdk/tree/v10.4.0)). Our plan is to initially provide development versions first, and then gradually transition to a stable version as a subsequent step following stable SDK releases.
@@ -19,6 +20,7 @@ Please note that the plugins are now in an early stage of development, as they'r
| Cloud Functions | 7.0 | βοΈ | βοΈ |
| Core | 7.0 | βοΈ | βοΈ |
| Realtime Database | 7.0 | βοΈ | βοΈ |
+| Storage | 7.0 | βοΈ | βοΈ |
## License
diff --git a/packages/cloud_functions/tizen/build_def.prop b/packages/cloud_functions/tizen/build_def.prop
index 64d7f61..085fff2 100644
--- a/packages/cloud_functions/tizen/build_def.prop
+++ b/packages/cloud_functions/tizen/build_def.prop
@@ -1,2 +1,2 @@
PREBUILD_COMMAND = ./tar_url.sh \
- https://github.com/daeye0n/gooddaytocode/archive/refs/tags/v10.4.0-draft-4.tar.gz
+ https://raw.githubusercontent.com/hs0225/download/firebase-sdk/firebaseSDK-tizen-1.0.1.tar.gz
diff --git a/packages/firebase_core/tizen/build_def.prop b/packages/firebase_core/tizen/build_def.prop
index 144eb94..86c20a8 100644
--- a/packages/firebase_core/tizen/build_def.prop
+++ b/packages/firebase_core/tizen/build_def.prop
@@ -1,3 +1,3 @@
PREBUILD_COMMAND = ./tar_url.sh \
- https://github.com/daeye0n/gooddaytocode/archive/refs/tags/v10.4.0-draft-4.tar.gz \
+ https://raw.githubusercontent.com/hs0225/download/firebase-sdk/firebaseSDK-tizen-1.0.1.tar.gz\
&& ./cp_firebase_libs.sh
diff --git a/packages/firebase_storage/.clang-format b/packages/firebase_storage/.clang-format
new file mode 100644
index 0000000..5ab0945
--- /dev/null
+++ b/packages/firebase_storage/.clang-format
@@ -0,0 +1,3 @@
+---
+BasedOnStyle: Google
+---
diff --git a/packages/firebase_storage/.gitignore b/packages/firebase_storage/.gitignore
new file mode 100644
index 0000000..d87f6f0
--- /dev/null
+++ b/packages/firebase_storage/.gitignore
@@ -0,0 +1,28 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# VS Code related
+.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/CHANGELOG.md b/packages/firebase_storage/CHANGELOG.md
new file mode 100644
index 0000000..6073234
--- /dev/null
+++ b/packages/firebase_storage/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+* Initial release.
diff --git a/packages/firebase_storage/LICENSE b/packages/firebase_storage/LICENSE
new file mode 100644
index 0000000..f336bce
--- /dev/null
+++ b/packages/firebase_storage/LICENSE
@@ -0,0 +1,61 @@
+```
+Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
+
+```
+Copyright 2017 The Chromium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Google Inc. 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
+OWNER 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.
+```
+
+```
+Copyright 2019 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
diff --git a/packages/firebase_storage/README.md b/packages/firebase_storage/README.md
new file mode 100644
index 0000000..6c9baa6
--- /dev/null
+++ b/packages/firebase_storage/README.md
@@ -0,0 +1,43 @@
+# firebase_storage_tizen
+
+The [Firebase Storage for Flutter](https://pub.dev/packages/firebase_storage) implementation for Tizen.
+
+It offers experimental features for using Firebase on Flutter for Tizen. It works by wrapping cross-compiled libraries that are based on the [Firebase C++ SDK](https://github.com/firebase/firebase-cpp-sdk) for Linux.
+
+
+# Usage
+
+To use this package, you need to include `firebase_storage_tizen` as a dependency alongside `firebase_storage` in your `pubspec.yaml`. Please note that `firebase_storage_tizen` implementation is not officially endorsed for `firebase_storage`.
+
+```yaml
+dependencies:
+ firebase_storage: ^11.0.10
+ firebase_storage_tizen: ^0.1.0
+```
+
+Then you can import `firebase_storage` in your Dart code:
+
+```dart
+import 'package:firebase_storage/firebase_storage.dart';
+```
+
+## Required privileges
+
+To use this plugin in a Tizen application, you may need to declare the following privileges in your `tizen-manifest.xml` file.
+
+```xml
+
+ http://tizen.org/privilege/internet
+
+```
+
+- `http://tizen.org/privilege/internet` allows the application to access the Internet.
+
+For the details on Tizen privileges, please see [Tizen Docs: API Privileges](https://docs.tizen.org/application/dotnet/get-started/api-privileges).
+
+# Limitations
+
+The following features are currently unavailable as they're not supported by the version of Firebase C++ SDK for Linux that this plugin is currently based on.
+
+ - useEmulator method of FirebaseStorage class.
+ - listAll method of Reference class.
diff --git a/packages/firebase_storage/analysis_options.yaml b/packages/firebase_storage/analysis_options.yaml
new file mode 100644
index 0000000..cd5e21a
--- /dev/null
+++ b/packages/firebase_storage/analysis_options.yaml
@@ -0,0 +1,92 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# in the LICENSE file.
+
+include: all_lint_rules.yaml
+analyzer:
+ # TODO(rrousselGit): disable implicit-cast/implicit-dynamic
+ errors:
+ # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts.
+ # We explicitly enabled even conflicting rules and are fixing the conflict
+ # in this file
+ included_file_warning: ignore
+
+linter:
+ rules:
+ ## Disabled rules because the repository doesn't respect them (yet)
+
+ always_put_control_body_on_new_line: false
+ comment_references: false
+ prefer_constructors_over_static_methods: false
+ prefer_final_fields: false
+ prefer_final_locals: false
+ omit_local_variable_types: false
+ avoid_equals_and_hash_code_on_mutable_classes: false
+
+ #############
+
+ # Personal preference. I don't find it more readable
+ cascade_invocations: false
+
+ # Conflicts with `prefer_single_quotes`
+ # Single quotes are easier to type and don't compromise on readability.
+ prefer_double_quotes: false
+
+ # Conflicts with `omit_local_variable_types` and other rules.
+ # As per Dart guidelines, we want to avoid unnecessary types to make the code
+ # more readable.
+ # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables
+ always_specify_types: false
+
+ # Incompatible with `prefer_final_locals`
+ # Having immutable local variables makes larger functions more predictible
+ # so we will use `prefer_final_locals` instead.
+ unnecessary_final: false
+
+ # Not quite suitable for Flutter, which may have a `build` method with a single
+ # return, but that return is still complex enough that a "body" is worth it.
+ prefer_expression_function_bodies: false
+
+ # Conflicts with the convention used by flutter, which puts `Key key`
+ # and `@required Widget child` last.
+ always_put_required_named_parameters_first: false
+
+ # `as` is not that bad (especially with the upcoming non-nullable types).
+ # Explicit exceptions is better than implicit exceptions.
+ avoid_as: false
+
+ # This project doesn't use Flutter-style todos
+ flutter_style_todos: false
+
+ # There are situations where we voluntarily want to catch everything,
+ # especially as a library.
+ avoid_catches_without_on_clauses: false
+
+ # Boring as it sometimes force a line of 81 characters to be split in two.
+ # As long as we try to respect that 80 characters limit, going slightly
+ # above is fine.
+ lines_longer_than_80_chars: false
+
+ # Conflicts with disabling `implicit-dynamic`
+ avoid_annotating_with_dynamic: false
+
+ # conflicts with `prefer_relative_imports`
+ always_use_package_imports: false
+
+ # Disabled for now until we have NNBD as it otherwise conflicts with `missing_return`
+ no_default_cases: false
+
+ # False positive, null checks don't need a message
+ prefer_asserts_with_message: false
+
+ # Cumbersome with `context.select`
+ avoid_types_on_closure_parameters: false
+
+ # Too many false positive (builders)
+ diagnostic_describe_all_properties: false
+
+ # false positives (setter-like functions)
+ avoid_positional_boolean_parameters: false
+
+ # Does not apply to providers
+ prefer_const_constructors_in_immutables: false
diff --git a/packages/firebase_storage/example/.gitignore b/packages/firebase_storage/example/.gitignore
new file mode 100644
index 0000000..f0f3dcb
--- /dev/null
+++ b/packages/firebase_storage/example/.gitignore
@@ -0,0 +1,38 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# VS Code related
+.vscode/
+
+# Flutter/Dart/Pub related
+/pubspec.lock
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
diff --git a/packages/firebase_storage/example/README.md b/packages/firebase_storage/example/README.md
new file mode 100644
index 0000000..bec2b4a
--- /dev/null
+++ b/packages/firebase_storage/example/README.md
@@ -0,0 +1,16 @@
+# firebase_storage_tizen_example
+
+Demonstrates how to use the firebase_storage_tizen plugin.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/packages/firebase_storage/example/analysis_options.yaml b/packages/firebase_storage/example/analysis_options.yaml
new file mode 100644
index 0000000..a174f61
--- /dev/null
+++ b/packages/firebase_storage/example/analysis_options.yaml
@@ -0,0 +1,10 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# in the LICENSE file.
+
+include: ../../../analysis_options.yaml
+linter:
+ rules:
+ avoid_print: false
+ depend_on_referenced_packages: false
+ library_private_types_in_public_api: false
diff --git a/packages/firebase_storage/example/assets/hello.txt b/packages/firebase_storage/example/assets/hello.txt
new file mode 100644
index 0000000..802992c
--- /dev/null
+++ b/packages/firebase_storage/example/assets/hello.txt
@@ -0,0 +1 @@
+Hello world
diff --git a/packages/firebase_storage/example/integration_test/extratest_e2e.dart b/packages/firebase_storage/example/integration_test/extratest_e2e.dart
new file mode 100644
index 0000000..e88bd53
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/extratest_e2e.dart
@@ -0,0 +1,104 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. 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:convert';
+import 'dart:io';
+
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import './test_utils.dart';
+
+const String kOkString = 'kOk';
+
+void setupExtraTests() {
+ group('$Reference', () {
+ late FirebaseStorage storage;
+
+ setUpAll(() async {
+ storage = FirebaseStorage.instance;
+ });
+
+ test('getData', () async {
+ final Reference ref = storage.ref('flutter-tests/ok.txt');
+
+ // fix: Ensure reference file has an ok string.
+ await ref.putString(kOkString);
+ final bytes = await ref.getData();
+
+ List okStringList = utf8.encode(kOkString);
+ expect(bytes?.length, okStringList.length);
+ expect(bytes, okStringList);
+ });
+
+ test('getMetadata', () async {
+ final Reference ref = storage.ref('flutter-tests/ok.txt');
+
+ final result = await ref.getMetadata();
+ expect(result, isA());
+ expect(result.name, 'ok.txt');
+ });
+ });
+
+ group('ControlTask', () {
+ late FirebaseStorage storage;
+
+ setUpAll(() async {
+ storage = FirebaseStorage.instance;
+ });
+
+ Future _testPauseTask(Task task) async {
+ List snapshots = [];
+ bool hasError = false;
+ expect(task.snapshot.state, TaskState.running);
+
+ task.snapshotEvents.listen(
+ (TaskSnapshot snapshot) {
+ snapshots.add(snapshot);
+ },
+ onError: (error) {
+ hasError = true;
+ },
+ cancelOnError: true,
+ );
+
+ await task.snapshotEvents.first;
+ await Future.delayed(const Duration(milliseconds: 100));
+
+ bool? paused = await task.pause();
+ expect(paused, isTrue);
+ expect(task.snapshot.state, TaskState.paused);
+
+ await Future.delayed(const Duration(milliseconds: 100));
+ bool? resumed = await task.resume();
+ expect(resumed, isTrue);
+ expect(task.snapshot.state, TaskState.running);
+
+ TaskSnapshot snapshot = await task;
+ expect(task.snapshot.state, TaskState.success);
+ expect(snapshot.state, TaskState.success);
+
+ expect(snapshot.totalBytes, snapshot.bytesTransferred);
+
+ expect(hasError, false);
+ }
+
+ test('upload pause and resume', () async {
+ final File file = await createFile('test-file.txt');
+ final Reference ref = storage.ref('flutter-tests').child('test-file.txt');
+
+ await _testPauseTask(ref.putFile(file));
+ });
+
+ test('download pause and resume', () async {
+ final Directory systemTempDir = Directory.systemTemp;
+ final File tempFile = File('${systemTempDir.path}/temp-test-file.txt');
+ if (tempFile.existsSync()) await tempFile.delete();
+
+ final Reference ref = storage.ref('flutter-tests').child('test-file.txt');
+
+ await _testPauseTask(ref.writeToFile(tempFile));
+ });
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/firebase_storage_e2e_test.dart b/packages/firebase_storage/example/integration_test/firebase_storage_e2e_test.dart
new file mode 100644
index 0000000..7cb9095
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/firebase_storage_e2e_test.dart
@@ -0,0 +1,64 @@
+// Copyright 2022, 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_core/firebase_core.dart';
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../lib/firebase_options.dart';
+import 'instance_e2e.dart';
+import 'list_result_e2e.dart';
+import 'reference_e2e.dart';
+import 'task_e2e.dart';
+import 'extratest_e2e.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('firebase_storage', () {
+ setUpAll(() async {
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ // Add a write only file
+ await FirebaseStorage.instance
+ .ref('writeOnly.txt')
+ .putString('Write Only');
+
+ await FirebaseStorage.instance
+ .ref('flutter-tests/ok.txt')
+ .putString('Ok!');
+
+ // Setup list items - Future.wait not working...
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/file1.txt')
+ .putString('File 1');
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/file2.txt')
+ .putString('File 2');
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/file3.txt')
+ .putString('File 3');
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/file4.txt')
+ .putString('File 4');
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/nested/file5.txt')
+ .putString('File 5');
+
+ // fix: Add file to pass the list result test.
+ await FirebaseStorage.instance
+ .ref('flutter-tests/list/child/file6.txt')
+ .putString('File 6');
+ });
+
+ setupInstanceTests();
+ setupListResultTests();
+ setupReferenceTests();
+ setupTaskTests();
+ setupExtraTests();
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/instance_e2e.dart b/packages/firebase_storage/example/integration_test/instance_e2e.dart
new file mode 100644
index 0000000..a3071e5
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/instance_e2e.dart
@@ -0,0 +1,250 @@
+// Copyright 2022, 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_core/firebase_core.dart';
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../lib/firebase_options.dart';
+import 'test_utils.dart';
+
+void setupInstanceTests() {
+ group('$FirebaseStorage', () {
+ late FirebaseStorage storage;
+ late FirebaseApp secondaryApp;
+ late FirebaseApp secondaryAppWithoutBucket;
+
+ setUpAll(() async {
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+ storage = FirebaseStorage.instance;
+ secondaryApp = await testInitializeSecondaryApp();
+ });
+
+ test('instance', () {
+ expect(storage, isA());
+ expect(storage.app, isA());
+ expect(storage.app.name, defaultFirebaseAppName);
+ });
+
+ test('instanceFor', () {
+ FirebaseStorage secondaryStorage =
+ FirebaseStorage.instanceFor(app: secondaryApp, bucket: 'test');
+ expect(storage.app, isA());
+ expect(secondaryStorage, isA());
+ expect(secondaryStorage.app.name, 'testapp');
+ });
+
+ test('default bucket cannot be null', () async {
+ try {
+ secondaryAppWithoutBucket =
+ await testInitializeSecondaryApp(withDefaultBucket: false);
+
+ FirebaseStorage.instanceFor(
+ app: secondaryAppWithoutBucket,
+ );
+ fail('should have thrown an error');
+ } on FirebaseException catch (e) {
+ expect(
+ e.message,
+ "No storage bucket could be found for the app 'testapp-no-bucket'. Ensure you have set the [storageBucket] on [FirebaseOptions] whilst initializing the secondary Firebase app.",
+ );
+ }
+ });
+
+ group('ref', () {
+ test('uses default path if none provided', () {
+ Reference ref = storage.ref();
+ expect(ref.fullPath, '/');
+ });
+
+ test('accepts a custom path', () async {
+ Reference ref = storage.ref('foo/bar/baz.png');
+ expect(ref.fullPath, 'foo/bar/baz.png');
+ });
+
+ test('strips leading / from custom path', () async {
+ Reference ref = storage.ref('/foo/bar/baz.png');
+ expect(ref.fullPath, 'foo/bar/baz.png');
+ });
+ });
+
+ group('refFromURL', () {
+ test('accepts a gs url', () async {
+ const url = 'gs://foo/bar/baz.png';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.fullPath, 'bar/baz.png');
+ expect(ref.bucket, 'foo');
+ });
+
+ test('accepts a https url from google cloud', () async {
+ const url =
+ 'https://storage.googleapis.com/flutterfire-e2e-tests.appspot.com/pdf/4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241');
+ expect(
+ ref.fullPath,
+ 'pdf/4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241',
+ );
+ });
+
+ test('accepts a https url', () async {
+ const url =
+ 'https://firebasestorage.googleapis.com/v0/b/flutterfire-e2e-tests.appspot.com/o/1mbTestFile.gif?alt=media';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '1mbTestFile.gif');
+ expect(ref.fullPath, '1mbTestFile.gif');
+ });
+
+ test('accepts a https url with a deep path', () async {
+ const url =
+ 'https://firebasestorage.googleapis.com/v0/b/flutterfire-e2e-tests.appspot.com/o/nested/path/segments/1mbTestFile.gif?alt=media';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '1mbTestFile.gif');
+ expect(ref.fullPath, 'nested/path/segments/1mbTestFile.gif');
+ });
+
+ test('accepts a https url with special characters', () async {
+ const url =
+ 'https://firebasestorage.googleapis.com/v0/b/flutterfire-e2e-tests.appspot.com/o/foo+bar/file with spaces.png?alt=media';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, 'file with spaces.png');
+ expect(ref.fullPath, 'foo+bar/file with spaces.png');
+ });
+
+ test('accepts a https encoded url', () async {
+ const url =
+ 'https%3A%2F%2Ffirebasestorage.googleapis.com%2Fv0%2Fb%2Fflutterfire-e2e-tests.appspot.com%2Fo%2F1mbTestFile.gif%3Falt%3Dmedia';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '1mbTestFile.gif');
+ expect(ref.fullPath, '1mbTestFile.gif');
+ });
+
+ test('accepts a Storage emulator url', () {
+ const url =
+ 'http://localhost:9199/v0/b/flutterfire-e2e-tests.appspot.com/o/1mbTestFile.gif?alt=media';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '1mbTestFile.gif');
+ expect(ref.fullPath, '1mbTestFile.gif');
+ });
+
+ test('accepts a https url including port number', () {
+ const url =
+ 'https://firebasestorage.googleapis.com:433/v0/b/flutterfire-e2e-tests.appspot.com/o/nested/path/segments/1mbTestFile.gif?alt=media';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(ref.name, '1mbTestFile.gif');
+ expect(ref.fullPath, 'nested/path/segments/1mbTestFile.gif');
+
+ const googleUrl =
+ 'https://storage.googleapis.com/flutterfire-e2e-tests.appspot.com/pdf/4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241';
+ Reference refGoogle = storage.refFromURL(googleUrl);
+ expect(refGoogle.bucket, 'flutterfire-e2e-tests.appspot.com');
+ expect(refGoogle.name, '4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241');
+ expect(
+ refGoogle.fullPath,
+ 'pdf/4lqA70lYwfRgH1krOevw6mLMgPs2_162613790513241',
+ );
+ });
+
+ test('throws an error if https url could not be parsed', () async {
+ expect(
+ () {
+ storage.refFromURL('https://invertase.io');
+ fail('Did not throw an Error.');
+ },
+ throwsA(
+ isA().having(
+ (p0) => p0.message,
+ 'assertion message',
+ contains(
+ "url could not be parsed, ensure it's a valid storage url",
+ ),
+ ),
+ ),
+ );
+ });
+
+ test('accepts a gs url without a fullPath', () async {
+ const url = 'gs://some-bucket';
+ Reference ref = storage.refFromURL(url);
+ expect(ref.bucket, url.replaceFirst('gs://', ''));
+ expect(ref.fullPath, '/');
+ });
+
+ test('throws an error if url does not start with gs:// or https://',
+ () async {
+ expect(
+ () {
+ storage.refFromURL('bs://foo/bar/cat.gif');
+ fail('Should have thrown an [AssertionError]');
+ },
+ throwsA(
+ isA().having(
+ (p0) => p0.message,
+ 'assertion message',
+ contains("a url must start with 'gs://' or 'https://'"),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('setMaxOperationRetryTime', () {
+ test('should set', () async {
+ expect(
+ storage.maxOperationRetryTime,
+ const Duration(milliseconds: 120000),
+ );
+ storage.setMaxOperationRetryTime(const Duration(milliseconds: 100000));
+ expect(
+ storage.maxOperationRetryTime,
+ const Duration(milliseconds: 100000),
+ );
+ });
+ });
+
+ group('setMaxUploadRetryTime', () {
+ test('should set', () async {
+ expect(
+ storage.maxUploadRetryTime,
+ const Duration(milliseconds: 600000),
+ );
+ storage.setMaxUploadRetryTime(const Duration(milliseconds: 120000));
+ expect(
+ storage.maxUploadRetryTime,
+ const Duration(milliseconds: 120000),
+ );
+ });
+ });
+
+ group('setMaxDownloadRetryTime', () {
+ test('should set', () async {
+ expect(
+ storage.maxDownloadRetryTime,
+ const Duration(milliseconds: 600000),
+ );
+ storage.setMaxDownloadRetryTime(const Duration(milliseconds: 120000));
+ expect(
+ storage.maxDownloadRetryTime,
+ const Duration(milliseconds: 120000),
+ );
+ });
+ });
+
+ test('toString', () {
+ expect(
+ storage.toString(),
+ 'FirebaseStorage(app: [DEFAULT], bucket: flutterfire-e2e-tests.appspot.com)',
+ );
+ });
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/list_result_e2e.dart b/packages/firebase_storage/example/integration_test/list_result_e2e.dart
new file mode 100644
index 0000000..d74b788
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/list_result_e2e.dart
@@ -0,0 +1,37 @@
+// Copyright 2022, 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/firebase_storage.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void setupListResultTests() {
+ group('$ListResult', () {
+ late FirebaseStorage storage;
+ late ListResult result;
+
+ setUpAll(() async {
+ storage = FirebaseStorage.instance;
+ Reference ref = storage.ref('flutter-tests/list');
+ // Needs to be > half of the # of items in the storage,
+ // so there's a chance of picking up some items and some
+ // prefixes.
+ // fix: Change maxResults from 3 to 5 with reference to above.
+ result = await ref.list(const ListOptions(maxResults: 5));
+ });
+
+ test('items', () async {
+ expect(result.items, isA>());
+ expect(result.items.length, greaterThan(0));
+ });
+
+ test('nextPageToken', () async {
+ expect(result.nextPageToken, isNotNull);
+ });
+
+ test('prefixes', () async {
+ expect(result.prefixes, isA>());
+ expect(result.prefixes.length, greaterThan(0));
+ });
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/reference_e2e.dart b/packages/firebase_storage/example/integration_test/reference_e2e.dart
new file mode 100644
index 0000000..c0cbc67
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/reference_e2e.dart
@@ -0,0 +1,467 @@
+// Copyright 2022, 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:convert';
+import 'dart:io';
+
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import './test_utils.dart';
+
+void setupReferenceTests() {
+ group('$Reference', () {
+ late FirebaseStorage storage;
+
+ setUpAll(() async {
+ storage = FirebaseStorage.instance;
+ });
+
+ group('bucket', () {
+ test('returns the storage bucket as a string', () async {
+ expect(
+ storage.ref('/ok.jpeg').bucket,
+ storage.app.options.storageBucket,
+ );
+ });
+ });
+
+ group('fullPath', () {
+ test('returns the full path as a string', () async {
+ expect(
+ storage.ref('/foo/uploadNope.jpeg').fullPath,
+ 'foo/uploadNope.jpeg',
+ );
+
+ expect(
+ storage.ref('foo/uploadNope.jpeg').fullPath,
+ 'foo/uploadNope.jpeg',
+ );
+ });
+ });
+
+ group('name', () {
+ test('returns the file name as a string', () async {
+ Reference ref = storage.ref('/foo/uploadNope.jpeg');
+ expect(ref.name, 'uploadNope.jpeg');
+ });
+ });
+
+ group('parent', () {
+ test('returns the parent directory as a reference', () async {
+ expect(storage.ref('/foo/uploadNope.jpeg').parent?.fullPath, 'foo');
+ });
+
+ test('returns null if already at root', () async {
+ Reference ref = storage.ref('/');
+ expect(ref.parent, isNull);
+ });
+ });
+
+ group('root', () {
+ test('returns a reference to the root of the bucket', () async {
+ expect(storage.ref('/foo/uploadNope.jpeg').root.fullPath, '/');
+ });
+ });
+
+ group('child()', () {
+ test('returns a reference to a child path', () async {
+ Reference parentRef = storage.ref('/foo');
+ Reference childRef = parentRef.child('someFile.json');
+
+ expect(childRef.fullPath, 'foo/someFile.json');
+ });
+ });
+
+ group('delete()', () {
+ test('should delete a file', () async {
+ Reference ref = storage.ref('flutter-tests/deleteMe.jpeg');
+ await ref.putString('To Be Deleted :)');
+ await ref.delete();
+
+ await expectLater(
+ () => ref.delete(),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'object-not-found')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'No object exists at the desired reference',
+ ),
+ ),
+ );
+ });
+
+ test('throws error if file does not exist', () async {
+ Reference ref = storage.ref('flutter-tests/iDoNotExist.jpeg');
+
+ await expectLater(
+ () => ref.delete(),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'object-not-found')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'No object exists at the desired reference',
+ ),
+ ),
+ );
+ });
+
+ test('throws error if no write permission', () async {
+ Reference ref = storage.ref('/uploadNope.jpeg');
+
+ await expectLater(
+ () => ref.delete(),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'unauthorized')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'User is not authorized to perform the desired action',
+ ),
+ ),
+ );
+ });
+ });
+
+ group('getDownloadURL', () {
+ test(
+ 'gets a download url',
+ () async {
+ Reference ref = storage.ref('flutter-tests/ok.txt');
+ await ref.putString('ok');
+
+ String downloadUrl = await ref.getDownloadURL();
+ expect(downloadUrl, isA());
+ expect(downloadUrl, contains('ok.txt'));
+ expect(downloadUrl, contains(storage.app.options.projectId));
+ },
+ // Fails on emulator since iOS SDK 10. See PR notes:
+ // https://github.com/firebase/flutterfire/pull/9708
+ skip: defaultTargetPlatform == TargetPlatform.iOS ||
+ defaultTargetPlatform == TargetPlatform.macOS,
+ );
+
+ test('errors if permission denied', () async {
+ Reference ref = storage.ref('writeOnly.txt');
+
+ await expectLater(
+ () => ref.getDownloadURL(),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'unauthorized')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'User is not authorized to perform the desired action',
+ ),
+ ),
+ );
+ });
+
+ test('throws error if file does not exist', () async {
+ Reference ref = storage.ref('flutter-tests/iDoNotExist.jpeg');
+
+ await expectLater(
+ () => ref.getDownloadURL(),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'object-not-found')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'No object exists at the desired reference',
+ ),
+ ),
+ );
+ });
+ });
+
+ group('list', () {
+ test('returns list results', () async {
+ Reference ref = storage.ref('flutter-tests/list');
+ ListResult result = await ref.list(const ListOptions(maxResults: 25));
+
+ expect(result.items.length, greaterThan(0));
+ expect(result.prefixes, isA>());
+ expect(result.prefixes.length, greaterThan(0));
+ });
+
+ test('errors if maxResults is less than 0 ', () async {
+ Reference ref = storage.ref('/list');
+ expect(
+ () => ref.list(const ListOptions(maxResults: -1)),
+ throwsAssertionError,
+ );
+ });
+
+ test('errors if maxResults is 0 ', () async {
+ Reference ref = storage.ref('/list');
+ expect(
+ () => ref.list(const ListOptions(maxResults: 0)),
+ throwsAssertionError,
+ );
+ });
+
+ test('errors if maxResults is more than 1000 ', () async {
+ Reference ref = storage.ref('/list');
+ expect(
+ () => ref.list(const ListOptions(maxResults: 1001)),
+ throwsAssertionError,
+ );
+ });
+ });
+
+ // hosung: not supported on Tizen.
+ test('listAll', () async {
+ Reference ref = storage.ref('flutter-tests/list');
+ ListResult result = await ref.listAll();
+ expect(result.items, isNotNull);
+ expect(result.items.length, greaterThan(0));
+ expect(result.nextPageToken, isNull);
+
+ expect(result.prefixes, isA>());
+ expect(result.prefixes.length, greaterThan(0));
+ }, skip: Platform.isLinux);
+
+ group(
+ 'putData',
+ () {
+ test('uploads a file with buffer', () async {
+ List list = utf8.encode(kTestString);
+
+ Uint8List data = Uint8List.fromList(list);
+
+ final Reference ref =
+ storage.ref('flutter-tests').child('flt-ok.txt');
+
+ final TaskSnapshot complete = await ref.putData(
+ data,
+ SettableMetadata(
+ contentLanguage: 'en',
+ ),
+ );
+
+ expect(complete.metadata?.size, kTestString.length);
+ // Metadata isn't saved on objects when using the emulator which fails test
+ // expect(complete.metadata?.contentLanguage, 'en');
+ });
+
+ //TODO(pr-mais): causes the emulator to crash
+ // test('errors if permission denied', () async {
+ // List list = utf8.encode('hello world');
+ // Uint8List data = Uint8List.fromList(list);
+
+ // final Reference ref = storage.ref('/uploadNope.jpeg');
+
+ // await expectLater(
+ // () => ref.putData(data),
+ // throwsA(isA()
+ // .having((e) => e.code, 'code', 'unauthorized')
+ // .having((e) => e.message, 'message',
+ // 'User is not authorized to perform the desired action.')));
+ // });
+ },
+ skip: kIsWeb,
+ );
+
+ group('putBlob', () {
+ test(
+ 'throws [UnimplementedError] for native platforms',
+ () async {
+ final File file = await createFile('flt-ok.txt');
+ final Reference ref =
+ storage.ref('flutter-tests').child('flt-ok.txt');
+
+ await expectLater(
+ () => ref.putBlob(
+ file,
+ SettableMetadata(
+ contentLanguage: 'en',
+ customMetadata: {'activity': 'test'},
+ ),
+ ),
+ throwsA(
+ isA().having(
+ (e) => e.message,
+ 'message',
+ 'putBlob() is not supported on native platforms. Use [put], [putFile] or [putString] instead.',
+ ),
+ ),
+ );
+
+ // This *must* be skipped in web, the test is intended for native platforms.
+ },
+ skip: kIsWeb,
+ );
+ });
+
+ group(
+ 'putFile',
+ () {
+ test(
+ 'uploads a file',
+ () async {
+ final File file = await createFile('flt-ok.txt');
+
+ final Reference ref =
+ storage.ref('flutter-tests').child('flt-ok.txt');
+
+ final TaskSnapshot complete = await ref.putFile(
+ file,
+ SettableMetadata(
+ contentLanguage: 'en',
+ customMetadata: {'activity': 'test'},
+ ),
+ );
+
+ expect(complete.metadata?.size, kTestString.length);
+ // Metadata isn't saved on objects when using the emulator which fails test
+ // expect(complete.metadata?.contentLanguage, 'en');
+ // expect(complete.metadata?.customMetadata!['activity'], 'test');
+ },
+ );
+
+ // TODO(ehesp): Emulator rules issue - comment back in once fixed
+ // test('errors if permission denied', () async {
+ // File file = await createFile('flt-ok.txt');
+ // final Reference ref = storage.ref('uploadNope.jpeg');
+
+ // await expectLater(
+ // () => ref.putFile(file),
+ // throwsA(isA()
+ // .having((e) => e.code, 'code', 'unauthorized')
+ // .having((e) => e.message, 'message',
+ // 'User is not authorized to perform the desired action.')));
+ // });
+ },
+ // putFile is not supported in web.
+ // iOS & macOS work locally but times out on CI. We ought to check this periodically
+ // as it may be OS version specific.
+ skip: kIsWeb ||
+ defaultTargetPlatform == TargetPlatform.iOS ||
+ defaultTargetPlatform == TargetPlatform.macOS,
+ );
+
+ group('putString', () {
+ test('uploads a string', () async {
+ final Reference ref = storage.ref('flutter-tests').child('flt-ok.txt');
+ final TaskSnapshot complete = await ref.putString('data');
+ expect(complete.totalBytes, greaterThan(0));
+ });
+
+ // Emulator continues to make request rather than throw unauthorized exception as expected
+ // test('errors if permission denied', () async {
+ // final Reference ref = storage.ref('uploadNope.jpeg');
+ //
+ // await expectLater(
+ // () => ref.putString('data'),
+ // throwsA(
+ // isA()
+ // .having((e) => e.code, 'code', 'unauthorized')
+ // .having(
+ // (e) => e.message,
+ // 'message',
+ // 'User is not authorized to perform the desired action.',
+ // ),
+ // ),
+ // );
+ // });
+ });
+
+ group('updateMetadata', () {
+ test('updates metadata', () async {
+ Reference ref = storage.ref('flutter-tests').child('flt-ok.txt');
+ FullMetadata fullMetadata = await ref
+ .updateMetadata(SettableMetadata(customMetadata: {'foo': 'bar'}));
+ expect(fullMetadata.customMetadata!['foo'], 'bar');
+ });
+
+ test('errors if property does not exist', () async {
+ Reference ref = storage.ref('flutter-tests/iDoNotExist.jpeg');
+
+ // -
+ await expectLater(
+ () => ref.updateMetadata(SettableMetadata(contentType: 'unknown')),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'unauthorized')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'User is not authorized to perform the desired action',
+ ),
+ ),
+ );
+ });
+
+ test(
+ 'errors if permission denied',
+ () async {
+ final ref = storage.ref('uploadNope.jpeg');
+ await expectLater(
+ () => ref.updateMetadata(SettableMetadata(contentType: 'jpeg')),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'unauthorized')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'User is not authorized to perform the desired action',
+ ),
+ ),
+ );
+ },
+ // TODO(salakar): Firebase storage emulator incorrectly returns `object-not-found` instead of `unauthorized`.
+ skip: true,
+ );
+ });
+
+ group(
+ 'writeToFile',
+ () {
+ test('downloads a file', () async {
+ File file = await createFile('ok.jpeg');
+ TaskSnapshot complete =
+ await storage.ref('flutter-tests/ok.txt').writeToFile(file);
+ expect(complete.bytesTransferred, complete.totalBytes);
+ expect(complete.state, TaskState.success);
+ });
+
+ test('errors if permission denied', () async {
+ File file = await createFile('not.jpeg');
+ final Reference ref = storage.ref('/nope.jpeg');
+
+ await expectLater(
+ () => ref.writeToFile(file),
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'unauthorized')
+ .having(
+ (e) => e.message,
+ 'message',
+ 'User is not authorized to perform the desired action',
+ ),
+ ),
+ );
+ // TODO(hosung): Firebase cpp sdk does not throw error.
+ }, skip: Platform.isLinux);
+ // writeToFile is not supported in web
+ },
+ skip: kIsWeb,
+ );
+
+ test('toString', () async {
+ expect(
+ storage.ref('/uploadNope.jpeg').toString(),
+ equals('Reference(app: [DEFAULT], fullPath: uploadNope.jpeg)'),
+ );
+ });
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/task_e2e.dart b/packages/firebase_storage/example/integration_test/task_e2e.dart
new file mode 100644
index 0000000..9f366c4
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/task_e2e.dart
@@ -0,0 +1,275 @@
+// Copyright 2022, 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:io';
+
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import './test_utils.dart';
+
+void setupTaskTests() {
+ group('Task', () {
+ late FirebaseStorage storage;
+ late File file;
+ late Reference uploadRef;
+ late Reference downloadRef;
+
+ setUpAll(() async {
+ storage = FirebaseStorage.instance;
+ uploadRef = storage.ref('flutter-tests').child('ok.txt');
+ downloadRef = storage.ref('flutter-tests/ok.txt'); // 15mb
+ });
+
+ group('pause() resume() onComplete()', () {
+ late Task? task;
+
+ setUp(() {
+ task = null;
+ });
+
+ Future _testPauseTask(String type) async {
+ List snapshots = [];
+ late FirebaseException streamError;
+ expect(task!.snapshot.state, TaskState.running);
+
+ task!.snapshotEvents.listen(
+ (TaskSnapshot snapshot) {
+ snapshots.add(snapshot);
+ },
+ onError: (error) {
+ streamError = error;
+ },
+ cancelOnError: true,
+ );
+
+ // TODO(Salakar): Known issue with iOS SDK where pausing immediately will cause an 'unknown' error.
+ if (defaultTargetPlatform == TargetPlatform.iOS) {
+ await task!.snapshotEvents.first;
+ await Future.delayed(const Duration(milliseconds: 750));
+ }
+
+ // TODO(Salakar): Known issue with iOS where pausing/resuming doesn't immediately return as paused/resumed 'true'.
+ if (defaultTargetPlatform != TargetPlatform.iOS) {
+ bool? paused = await task!.pause();
+ expect(paused, isTrue);
+ expect(task!.snapshot.state, TaskState.paused);
+
+ bool? resumed = await task!.resume();
+ expect(resumed, isTrue);
+ expect(task!.snapshot.state, TaskState.running);
+ }
+
+ TaskSnapshot? snapshot = await task;
+ expect(task!.snapshot.state, TaskState.success);
+ expect(snapshot!.state, TaskState.success);
+
+ expect(snapshot.totalBytes, snapshot.bytesTransferred);
+
+ expect(streamError, isNull);
+ // TODO(Salakar): Known issue with iOS where pausing/resuming doesn't immediately return as paused/resumed 'true'.
+ if (defaultTargetPlatform != TargetPlatform.iOS) {
+ expect(
+ snapshots,
+ anyElement(
+ predicate(
+ (TaskSnapshot element) => element.state == TaskState.paused,
+ ),
+ ),
+ );
+ expect(
+ snapshots,
+ anyElement(
+ predicate(
+ (TaskSnapshot element) => element.state == TaskState.running,
+ ),
+ ),
+ );
+ }
+ }
+
+ // TODO(Salakar): Test fails on emulator.
+ test(
+ 'successfully pauses and resumes a download task',
+ () async {
+ file = await createFile('ok.jpeg');
+ await downloadRef.writeToFile(file);
+ await _testPauseTask('Download');
+ // Skip on web: There's no DownloadTask on web.
+ },
+ skip: true,
+ );
+
+ // TODO(Salakar): Test is flaky on CI - needs investigating ('[firebase_storage/unknown] An unknown error occurred, please check the server response.')
+ test(
+ 'successfully pauses and resumes a upload task',
+ () async {
+ await uploadRef.putString('This is an upload task!');
+ await _testPauseTask('Upload');
+ },
+ skip: true,
+ );
+
+ //TODO(pr-mais): causes the emulator to crash
+ // test('handles errors, e.g. if permission denied', () async {
+ // late FirebaseException streamError;
+
+ // List list = utf8.encode('hello world');
+ // Uint8List data = Uint8List.fromList(list);
+ // UploadTask task = storage.ref('/uploadNope.jpeg').putData(data);
+
+ // expect(task.snapshot.state, TaskState.running);
+
+ // task.snapshotEvents.listen((TaskSnapshot snapshot) {
+ // // noop
+ // }, onError: (error) {
+ // streamError = error;
+ // }, cancelOnError: true);
+
+ // await expectLater(
+ // task,
+ // throwsA(isA()
+ // .having((e) => e.code, 'code', 'unauthorized')),
+ // );
+
+ // expect(streamError.plugin, 'firebase_storage');
+ // expect(streamError.code, 'unauthorized');
+ // expect(streamError.message,
+ // 'User is not authorized to perform the desired action.');
+
+ // expect(task.snapshot.state, TaskState.error);
+ // });
+ });
+
+ group('snapshot', () {
+ test(
+ 'returns the latest snapshot for download task',
+ () async {
+ file = await createFile('ok.jpeg');
+ final downloadTask = downloadRef.writeToFile(file);
+
+ expect(downloadTask.snapshot, isNotNull);
+
+ TaskSnapshot completedSnapshot = await downloadTask;
+ final snapshot = downloadTask.snapshot;
+
+ expect(snapshot, isA());
+ expect(snapshot.state, TaskState.success);
+ expect(snapshot.bytesTransferred, completedSnapshot.bytesTransferred);
+ expect(snapshot.totalBytes, completedSnapshot.totalBytes);
+ expect(snapshot.metadata, isNull);
+ },
+ // TODO(salakar): this test is flakey when using the Firebase Storage Emulator.
+ skip: true,
+ // There's no DownloadTask on web.
+ // skip: kIsWeb
+ retry: 2,
+ );
+
+ test(
+ 'returns the latest snapshot for upload task',
+ () async {
+ final uploadTask = uploadRef.putString('This is an upload task!');
+ expect(uploadTask.snapshot, isNotNull);
+
+ TaskSnapshot completedSnapshot = await uploadTask;
+ final snapshot = uploadTask.snapshot;
+ expect(snapshot, isA());
+ expect(snapshot.bytesTransferred, completedSnapshot.bytesTransferred);
+ expect(snapshot.totalBytes, completedSnapshot.totalBytes);
+ expect(snapshot.metadata, isA());
+ },
+ retry: 2,
+ // TODO(salakar): this test is flakey when using the Firebase Storage Emulator.
+ skip: true,
+ );
+
+ test(
+ 'upload task to a custom bucket',
+ () async {
+ String secondaryBucket = 'flutterfire-e2e-tests-two';
+ Reference storageReference =
+ FirebaseStorage.instanceFor(bucket: secondaryBucket)
+ .ref('flutter-tests/ok.txt');
+
+ expect(storageReference.bucket, secondaryBucket);
+
+ final task = storageReference.putString('test second bucket');
+ final snapshot = await task;
+
+ expect(snapshot.ref.bucket, secondaryBucket);
+
+ String url = await storageReference.getDownloadURL();
+
+ expect(url, contains('/$secondaryBucket/'));
+ },
+ // TODO(salakar): blocked by https://github.com/firebase/firebase-tools/issues/3390
+ skip: true,
+ );
+ });
+
+ // TODO(Salakar): Test fails on Firebase Storage emulator since the task completes before .cancel() has a chance to be called.
+ group(
+ 'cancel()',
+ () {
+ late Task task;
+
+ Future _testCancelTask() async {
+ List snapshots = [];
+ late FirebaseException streamError;
+ expect(task.snapshot.state, TaskState.running);
+
+ task.snapshotEvents.listen(
+ (TaskSnapshot snapshot) {
+ snapshots.add(snapshot);
+ },
+ onError: (error) {
+ streamError = error;
+ },
+ cancelOnError: true,
+ );
+
+ bool canceled = await task.cancel();
+ expect(canceled, isTrue);
+ expect(task.snapshot.state, TaskState.canceled);
+
+ await expectLater(
+ task,
+ throwsA(
+ isA()
+ .having((e) => e.code, 'code', 'canceled'),
+ ),
+ );
+
+ expect(task.snapshot.state, TaskState.canceled);
+
+ expect(streamError, isNotNull);
+ expect(streamError.code, 'canceled');
+ // Expecting there to only be running states, canceled should not get sent as an event.
+ expect(
+ snapshots.every((snapshot) => snapshot.state == TaskState.running),
+ isTrue,
+ );
+ }
+
+ test(
+ 'successfully cancels download task',
+ () async {
+ file = await createFile('ok.jpeg');
+ task = downloadRef.writeToFile(file);
+ await _testCancelTask();
+ // There's no DownloadTask on web.
+ },
+ );
+
+ test('successfully cancels upload task', () async {
+ task = uploadRef.putString('This is an upload task!');
+ await _testCancelTask();
+ });
+ },
+ skip: true,
+ );
+ });
+}
diff --git a/packages/firebase_storage/example/integration_test/test_utils.dart b/packages/firebase_storage/example/integration_test/test_utils.dart
new file mode 100644
index 0000000..f1d6a06
--- /dev/null
+++ b/packages/firebase_storage/example/integration_test/test_utils.dart
@@ -0,0 +1,75 @@
+// Copyright 2022, 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:io';
+import 'dart:math';
+
+import 'package:firebase_core/firebase_core.dart';
+import 'package:flutter/foundation.dart';
+
+import '../lib/firebase_options.dart';
+
+final String kTestString =
+ ([]..length = int.parse('${pow(2, 12)}')).join(_getRandomString(8)) * 100;
+const String kTestStorageBucket = 'flutterfire-e2e-tests.appspot.com';
+
+const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
+Random _random = Random();
+String _getRandomString(int length) => String.fromCharCodes(
+ Iterable.generate(
+ length,
+ (_) => _chars.codeUnitAt(_random.nextInt(_chars.length)),
+ ),
+ );
+
+String get testEmulatorHost {
+ if (defaultTargetPlatform == TargetPlatform.android && !kIsWeb) {
+ return '10.0.2.2';
+ }
+ return 'localhost';
+}
+
+const int testEmulatorPort = 9199;
+
+// Creates a test file with a specified name to
+// a locally directory
+Future createFile(String name) async {
+ final Directory systemTempDir = Directory.systemTemp;
+ final File file = await File('${systemTempDir.path}/$name').create();
+ await file.writeAsString(kTestString);
+ return file;
+}
+
+// Initializes a secondary app with or without a
+// default storageBucket value in FirebaseOptions for testing
+Future testInitializeSecondaryApp({
+ bool withDefaultBucket = true,
+}) async {
+ final String testAppName =
+ withDefaultBucket ? 'testapp' : 'testapp-no-bucket';
+
+ FirebaseOptions testAppOptions;
+ if (!kIsWeb && (Platform.isIOS || Platform.isMacOS)) {
+ testAppOptions = FirebaseOptions(
+ appId: DefaultFirebaseOptions.currentPlatform.appId,
+ apiKey: DefaultFirebaseOptions.currentPlatform.apiKey,
+ projectId: DefaultFirebaseOptions.currentPlatform.projectId,
+ messagingSenderId:
+ DefaultFirebaseOptions.currentPlatform.messagingSenderId,
+ iosBundleId: DefaultFirebaseOptions.currentPlatform.iosBundleId,
+ storageBucket: withDefaultBucket ? kTestStorageBucket : null,
+ );
+ } else {
+ testAppOptions = FirebaseOptions(
+ appId: DefaultFirebaseOptions.currentPlatform.appId,
+ apiKey: DefaultFirebaseOptions.currentPlatform.apiKey,
+ projectId: DefaultFirebaseOptions.currentPlatform.projectId,
+ messagingSenderId:
+ DefaultFirebaseOptions.currentPlatform.messagingSenderId,
+ storageBucket: withDefaultBucket ? kTestStorageBucket : null,
+ );
+ }
+
+ return Firebase.initializeApp(name: testAppName, options: testAppOptions);
+}
diff --git a/packages/firebase_storage/example/lib/firebase_options.dart b/packages/firebase_storage/example/lib/firebase_options.dart
new file mode 100644
index 0000000..97dffb5
--- /dev/null
+++ b/packages/firebase_storage/example/lib/firebase_options.dart
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023-present Samsung Electronics Co., Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
+import 'package:flutter/foundation.dart'
+ show defaultTargetPlatform, TargetPlatform;
+
+class DefaultFirebaseOptions {
+ static FirebaseOptions get currentPlatform {
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.linux:
+ // Note: To find out if you are using the Tizen platform, refer to the link below.
+ // https://github.com/flutter-tizen/flutter-tizen/issues/482#issuecomment-1441139704
+ return tizen;
+ default:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions are not supported for this platform.',
+ );
+ }
+ }
+
+ static const FirebaseOptions tizen = FirebaseOptions(
+ apiKey: 'PLACEHOLDER',
+ appId: 'PLACEHOLDER',
+ messagingSenderId: 'PLACEHOLDER',
+ projectId: 'PLACEHOLDER',
+ databaseURL: 'PLACEHOLDER',
+ storageBucket: 'PLACEHOLDER',
+ );
+}
diff --git a/packages/firebase_storage/example/lib/main.dart b/packages/firebase_storage/example/lib/main.dart
new file mode 100755
index 0000000..9df5d2a
--- /dev/null
+++ b/packages/firebase_storage/example/lib/main.dart
@@ -0,0 +1,386 @@
+// Copyright 2022, 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' as io;
+
+import 'package:firebase_core/firebase_core.dart';
+import 'package:firebase_storage/firebase_storage.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:image_picker/image_picker.dart';
+
+import 'firebase_options.dart';
+import 'save_as/save_as.dart';
+
+bool USE_FIRESTORAGE_EMULATOR = false;
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ if (USE_FIRESTORAGE_EMULATOR) {
+ final emulatorHost =
+ (!kIsWeb && defaultTargetPlatform == TargetPlatform.android)
+ ? '10.0.2.2'
+ : 'localhost';
+
+ await FirebaseStorage.instance.useStorageEmulator(emulatorHost, 9199);
+ }
+ runApp(StorageExampleApp());
+}
+
+/// Enum representing the upload task types the example app supports.
+enum UploadType {
+ /// Uploads a randomly generated string (as a file) to Storage.
+ string,
+
+ /// Uploads a file from the device.
+ file,
+
+ /// Clears any tasks from the list.
+ clear,
+
+ // Get uploaded files list.
+ list,
+}
+
+/// The entry point of the application.
+///
+/// Returns a [MaterialApp].
+class StorageExampleApp extends StatelessWidget {
+ StorageExampleApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Storage Example App',
+ theme: ThemeData.dark(),
+ home: Scaffold(
+ body: TaskManager(),
+ ),
+ );
+ }
+}
+
+/// A StatefulWidget which keeps track of the current uploaded files.
+class TaskManager extends StatefulWidget {
+ // ignore: public_member_api_docs
+ TaskManager({Key? key}) : super(key: key);
+
+ @override
+ State createState() {
+ return _TaskManager();
+ }
+}
+
+class _TaskManager extends State {
+ List _uploadTasks = [];
+
+ /// The user selects a file, and the task is added to the list.
+ Future uploadFile(XFile? file) async {
+ if (file == null) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('No file was selected'),
+ ),
+ );
+
+ return null;
+ }
+
+ UploadTask uploadTask;
+
+ // Create a Reference to the file
+ Reference ref = FirebaseStorage.instance
+ .ref()
+ .child('flutter-tests')
+ .child('/some-image.jpg');
+
+ final metadata = SettableMetadata(
+ contentType: 'image/jpeg',
+ customMetadata: {'picked-file-path': file.path},
+ );
+
+ if (kIsWeb) {
+ uploadTask = ref.putData(await file.readAsBytes(), metadata);
+ } else {
+ uploadTask = ref.putFile(io.File(file.path), metadata);
+ }
+
+ return Future.value(uploadTask);
+ }
+
+ /// A new string is uploaded to storage.
+ UploadTask uploadString() {
+ const String putStringText =
+ 'This upload has been generated using the putString method! Check the metadata too!';
+
+ // Create a Reference to the file
+ Reference ref = FirebaseStorage.instance
+ .ref()
+ .child('flutter-tests')
+ .child('/put-string-example.txt');
+
+ // Start upload of putString
+ return ref.putString(
+ putStringText,
+ metadata: SettableMetadata(
+ contentLanguage: 'en',
+ customMetadata: {'example': 'putString'},
+ ),
+ );
+ }
+
+ /// Handles the user pressing the PopupMenuItem item.
+ Future handleUploadType(UploadType type) async {
+ switch (type) {
+ case UploadType.string:
+ setState(() {
+ _uploadTasks = [..._uploadTasks, uploadString()];
+ });
+ break;
+ case UploadType.file:
+ final file = await ImagePicker().pickImage(source: ImageSource.gallery);
+ UploadTask? task = await uploadFile(file);
+
+ if (task != null) {
+ setState(() {
+ _uploadTasks = [..._uploadTasks, task];
+ });
+ }
+ break;
+ case UploadType.clear:
+ setState(() {
+ _uploadTasks = [];
+ });
+ break;
+ case UploadType.list:
+ Reference ref = FirebaseStorage.instance.ref().child('flutter-tests');
+ _getList(ref);
+ break;
+ }
+ }
+
+ void _removeTaskAtIndex(int index) {
+ setState(() {
+ _uploadTasks = _uploadTasks..removeAt(index);
+ });
+ }
+
+ Future _downloadBytes(Reference ref) async {
+ final bytes = await ref.getData();
+ // Download...
+ await saveAsBytes(bytes!, 'some-image.jpg');
+ }
+
+ Future _downloadLink(Reference ref) async {
+ final link = await ref.getDownloadURL();
+
+ await Clipboard.setData(
+ ClipboardData(
+ text: link,
+ ),
+ );
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text(
+ 'Success!\n Copied download URL to Clipboard!',
+ ),
+ ),
+ );
+ }
+
+ Future _downloadFile(Reference ref) async {
+ final io.Directory systemTempDir = io.Directory.systemTemp;
+ final io.File tempFile = io.File('${systemTempDir.path}/temp-${ref.name}');
+ if (tempFile.existsSync()) await tempFile.delete();
+
+ await ref.writeToFile(tempFile);
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ 'Success!\n Downloaded ${ref.name} \n from bucket: ${ref.bucket}\n '
+ 'at path: ${ref.fullPath} \n'
+ 'Wrote "${ref.fullPath}" to tmp-${ref.name}',
+ ),
+ ),
+ );
+ }
+
+ Future _getList(Reference ref) async {
+ ListResult result = await ref.list(ListOptions(maxResults: 3));
+ final itemsStringBuffer = StringBuffer();
+ result.items.forEach((item) {
+ itemsStringBuffer.writeln(item.fullPath);
+ });
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ itemsStringBuffer.toString(),
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Storage Example App'),
+ actions: [
+ PopupMenuButton(
+ onSelected: handleUploadType,
+ icon: const Icon(Icons.add),
+ itemBuilder: (context) => [
+ const PopupMenuItem(
+ // ignore: sort_child_properties_last
+ child: Text('Upload string'),
+ value: UploadType.string,
+ ),
+ const PopupMenuItem(
+ // ignore: sort_child_properties_last
+ child: Text('Upload local file'),
+ value: UploadType.file,
+ ),
+ if (_uploadTasks.isNotEmpty)
+ const PopupMenuItem(
+ // ignore: sort_child_properties_last
+ child: Text('Clear list'),
+ value: UploadType.clear,
+ ),
+ PopupMenuDivider(),
+ const PopupMenuItem(
+ child: Text('List'),
+ value: UploadType.list,
+ ),
+ ],
+ )
+ ],
+ ),
+ body: _uploadTasks.isEmpty
+ ? const Center(child: Text("Press the '+' button to add a new file."))
+ : ListView.builder(
+ itemCount: _uploadTasks.length,
+ itemBuilder: (context, index) => UploadTaskListTile(
+ task: _uploadTasks[index],
+ onDismissed: () => _removeTaskAtIndex(index),
+ onDownloadLink: () async {
+ return _downloadLink(_uploadTasks[index].snapshot.ref);
+ },
+ onDownload: () async {
+ if (kIsWeb) {
+ return _downloadBytes(_uploadTasks[index].snapshot.ref);
+ } else {
+ return _downloadFile(_uploadTasks[index].snapshot.ref);
+ }
+ },
+ ),
+ ),
+ );
+ }
+}
+
+/// Displays the current state of a single UploadTask.
+class UploadTaskListTile extends StatelessWidget {
+ // ignore: public_member_api_docs
+ const UploadTaskListTile({
+ Key? key,
+ required this.task,
+ required this.onDismissed,
+ required this.onDownload,
+ required this.onDownloadLink,
+ }) : super(key: key);
+
+ /// The [UploadTask].
+ final UploadTask /*!*/ task;
+
+ /// Triggered when the user dismisses the task from the list.
+ final VoidCallback /*!*/ onDismissed;
+
+ /// Triggered when the user presses the download button on a completed upload task.
+ final VoidCallback /*!*/ onDownload;
+
+ /// Triggered when the user presses the "link" button on a completed upload task.
+ final VoidCallback /*!*/ onDownloadLink;
+
+ /// Displays the current transferred bytes of the task.
+ String _bytesTransferred(TaskSnapshot snapshot) {
+ return '${snapshot.bytesTransferred}/${snapshot.totalBytes}';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder(
+ stream: task.snapshotEvents,
+ builder: (
+ BuildContext context,
+ AsyncSnapshot asyncSnapshot,
+ ) {
+ Widget subtitle = const Text('---');
+ TaskSnapshot? snapshot = asyncSnapshot.data;
+ TaskState? state = snapshot?.state;
+
+ if (asyncSnapshot.hasError) {
+ if (asyncSnapshot.error is FirebaseException &&
+ // ignore: cast_nullable_to_non_nullable
+ (asyncSnapshot.error as FirebaseException).code == 'canceled') {
+ subtitle = const Text('Upload canceled.');
+ } else {
+ // ignore: avoid_print
+ print(asyncSnapshot.error);
+ subtitle = const Text('Something went wrong.');
+ }
+ } else if (snapshot != null) {
+ subtitle = Text('$state: ${_bytesTransferred(snapshot)} bytes sent');
+ }
+
+ return Dismissible(
+ key: Key(task.hashCode.toString()),
+ onDismissed: ($) => onDismissed(),
+ child: ListTile(
+ title: Text('Upload Task #${task.hashCode}'),
+ subtitle: subtitle,
+ trailing: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (state == TaskState.running)
+ IconButton(
+ icon: const Icon(Icons.pause),
+ onPressed: task.pause,
+ ),
+ if (state == TaskState.running)
+ IconButton(
+ icon: const Icon(Icons.cancel),
+ onPressed: task.cancel,
+ ),
+ if (state == TaskState.paused)
+ IconButton(
+ icon: const Icon(Icons.file_upload),
+ onPressed: task.resume,
+ ),
+ if (state == TaskState.success)
+ IconButton(
+ icon: const Icon(Icons.file_download),
+ onPressed: onDownload,
+ ),
+ if (state == TaskState.success)
+ IconButton(
+ icon: const Icon(Icons.link),
+ onPressed: onDownloadLink,
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/packages/firebase_storage/example/lib/save_as/save_as.dart b/packages/firebase_storage/example/lib/save_as/save_as.dart
new file mode 100644
index 0000000..c126e02
--- /dev/null
+++ b/packages/firebase_storage/example/lib/save_as/save_as.dart
@@ -0,0 +1,5 @@
+// Copyright 2022, 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.
+
+export 'save_as_interface.dart' if (dart.library.html) 'save_as_html.dart';
diff --git a/packages/firebase_storage/example/lib/save_as/save_as_html.dart b/packages/firebase_storage/example/lib/save_as/save_as_html.dart
new file mode 100644
index 0000000..8ef65bc
--- /dev/null
+++ b/packages/firebase_storage/example/lib/save_as/save_as_html.dart
@@ -0,0 +1,49 @@
+// Copyright 2022, 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.
+
+// ignore: avoid_web_libraries_in_flutter
+import 'dart:html';
+import 'dart:typed_data';
+
+/// Initializes a DOM container where we can host elements.
+Element _ensureInitialized(String id) {
+ var target = querySelector('#$id');
+ if (target == null) {
+ final Element targetElement = Element.tag('flt-x-file')..id = id;
+
+ querySelector('body')?.children.add(targetElement);
+ target = targetElement;
+ }
+ return target;
+}
+
+AnchorElement _createAnchorElement(String href, String suggestedName) {
+ return AnchorElement(href: href)..download = suggestedName;
+}
+
+/// Add an element to a container and click it
+void _addElementToContainerAndClick(Element container, Element element) {
+ // Add the element and click it
+ // All previous elements will be removed before adding the new one
+ container.children.add(element);
+ element.click();
+}
+
+/// Present a dialog so the user can save as... a bunch of bytes.
+Future saveAsBytes(Uint8List bytes, String suggestedName) async {
+ // Convert bytes to an ObjectUrl through Blob
+ final blob = Blob([bytes]);
+ final path = Url.createObjectUrl(blob);
+
+ // Create a DOM container where we can host the anchor.
+ final target = _ensureInitialized('__x_file_dom_element');
+
+ // Create an tag with the appropriate download attributes and click it
+ // May be overridden with XFileTestOverrides
+ final AnchorElement element = _createAnchorElement(path, suggestedName);
+
+ // Clear the children in our container so we can add an element to click
+ target.children.clear();
+ _addElementToContainerAndClick(target, element);
+}
diff --git a/packages/firebase_storage/example/lib/save_as/save_as_interface.dart b/packages/firebase_storage/example/lib/save_as/save_as_interface.dart
new file mode 100644
index 0000000..e4d8a5a
--- /dev/null
+++ b/packages/firebase_storage/example/lib/save_as/save_as_interface.dart
@@ -0,0 +1,10 @@
+// Copyright 2022, 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:typed_data';
+
+/// Present a dialog so the user can save as... a bunch of bytes.
+Future saveAsBytes(Uint8List bytes, String suggestedName) async {
+ return;
+}
diff --git a/packages/firebase_storage/example/pubspec.yaml b/packages/firebase_storage/example/pubspec.yaml
new file mode 100644
index 0000000..154cc1d
--- /dev/null
+++ b/packages/firebase_storage/example/pubspec.yaml
@@ -0,0 +1,34 @@
+name: firebase_storage_tizen_example
+description: Demonstrates how to use the firebase_storage plugin.
+
+environment:
+ sdk: ">=2.18.0 <4.0.0"
+ flutter: ">=3.3.0"
+
+dependencies:
+ firebase_core: 2.4.1
+ firebase_core_tizen: ^1.0.0
+ firebase_storage: ^11.0.10
+ firebase_storage_tizen: ^0.1.0
+ flutter:
+ sdk: flutter
+ image_picker: ^0.8.6
+ image_picker_tizen: ^2.2.0
+
+dependency_overrides:
+ firebase_core_tizen:
+ path: ../../firebase_core
+ firebase_storage_tizen:
+ path: ../
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ integration_test:
+ sdk: flutter
+ integration_test_tizen: ^2.0.1
+
+flutter:
+ uses-material-design: true
+ assets:
+ - assets/hello.txt
diff --git a/packages/firebase_storage/example/tizen/.gitignore b/packages/firebase_storage/example/tizen/.gitignore
new file mode 100644
index 0000000..750f3af
--- /dev/null
+++ b/packages/firebase_storage/example/tizen/.gitignore
@@ -0,0 +1,5 @@
+flutter/
+.vs/
+*.user
+bin/
+obj/
diff --git a/packages/firebase_storage/example/tizen/App.cs b/packages/firebase_storage/example/tizen/App.cs
new file mode 100644
index 0000000..6dd4a63
--- /dev/null
+++ b/packages/firebase_storage/example/tizen/App.cs
@@ -0,0 +1,20 @@
+ο»Ώusing Tizen.Flutter.Embedding;
+
+namespace Runner
+{
+ public class App : FlutterApplication
+ {
+ protected override void OnCreate()
+ {
+ base.OnCreate();
+
+ GeneratedPluginRegistrant.RegisterPlugins(this);
+ }
+
+ static void Main(string[] args)
+ {
+ var app = new App();
+ app.Run(args);
+ }
+ }
+}
diff --git a/packages/firebase_storage/example/tizen/Runner.csproj b/packages/firebase_storage/example/tizen/Runner.csproj
new file mode 100644
index 0000000..f4e369d
--- /dev/null
+++ b/packages/firebase_storage/example/tizen/Runner.csproj
@@ -0,0 +1,19 @@
+ο»Ώ
+
+
+ Exe
+ tizen40
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)
+
+
+
+
diff --git a/packages/firebase_storage/example/tizen/shared/res/ic_launcher.png b/packages/firebase_storage/example/tizen/shared/res/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/packages/firebase_storage/example/tizen/shared/res/ic_launcher.png differ
diff --git a/packages/firebase_storage/example/tizen/tizen-manifest.xml b/packages/firebase_storage/example/tizen/tizen-manifest.xml
new file mode 100644
index 0000000..d1ac0d8
--- /dev/null
+++ b/packages/firebase_storage/example/tizen/tizen-manifest.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ ic_launcher.png
+
+
+
+ T-INFOLINK2021-1000
+
+
+
+ http://tizen.org/privilege/internet
+ http://tizen.org/privilege/mediastorage
+ http://tizen.org/privilege/appmanager.launch
+
+
diff --git a/packages/firebase_storage/pubspec.yaml b/packages/firebase_storage/pubspec.yaml
new file mode 100644
index 0000000..56568a9
--- /dev/null
+++ b/packages/firebase_storage/pubspec.yaml
@@ -0,0 +1,30 @@
+name: firebase_storage_tizen
+description: Flutter plugin for Firebase Cloud Storage, a powerful, simple, and
+ cost-effective object storage service for Tizen.
+version: 0.1.0
+homepage:
+
+environment:
+ sdk: ">=2.18.0 <4.0.0"
+ flutter: ">=3.3.0"
+
+dependencies:
+ firebase_core_tizen: ^1.0.0
+ firebase_storage: ^11.0.10
+ flutter:
+ sdk: flutter
+
+dependency_overrides:
+ firebase_core_tizen:
+ path: ../firebase_core
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ plugin:
+ platforms:
+ tizen:
+ pluginClass: FirebaseStorageTizenPlugin
+ fileName: firebase_storage_tizen_plugin.h
diff --git a/packages/firebase_storage/tizen/.gitignore b/packages/firebase_storage/tizen/.gitignore
new file mode 100644
index 0000000..a2a7d62
--- /dev/null
+++ b/packages/firebase_storage/tizen/.gitignore
@@ -0,0 +1,5 @@
+.cproject
+.sign
+crash-info/
+Debug/
+Release/
diff --git a/packages/firebase_storage/tizen/build_def.prop b/packages/firebase_storage/tizen/build_def.prop
new file mode 100644
index 0000000..085fff2
--- /dev/null
+++ b/packages/firebase_storage/tizen/build_def.prop
@@ -0,0 +1,2 @@
+PREBUILD_COMMAND = ./tar_url.sh \
+ https://raw.githubusercontent.com/hs0225/download/firebase-sdk/firebaseSDK-tizen-1.0.1.tar.gz
diff --git a/packages/firebase_storage/tizen/inc/firebase_storage_tizen_plugin.h b/packages/firebase_storage/tizen/inc/firebase_storage_tizen_plugin.h
new file mode 100644
index 0000000..abd7ec4
--- /dev/null
+++ b/packages/firebase_storage/tizen/inc/firebase_storage_tizen_plugin.h
@@ -0,0 +1,23 @@
+#ifndef FLUTTER_PLUGIN_FIREBASE_STORAGE_TIZEN_PLUGIN_H_
+#define FLUTTER_PLUGIN_FIREBASE_STORAGE_TIZEN_PLUGIN_H_
+
+#include
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
+#else
+#define FLUTTER_PLUGIN_EXPORT
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+FLUTTER_PLUGIN_EXPORT void FirebaseStorageTizenPluginRegisterWithRegistrar(
+ FlutterDesktopPluginRegistrarRef registrar);
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+#endif // FLUTTER_PLUGIN_FIREBASE_STORAGE_TIZEN_PLUGIN_H_
diff --git a/packages/firebase_storage/tizen/project_def.prop b/packages/firebase_storage/tizen/project_def.prop
new file mode 100644
index 0000000..56ef1b9
--- /dev/null
+++ b/packages/firebase_storage/tizen/project_def.prop
@@ -0,0 +1,30 @@
+# See https://docs.tizen.org/application/tizen-studio/native-tools/project-conversion
+# for details.
+
+APPNAME = firebase_storage_tizen_plugin
+type = sharedLib
+profile = common-7.0
+
+# Source files
+USER_SRCS += src/*.cc
+
+# User defines
+USER_DEFS =
+USER_UNDEFS =
+USER_CPP_DEFS = FLUTTER_PLUGIN_IMPL
+USER_CPP_UNDEFS =
+
+# Custom defines
+FIREBASE_SDK_DIR = $(subst $() ,\ ,$(FLUTTER_BUILD_DIR))/.firebaseSDK
+FIREBASE_INC_DIR = $(FIREBASE_SDK_DIR)/inc
+FIREBASE_LIB_DIR = $(FIREBASE_SDK_DIR)/lib/$(BUILD_ARCH)
+
+# User includes
+USER_INC_DIRS = inc src $(FIREBASE_INC_DIR)
+USER_INC_FILES =
+USER_CPP_INC_FILES =
+
+# User libs
+USER_LIBS = firebase_app firebase_storage
+USER_LIB_DIRS = lib/$(BUILD_ARCH) $(FIREBASE_LIB_DIR)
+USER_LFLAGS = -Wl,-rpath='$$ORIGIN'
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_error.cc b/packages/firebase_storage/tizen/src/firebase_storage_error.cc
new file mode 100644
index 0000000..3dcf1b5
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_error.cc
@@ -0,0 +1,77 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "firebase_storage_error.h"
+
+std::string FirebaseStorageError::GetCodeString() const {
+ switch (code_) {
+ case Code::kObjectNotFound:
+ return "object-not-found";
+ case Code::kBucketNotFound:
+ return "bucket-not-found";
+ case Code::kProjectNotFound:
+ return "project-not-found";
+ case Code::kQuotaExceeded:
+ return "quota-exceeded";
+ case Code::kUnauthenticated:
+ return "unauthenticated";
+ case Code::kUnauthorized:
+ return "unauthorized";
+ case Code::kRetryLimitExceeded:
+ return "retry-limit-exceeded";
+ case Code::kNonMatchingChecksum:
+ return "invalid-checksum";
+ case Code::kDownloadSizeExceeded:
+ return "download-size-exceeded";
+ case Code::kCancelled:
+ return "canceled";
+ case Code::kInvalidArgument:
+ return "invalid-argument";
+ case Code::kAppNotFound:
+ return "app-not-found";
+ case Code::kNotSupported:
+ return "not-supported";
+ case Code::kInvalidString:
+ return "invalid-string";
+ case Code::kSystemError:
+ return "system-error";
+ case Code::KTaskNotFound:
+ return "task-not-found";
+ case Code::kUnknown:
+ default:
+ return "unknown";
+ }
+}
+
+std::string FirebaseStorageError::GetErrorString() const {
+ switch (code_) {
+ case Code::kUnknown:
+ case Code::kObjectNotFound:
+ case Code::kBucketNotFound:
+ case Code::kProjectNotFound:
+ case Code::kQuotaExceeded:
+ case Code::kUnauthenticated:
+ case Code::kUnauthorized:
+ case Code::kRetryLimitExceeded:
+ case Code::kNonMatchingChecksum:
+ case Code::kDownloadSizeExceeded:
+ case Code::kCancelled:
+ return firebase::storage::GetErrorMessage(
+ firebase::storage::Error(code_));
+ case Code::kInvalidArgument:
+ return "Invalid argument.";
+ case Code::kAppNotFound:
+ return "No app is configured with that name.";
+ case Code::kNotSupported:
+ return "The feature is not supported.";
+ case Code::kInvalidString:
+ return "The string contains invalid characters.";
+ case Code::kSystemError:
+ return "A system error occurred.";
+ case Code::KTaskNotFound:
+ return "A task does not exist.";
+ default:
+ return "An unknown error occurred.";
+ }
+}
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_error.h b/packages/firebase_storage/tizen/src/firebase_storage_error.h
new file mode 100644
index 0000000..fb38128
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_error.h
@@ -0,0 +1,68 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_PLUGIN_FIREBASE_STORAGE_ERROR_H_
+#define FLUTTER_PLUGIN_FIREBASE_STORAGE_ERROR_H_
+
+#include
+
+#include
+
+using namespace firebase::storage;
+
+class FirebaseStorageError {
+ public:
+ enum class Code {
+ kUnknown = Error::kErrorUnknown,
+ kObjectNotFound = Error::kErrorObjectNotFound,
+ kBucketNotFound = Error::kErrorBucketNotFound,
+ kProjectNotFound = Error::kErrorProjectNotFound,
+ kQuotaExceeded = Error::kErrorQuotaExceeded,
+ kUnauthenticated = Error::kErrorUnauthenticated,
+ kUnauthorized = Error::kErrorUnauthorized,
+ kRetryLimitExceeded = Error::kErrorRetryLimitExceeded,
+ kNonMatchingChecksum = Error::kErrorNonMatchingChecksum,
+ kDownloadSizeExceeded = Error::kErrorDownloadSizeExceeded,
+ kCancelled = Error::kErrorCancelled,
+ kInvalidArgument = 20,
+ kAppNotFound,
+ kNotSupported,
+ kInvalidString,
+ kSystemError,
+ KTaskNotFound,
+ };
+
+ FirebaseStorageError(Code code, const std::string& message)
+ : code_(code), message_(message) {}
+
+ FirebaseStorageError(Code code) : code_(code), message_(GetErrorString()) {}
+
+ FirebaseStorageError(int code)
+ : code_(FirebaseStorageError::Code(code)), message_(GetErrorString()) {}
+
+ FirebaseStorageError(const FirebaseStorageError& other) {
+ this->code_ = other.code_;
+ this->message_ = other.message_;
+ }
+
+ ~FirebaseStorageError() = default;
+
+ FirebaseStorageError& operator=(const FirebaseStorageError& other) {
+ this->code_ = other.code_;
+ this->message_ = other.message_;
+ return *this;
+ }
+
+ Code GetCode() const { return code_; }
+ std::string GetMessage() const { return message_; }
+ std::string GetCodeString() const;
+
+ private:
+ Code code_;
+ std::string message_;
+
+ std::string GetErrorString() const;
+};
+
+#endif // FLUTTER_PLUGIN_FIREBASE_STORAGE_ERROR_H_
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_listener.cc b/packages/firebase_storage/tizen/src/firebase_storage_listener.cc
new file mode 100644
index 0000000..2ca5cc7
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_listener.cc
@@ -0,0 +1,29 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "firebase_storage_listener.h"
+
+#include
+
+#include
+
+StorageListener::StorageListener(const utils::StorageTaskData& task_data,
+ const std::shared_ptr channel)
+ : task_data_(task_data), channel_(std::move(channel)) {}
+
+void StorageListener::OnPaused(firebase::storage::Controller* controller) {
+ channel_->InvokeMethod(
+ "Task#onPaused",
+ std::make_unique(
+ utils::GetTaskEventValue(task_data_, controller->bytes_transferred(),
+ controller->total_byte_count())));
+}
+
+void StorageListener::OnProgress(firebase::storage::Controller* controller) {
+ channel_->InvokeMethod(
+ "Task#onProgress",
+ std::make_unique(
+ utils::GetTaskEventValue(task_data_, controller->bytes_transferred(),
+ controller->total_byte_count())));
+}
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_listener.h b/packages/firebase_storage/tizen/src/firebase_storage_listener.h
new file mode 100644
index 0000000..bdd040c
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_listener.h
@@ -0,0 +1,27 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_PLUGIN_FIREBASE_STORAGE_LISTENER_H_
+#define FLUTTER_PLUGIN_FIREBASE_STORAGE_LISTENER_H_
+
+#include "firebase/storage.h"
+#include "firebase_storage_utils.h"
+#include "flutter_types.hpp"
+
+class StorageListener : public firebase::storage::Listener {
+ public:
+ StorageListener(const utils::StorageTaskData& task_data,
+ const std::shared_ptr channel);
+
+ void OnPaused(firebase::storage::Controller* controller) override;
+ void OnProgress(firebase::storage::Controller* controller) override;
+
+ const utils::StorageTaskData& GetStorageTaskData() { return task_data_; }
+
+ private:
+ utils::StorageTaskData task_data_;
+ std::shared_ptr channel_;
+};
+
+#endif
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_task.cc b/packages/firebase_storage/tizen/src/firebase_storage_task.cc
new file mode 100644
index 0000000..65b8775
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_task.cc
@@ -0,0 +1,186 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "firebase_storage_task.h"
+
+#include
+
+#include
+#include
+#include
+
+#include "firebase/app.h"
+#include "firebase/storage.h"
+#include "firebase_storage_error.h"
+#include "log.h"
+
+StorageTaskHandler& StorageTaskHandler::GetInstance() {
+ static StorageTaskHandler instance;
+ return instance;
+}
+
+void StorageTaskHandler::AddTask(int handle,
+ std::unique_ptr task) {
+ std::lock_guard lock(mutex_);
+ tasks_[handle] = std::move(task);
+}
+
+std::optional StorageTaskHandler::GetTask(int handle) {
+ std::lock_guard lock(mutex_);
+ auto it = tasks_.find(handle);
+ if (it == tasks_.end()) {
+ return std::nullopt;
+ }
+ return (it->second).get();
+}
+
+void StorageTaskHandler::RemoveTask(int handle) {
+ std::lock_guard lock(mutex_);
+ tasks_.erase(handle);
+}
+
+StorageTask::StorageTask(Type type,
+ const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : type_(type), channel_(std::move(channel)), method_args_(std::move(args)) {
+ auto path = method_args_->GetRequiredArg("path");
+
+ storage_reference_ =
+ utils::GetStorage(method_args_.get())->GetReference(path);
+ utils::StorageTaskData task_data{
+ method_args_->GetRequiredArg("handle"),
+ method_args_->GetRequiredArg("appName"), path,
+ storage_reference_.bucket()};
+ listener_ = std::make_unique(task_data, channel);
+}
+
+const char* StorageTask::GetTaskName() {
+ switch (type_) {
+ case kPutData:
+ return "Put-Data";
+ case kPutString:
+ return "Put-String";
+ case kPutFile:
+ return "Put-File";
+ case kWriteToFile:
+ return "Write-To-File";
+ case kNone:
+ default:
+ return "None";
+ }
+}
+
+void StorageTask::Complete() {
+ StorageTaskHandler::GetInstance().RemoveTask(GetHandle());
+}
+
+void StorageTask::Success(const flutter::EncodableValue& result) {
+ channel_->InvokeMethod("Task#onSuccess",
+ std::make_unique(result));
+}
+
+void StorageTask::Fail(const flutter::EncodableValue& result,
+ const char* error_message) {
+ LOG_ERROR("Fail %s: %s", GetTaskName(), error_message);
+
+ channel_->InvokeMethod("Task#onFailure",
+ std::make_unique(result));
+}
+
+void StoragePutTask::Run() {
+ RunTaskImpl().OnCompletion(
+ [](const firebase::Future& metadata,
+ void* userdata) {
+ auto task = static_cast(userdata);
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ task->Success(utils::GetPutTaskSuccessEventValue(
+ task->GetStorageTaskData(), metadata.result()));
+ } else {
+ task->Fail(utils::GetTaskErrorEventValue(task->GetStorageTaskData(),
+ metadata.error(),
+ metadata.error_message()),
+ metadata.error_message());
+ }
+
+ task->Complete();
+ },
+ this);
+}
+
+firebase::Future
+StoragePutDataTask::RunTaskImpl() {
+ buffer_ = method_args_->GetRequiredArg>("data");
+
+ auto metadata_value = method_args_->GetArg("metadata");
+ if (metadata_value.has_value()) {
+ return storage_reference_.PutBytes(
+ buffer_.data(), buffer_.size(),
+ utils::ParseMetadata(metadata_value.value()), GetListener(),
+ GetController());
+ } else {
+ return storage_reference_.PutBytes(buffer_.data(), buffer_.size(),
+ GetListener(), GetController());
+ }
+}
+
+firebase::Future
+StoragePutStringTask::RunTaskImpl() {
+ auto format = method_args_->GetRequiredArg("format");
+ auto data = method_args_->GetRequiredArg("data");
+
+ if (!utils::StringToByteData(data, &buffer_, format)) {
+ throw FirebaseStorageError(FirebaseStorageError::Code::kInvalidString,
+ "Fail to decode the input string.");
+ }
+
+ auto metadata_value = method_args_->GetArg("metadata");
+ if (metadata_value.has_value()) {
+ return storage_reference_.PutBytes(
+ buffer_.data(), buffer_.size(),
+ utils::ParseMetadata(metadata_value.value()), GetListener(),
+ GetController());
+ } else {
+ return storage_reference_.PutBytes(buffer_.data(), buffer_.size(),
+ GetListener(), GetController());
+ }
+}
+
+firebase::Future
+StoragePutFileTask::RunTaskImpl() {
+ auto file_path = method_args_->GetRequiredArg("filePath");
+
+ auto metadata_value = method_args_->GetArg("metadata");
+ if (metadata_value.has_value()) {
+ return storage_reference_.PutFile(
+ file_path.data(), utils::ParseMetadata(metadata_value.value()),
+ GetListener(), GetController());
+ } else {
+ return storage_reference_.PutFile(file_path.data(), GetListener(),
+ GetController());
+ }
+}
+
+void StorageWriteToFileTask::Run() {
+ auto file_path = method_args_->GetRequiredArg("filePath");
+
+ storage_reference_.GetFile(file_path.data(), GetListener(), GetController())
+ .OnCompletion(
+ [](const firebase::Future& metadata, void* userdata) {
+ auto task = static_cast(userdata);
+
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ task->Success(utils::GetTaskEventValue(task->GetStorageTaskData(),
+ *metadata.result(),
+ *metadata.result()));
+ } else {
+ task->Fail(utils::GetTaskErrorEventValue(
+ task->GetStorageTaskData(), metadata.error(),
+ metadata.error_message()),
+ metadata.error_message());
+ }
+
+ task->Complete();
+ },
+ this);
+}
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_task.h b/packages/firebase_storage/tizen/src/firebase_storage_task.h
new file mode 100644
index 0000000..855fe3e
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_task.h
@@ -0,0 +1,159 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_PLUGIN_FIREBASE_STORAGE_TASK_H_
+#define FLUTTER_PLUGIN_FIREBASE_STORAGE_TASK_H_
+
+#include
+
+#include
+#include
+
+#include "firebase_storage_listener.h"
+#include "firebase_storage_utils.h"
+#include "flutter_types.hpp"
+
+class StorageTask;
+
+class StorageTaskHandler {
+ public:
+ static StorageTaskHandler& GetInstance();
+
+ void AddTask(int handle, std::unique_ptr task);
+
+ std::optional GetTask(int handle);
+
+ void RemoveTask(int handle);
+
+ private:
+ StorageTaskHandler() = default;
+ StorageTaskHandler(const StorageTaskHandler&) = delete;
+ void operator=(const StorageTaskHandler&) = delete;
+
+ std::unordered_map> tasks_;
+ std::mutex mutex_;
+};
+
+class StorageTask {
+ public:
+ enum Type {
+ kNone,
+ kPutData,
+ kPutString,
+ kPutFile,
+ kWriteToFile,
+ };
+
+ virtual ~StorageTask() = default;
+
+ template
+ static T* Create(const std::shared_ptr channel,
+ std::unique_ptr&& args) {
+ auto task = std::make_unique(channel, std::move(args));
+ auto instance = task.get();
+ StorageTaskHandler::GetInstance().AddTask(task->GetHandle(),
+ std::move(task));
+ return instance;
+ }
+
+ const int GetHandle() { return listener_->GetStorageTaskData().handle; }
+
+ std::string GetAppName() { return listener_->GetStorageTaskData().app_name; }
+
+ std::string GetPath() { return listener_->GetStorageTaskData().path; }
+
+ std::string GetBucket() { return listener_->GetStorageTaskData().bucket; }
+
+ const utils::StorageTaskData& GetStorageTaskData() {
+ return listener_->GetStorageTaskData();
+ }
+
+ firebase::storage::Controller* GetController() { return &controller_; }
+
+ StorageListener* GetListener() { return listener_.get(); }
+
+ const char* GetTaskName();
+
+ FlMethodChannel* GetMethodChannel() { return channel_.get(); }
+
+ void Complete();
+
+ void Success(const flutter::EncodableValue& result);
+
+ void Fail(const flutter::EncodableValue& result, const char* error_message);
+
+ virtual void Run() = 0;
+
+ protected:
+ StorageTask(Type type, const std::shared_ptr channel,
+ std::unique_ptr&& args);
+
+ Type type_;
+ std::shared_ptr channel_;
+ std::unique_ptr method_args_;
+
+ firebase::storage::StorageReference storage_reference_;
+ firebase::storage::Controller controller_;
+ std::unique_ptr listener_;
+};
+
+class StoragePutTask : public StorageTask {
+ public:
+ StoragePutTask(Type type, const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : StorageTask(type, channel, std::move(args)) {}
+
+ void Run() override;
+ virtual firebase::Future RunTaskImpl() = 0;
+};
+
+class StoragePutDataTask final : public StoragePutTask {
+ public:
+ StoragePutDataTask(const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : StoragePutTask(kPutData, channel, std::move(args)) {}
+
+ virtual ~StoragePutDataTask() = default;
+
+ firebase::Future RunTaskImpl() override;
+
+ private:
+ std::vector buffer_;
+};
+
+class StoragePutStringTask final : public StoragePutTask {
+ public:
+ StoragePutStringTask(const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : StoragePutTask(kPutString, channel, std::move(args)) {}
+
+ virtual ~StoragePutStringTask() = default;
+
+ firebase::Future RunTaskImpl() override;
+
+ private:
+ std::string buffer_;
+};
+
+class StoragePutFileTask final : public StoragePutTask {
+ public:
+ StoragePutFileTask(const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : StoragePutTask(kPutFile, channel, std::move(args)) {}
+
+ virtual ~StoragePutFileTask() = default;
+
+ firebase::Future RunTaskImpl() override;
+};
+
+class StorageWriteToFileTask final : public StorageTask {
+ public:
+ StorageWriteToFileTask(const std::shared_ptr channel,
+ std::unique_ptr&& args)
+ : StorageTask(kWriteToFile, channel, std::move(args)) {}
+
+ void Run() override;
+};
+
+#endif
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_tizen_plugin.cc b/packages/firebase_storage/tizen/src/firebase_storage_tizen_plugin.cc
new file mode 100644
index 0000000..27e2d71
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_tizen_plugin.cc
@@ -0,0 +1,293 @@
+#include "firebase_storage_tizen_plugin.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "firebase_storage_error.h"
+#include "firebase_storage_task.h"
+#include "flutter_types.hpp"
+#include "log.h"
+
+namespace {
+
+class StorageReferenceWork {
+ public:
+ StorageReferenceWork(std::unique_ptr&& args,
+ std::unique_ptr&& result)
+ : reference_(utils::GetStorageReference(args.get())),
+ method_call_args_(std::move(args)),
+ method_result_(std::move(result)) {}
+
+ firebase::storage::StorageReference* GetStorageReference() {
+ return &reference_;
+ }
+
+ MethodCallArguments* GetMethodCallArguments() {
+ return method_call_args_.get();
+ }
+
+ void Success(const flutter::EncodableValue& result) {
+ method_result_->Success(result);
+ }
+
+ void Success() { method_result_->Success(); }
+
+ void Fail(int error_code) {
+ FirebaseStorageError error(error_code);
+ auto code = error.GetCodeString();
+ auto message = error.GetMessage();
+
+ method_result_->Error(
+ code, message,
+ flutter::EncodableValue(flutter::EncodableMap{
+ {flutter::EncodableValue("code"), flutter::EncodableValue(code)},
+ {flutter::EncodableValue("message"),
+ flutter::EncodableValue(message)}}));
+ }
+
+ private:
+ firebase::storage::StorageReference reference_;
+ std::unique_ptr method_call_args_;
+ std::unique_ptr method_result_;
+};
+
+class FirebaseStorageTizenPlugin : public flutter::Plugin {
+ public:
+ static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar) {
+ auto channel = std::make_shared(
+ registrar->messenger(), "plugins.flutter.io/firebase_storage",
+ &flutter::StandardMethodCodec::GetInstance());
+
+ auto plugin = std::make_unique(channel);
+
+ channel->SetMethodCallHandler(
+ [plugin_pointer = plugin.get()](const auto& call, auto result) {
+ plugin_pointer->HandleMethodCall(call, std::move(result));
+ });
+
+ registrar->AddPlugin(std::move(plugin));
+ }
+
+ FirebaseStorageTizenPlugin(std::shared_ptr channel)
+ : channel_(std::move(channel)) {}
+
+ virtual ~FirebaseStorageTizenPlugin() {}
+
+ private:
+ std::shared_ptr channel_;
+
+ void HandleMethodCall(
+ const flutter::MethodCall& method_call,
+ std::unique_ptr result) {
+ auto method_call_arguments =
+ std::get_if(method_call.arguments());
+ if (!method_call_arguments) {
+ FirebaseStorageError error(FirebaseStorageError::Code::kInvalidArgument);
+ result->Error(error.GetCodeString(), "No arguments provided.");
+ return;
+ }
+
+ auto args = std::make_unique(method_call_arguments);
+ const auto& method_name = method_call.method_name();
+
+ try {
+ if (method_name == "Storage#useEmulator") {
+ result->NotImplemented();
+ } else if (method_name == "Reference#delete") {
+ ReferenceDelete(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Reference#getDownloadURL") {
+ ReferenceGetDownloadURL(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Reference#getMetadata") {
+ ReferenceGetMetadata(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Reference#getData") {
+ ReferenceGetData(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Reference#list") {
+ ReferenceList(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Reference#listAll") {
+ result->NotImplemented();
+ } else if (method_name == "Reference#updateMetadata") {
+ ReferenceUpdateMetadata(std::make_shared(
+ std::move(args), std::move(result)));
+ } else if (method_name == "Task#startPutData") {
+ StorageTask::Create(channel_, std::move(args))
+ ->Run();
+ result->Success();
+ } else if (method_name == "Task#startPutString") {
+ StorageTask::Create(channel_, std::move(args))
+ ->Run();
+ result->Success();
+ } else if (method_name == "Task#startPutFile") {
+ StorageTask::Create(channel_, std::move(args))
+ ->Run();
+ result->Success();
+ } else if (method_name == "Task#pause") {
+ TaskStorageControl(
+ std::move(args), std::move(result),
+ [](StorageTask* task) { return task->GetController()->Pause(); });
+ } else if (method_name == "Task#resume") {
+ TaskStorageControl(
+ std::move(args), std::move(result),
+ [](StorageTask* task) { return task->GetController()->Resume(); });
+ } else if (method_name == "Task#cancel") {
+ TaskStorageControl(
+ std::move(args), std::move(result), [](StorageTask* task) {
+ auto result = task->GetController()->Cancel();
+
+ if (result) {
+ task->GetMethodChannel()->InvokeMethod(
+ "Task#onCanceled",
+ std::make_unique(
+ utils::GetTaskEventValue(task->GetStorageTaskData())));
+ }
+
+ return result;
+ });
+ } else if (method_name == "Task#writeToFile") {
+ StorageTask::Create(channel_, std::move(args))
+ ->Run();
+ result->Success();
+ } else {
+ result->NotImplemented();
+ }
+ } catch (const std::invalid_argument& error) {
+ result->Error("invalid-argument", error.what());
+ } catch (const FirebaseStorageError& error) {
+ result->Error(error.GetCodeString(), error.GetMessage());
+ }
+ }
+
+ void ReferenceDelete(const std::shared_ptr& work) {
+ work->GetStorageReference()->Delete().OnCompletion(
+ [work](const firebase::Future& metadata) {
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ work->Success();
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void ReferenceGetDownloadURL(
+ const std::shared_ptr& work) {
+ work->GetStorageReference()->GetDownloadUrl().OnCompletion(
+ [work](const firebase::Future& metadata) {
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ work->Success(flutter::EncodableValue(flutter::EncodableMap{
+ {flutter::EncodableValue("downloadURL"),
+ flutter::EncodableValue(*metadata.result())}}));
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void ReferenceGetMetadata(const std::shared_ptr& work) {
+ work->GetStorageReference()->GetMetadata().OnCompletion(
+ [work](const firebase::Future& metadata) {
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ work->Success(utils::GetMetadataValue(metadata.result()));
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void ReferenceGetData(const std::shared_ptr& work) {
+ auto max_size =
+ work->GetMethodCallArguments()->GetRequiredArg("maxSize");
+ auto buffer = std::make_shared>(max_size);
+
+ work->GetStorageReference()
+ ->GetBytes(buffer.get()->data(), max_size)
+ .OnCompletion(
+ [work, buffer, max_size](const firebase::Future& metadata) {
+ auto buffer_size = *metadata.result();
+ assert(buffer_size <= max_size);
+
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ auto buffer_data = buffer.get();
+ buffer_data->resize(buffer_size);
+
+ work->Success(flutter::EncodableValue(*buffer_data));
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void ReferenceList(const std::shared_ptr& work) {
+ auto options_map =
+ work->GetMethodCallArguments()->GetRequiredArg(
+ "options");
+
+ MethodCallArguments options(&options_map);
+
+ int max_result = options.GetRequiredArg("maxResults");
+ auto page_token = options.GetArg("pageToken").value_or("");
+ work->GetStorageReference()
+ ->List(max_result, page_token)
+ .OnCompletion([work](const firebase::Future& metadata) {
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ work->Success(utils::ParseListResult(*metadata.result()));
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void ReferenceUpdateMetadata(
+ const std::shared_ptr& work) {
+ auto metadata =
+ work->GetMethodCallArguments()->GetRequiredArg(
+ "metadata");
+
+ work->GetStorageReference()
+ ->UpdateMetadata(utils::ParseMetadata(metadata))
+ .OnCompletion(
+ [work](
+ const firebase::Future& metadata) {
+ if (metadata.error() == firebase::storage::Error::kErrorNone) {
+ work->Success(utils::GetMetadataValue(metadata.result()));
+ } else {
+ work->Fail(metadata.error());
+ }
+ });
+ }
+
+ void TaskStorageControl(std::unique_ptr&& args,
+ std::unique_ptr&& result,
+ std::function control_callback) {
+ int handle = args->GetRequiredArg("handle");
+
+ auto task_value = StorageTaskHandler::GetInstance().GetTask(handle);
+ if (!task_value.has_value()) {
+ FirebaseStorageError error(FirebaseStorageError::Code::KTaskNotFound);
+ result->Error(error.GetCodeString(), error.GetMessage());
+ return;
+ }
+
+ auto task = *task_value;
+ bool status = control_callback(task);
+ result->Success(utils::GetTaskControlEventValue(status, task->GetPath(),
+ task->GetController()));
+ }
+};
+
+} // namespace
+
+void FirebaseStorageTizenPluginRegisterWithRegistrar(
+ FlutterDesktopPluginRegistrarRef registrar) {
+ FirebaseStorageTizenPlugin::RegisterWithRegistrar(
+ flutter::PluginRegistrarManager::GetInstance()
+ ->GetRegistrar(registrar));
+}
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_utils.cc b/packages/firebase_storage/tizen/src/firebase_storage_utils.cc
new file mode 100644
index 0000000..b94dd22
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_utils.cc
@@ -0,0 +1,311 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "firebase_storage_utils.h"
+
+#include
+#include
+#include
+
+#include "firebase/app/src/base64.h"
+#include "firebase/storage/controller.h"
+#include "firebase_storage_error.h"
+
+namespace utils {
+
+firebase::storage::Storage* GetStorage(MethodCallArguments* args) {
+ auto appName = args->GetRequiredArg("appName");
+ firebase::App* app = firebase::App::GetInstance(appName.data());
+ if (!app) {
+ throw FirebaseStorageError(FirebaseStorageError::Code::kAppNotFound);
+ }
+
+ firebase::storage::Storage* storage = nullptr;
+ auto bucket = args->GetArg("bucket");
+
+ if (bucket.has_value()) {
+ std::string url("gs://" + bucket.value());
+ storage = firebase::storage::Storage::GetInstance(app, url.data());
+ } else {
+ storage = firebase::storage::Storage::GetInstance(app);
+ }
+
+ auto max_operation_retry_time = args->GetArg("maxOperationRetryTime");
+ if (max_operation_retry_time.has_value()) {
+ storage->set_max_operation_retry_time(max_operation_retry_time.value());
+ }
+
+ auto max_download_retry_time = args->GetArg("maxDownloadRetryTime");
+ if (max_download_retry_time.has_value()) {
+ storage->set_max_download_retry_time(max_download_retry_time.value());
+ }
+
+ auto max_upload_retry_time = args->GetArg("maxUploadRetryTime");
+ if (max_upload_retry_time.has_value()) {
+ storage->set_max_upload_retry_time(max_upload_retry_time.value());
+ }
+
+ return storage;
+}
+
+firebase::storage::StorageReference GetStorageReference(
+ MethodCallArguments* args) {
+ return GetStorage(args)->GetReference(
+ args->GetRequiredArg("path"));
+}
+
+bool StringToByteData(const std::string& input, std::string* output,
+ int format) {
+ switch (format) {
+ case 1: // PutStringFormat.base64
+ return firebase::internal::Base64Decode(input, output);
+ default:
+ std::ostringstream os;
+ os << "This format(" << format << ") is not supported yet.";
+ throw FirebaseStorageError(FirebaseStorageError::Code::kNotSupported,
+ os.str());
+ }
+
+ return false;
+}
+
+firebase::storage::Metadata ParseMetadata(const flutter::EncodableMap& value) {
+ MethodCallArguments metadata(&value);
+ firebase::storage::Metadata out;
+
+ auto cache_control = metadata.GetArg("cacheControl");
+ if (cache_control.has_value()) {
+ out.set_cache_control(cache_control.value());
+ }
+
+ auto content_disposition = metadata.GetArg("contentDisposition");
+ if (content_disposition.has_value()) {
+ out.set_content_disposition(content_disposition.value());
+ }
+
+ auto content_encoding = metadata.GetArg("contentEncoding");
+ if (content_encoding.has_value()) {
+ out.set_content_encoding(content_encoding.value());
+ }
+
+ auto content_language = metadata.GetArg("contentLanguage");
+ if (content_language.has_value()) {
+ out.set_content_language(content_language.value());
+ }
+
+ auto content_type = metadata.GetArg("contentType");
+ if (content_type.has_value()) {
+ out.set_content_type(content_type.value());
+ }
+
+ auto custom_metadata =
+ metadata.GetArg("customMetadata");
+ if (custom_metadata.has_value()) {
+ for (const auto& [key, value] : custom_metadata.value()) {
+ if (std::holds_alternative(key) &&
+ std::holds_alternative(value)) {
+ (*out.custom_metadata())[std::get(key)] =
+ std::get(value);
+ }
+ }
+ }
+
+ return out;
+}
+
+flutter::EncodableValue GetMetadataValue(
+ const firebase::storage::Metadata* metadata) {
+ auto metadata_map = flutter::EncodableMap{
+ {flutter::EncodableValue("bucket"),
+ flutter::EncodableValue(metadata->bucket())},
+ {flutter::EncodableValue("cacheControl"),
+ flutter::EncodableValue(metadata->cache_control())},
+ {flutter::EncodableValue("contentDisposition"),
+ flutter::EncodableValue(metadata->content_disposition())},
+ {flutter::EncodableValue("contentEncoding"),
+ flutter::EncodableValue(metadata->content_encoding())},
+ {flutter::EncodableValue("contentLanguage"),
+ flutter::EncodableValue(metadata->content_language())},
+ {flutter::EncodableValue("contentType"),
+ flutter::EncodableValue(metadata->content_type())},
+ {flutter::EncodableValue("fullPath"),
+ flutter::EncodableValue(metadata->path())},
+ {flutter::EncodableValue("generation"),
+ flutter::EncodableValue(metadata->generation())},
+ {flutter::EncodableValue("metadataGeneration"),
+ flutter::EncodableValue(metadata->metadata_generation())},
+ {flutter::EncodableValue("md5Hash"),
+ flutter::EncodableValue(metadata->md5_hash())},
+ {flutter::EncodableValue("metageneration"),
+ flutter::EncodableValue(metadata->metadata_generation())},
+ {flutter::EncodableValue("name"),
+ flutter::EncodableValue(metadata->name())},
+ {flutter::EncodableValue("size"),
+ flutter::EncodableValue(metadata->size_bytes())},
+ {flutter::EncodableValue("creationTimeMillis"),
+ flutter::EncodableValue(metadata->creation_time())},
+ {flutter::EncodableValue("updatedTimeMillis"),
+ flutter::EncodableValue(metadata->updated_time())},
+ };
+
+ flutter::EncodableMap custom_metadata;
+ for (const auto& [key, value] : *(metadata->custom_metadata())) {
+ custom_metadata[flutter::EncodableValue(key)] =
+ flutter::EncodableValue(value);
+ }
+ metadata_map[flutter::EncodableValue("customMetadata")] =
+ flutter::EncodableValue(custom_metadata);
+
+ return flutter::EncodableValue(metadata_map);
+}
+
+static flutter::EncodableMap GetTaskEventMap(const int handle,
+ const std::string& name,
+ const std::string& bucket) {
+ return flutter::EncodableMap{
+ {flutter::EncodableValue("handle"), flutter::EncodableValue(handle)},
+ {flutter::EncodableValue("appName"), flutter::EncodableValue(name)},
+ {flutter::EncodableValue("bucket"), flutter::EncodableValue(bucket)}};
+}
+
+static flutter::EncodableMap GetSnapshotMap(const std::string& path,
+ const int64_t bytes_transferred,
+ const int64_t total_bytes) {
+ return flutter::EncodableMap{
+ {flutter::EncodableValue("path"), flutter::EncodableValue(path)},
+ {flutter::EncodableValue("bytesTransferred"),
+ flutter::EncodableValue(bytes_transferred)},
+ {flutter::EncodableValue("totalBytes"),
+ flutter::EncodableValue(total_bytes)}};
+}
+
+flutter::EncodableValue GetTaskEventValue(const StorageTaskData& data) {
+ return flutter::EncodableValue(
+ GetTaskEventMap(data.handle, data.app_name, data.bucket));
+}
+
+flutter::EncodableValue GetTaskEventValue(const StorageTaskData& data,
+ int64_t bytes_transferred,
+ int64_t total_bytes) {
+ flutter::EncodableMap map =
+ GetTaskEventMap(data.handle, data.app_name, data.bucket);
+
+ flutter::EncodableMap snapshot =
+ GetSnapshotMap(data.path, bytes_transferred, total_bytes);
+
+ map[flutter::EncodableValue("snapshot")] = flutter::EncodableValue(snapshot);
+
+ return flutter::EncodableValue(map);
+}
+
+flutter::EncodableValue GetTaskControlEventValue(
+ const bool status, const std::string& path,
+ const firebase::storage::Controller* controller) {
+ auto map = flutter::EncodableMap{
+ {flutter::EncodableValue("status"), flutter::EncodableValue(status)},
+ };
+ if (status) {
+ flutter::EncodableMap snapshot = GetSnapshotMap(
+ path, controller->bytes_transferred(), controller->total_byte_count());
+
+ map[flutter::EncodableValue("snapshot")] =
+ flutter::EncodableValue(snapshot);
+ }
+
+ return flutter::EncodableValue(map);
+}
+
+flutter::EncodableValue GetPutTaskSuccessEventValue(
+ const StorageTaskData& data, const firebase::storage::Metadata* result) {
+ flutter::EncodableMap map =
+ GetTaskEventMap(data.handle, data.app_name, data.bucket);
+
+ flutter::EncodableMap metadata{
+ {flutter::EncodableValue("generation"),
+ flutter::EncodableValue(result->generation())},
+ {flutter::EncodableValue("fullPath"),
+ flutter::EncodableValue(result->GetReference().full_path())},
+ {flutter::EncodableValue("cacheControl"),
+ flutter::EncodableValue(result->cache_control())},
+ {flutter::EncodableValue("bucket"),
+ flutter::EncodableValue(result->bucket())},
+ {flutter::EncodableValue("metadataGeneration"),
+ flutter::EncodableValue(result->metadata_generation())},
+ {flutter::EncodableValue("updatedTimeMillis"),
+ flutter::EncodableValue(result->updated_time())},
+ {flutter::EncodableValue("size"),
+ flutter::EncodableValue(result->size_bytes())},
+ {flutter::EncodableValue("md5Hash"),
+ flutter::EncodableValue(result->md5_hash())},
+ {flutter::EncodableValue("creationTimeMillis"),
+ flutter::EncodableValue(result->creation_time())},
+ {flutter::EncodableValue("contentDisposition"),
+ flutter::EncodableValue(result->content_disposition())},
+ {flutter::EncodableValue("contentEncoding"),
+ flutter::EncodableValue(result->content_encoding())},
+ {flutter::EncodableValue("contentLanguage"),
+ flutter::EncodableValue(result->content_language())},
+ {flutter::EncodableValue("contentType"),
+ flutter::EncodableValue(result->content_type())},
+ };
+
+ flutter::EncodableMap custom_metadata;
+ for (const auto& [key, value] : *(result->custom_metadata())) {
+ custom_metadata[flutter::EncodableValue(key)] =
+ flutter::EncodableValue(value);
+ }
+ metadata[flutter::EncodableValue("customMetadata")] =
+ flutter::EncodableValue(custom_metadata);
+
+ flutter::EncodableMap snapshot =
+ GetSnapshotMap(data.path, result->size_bytes(), result->size_bytes());
+ snapshot[flutter::EncodableValue("metadata")] =
+ flutter::EncodableValue(metadata);
+ map[flutter::EncodableValue("snapshot")] = flutter::EncodableValue(snapshot);
+
+ return flutter::EncodableValue(map);
+}
+
+flutter::EncodableValue GetTaskErrorEventValue(const StorageTaskData& data,
+ const int error_code,
+ const char* error_message) {
+ flutter::EncodableMap map =
+ GetTaskEventMap(data.handle, data.app_name, data.bucket);
+
+ flutter::EncodableMap error = {
+ {flutter::EncodableValue("code"), flutter::EncodableValue(error_code)},
+ {flutter::EncodableValue("message"),
+ flutter::EncodableValue(error_message)}};
+
+ map[flutter::EncodableValue("error")] = flutter::EncodableValue(error);
+
+ return flutter::EncodableValue(map);
+}
+
+flutter::EncodableValue ParseListResult(
+ const firebase::storage::ListResult& list_result) {
+ flutter::EncodableMap map;
+
+ if (!list_result.GetPageToken().empty()) {
+ map[flutter::EncodableValue("nextPageToken")] =
+ flutter::EncodableValue(list_result.GetPageToken());
+ }
+
+ flutter::EncodableList items;
+ for (StorageReference& reference : list_result.GetItems()) {
+ items.push_back(flutter::EncodableValue(reference.full_path()));
+ }
+
+ flutter::EncodableList prefixes;
+ for (StorageReference& reference : list_result.GetPrefixes()) {
+ prefixes.push_back(flutter::EncodableValue(reference.full_path()));
+ }
+
+ map[flutter::EncodableValue("items")] = items;
+ map[flutter::EncodableValue("prefixes")] = prefixes;
+
+ return flutter::EncodableValue(map);
+}
+
+} // namespace utils
diff --git a/packages/firebase_storage/tizen/src/firebase_storage_utils.h b/packages/firebase_storage/tizen/src/firebase_storage_utils.h
new file mode 100644
index 0000000..248f778
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/firebase_storage_utils.h
@@ -0,0 +1,58 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_PLUGIN_FIREBASE_STORAGE_UTILS_H_
+#define FLUTTER_PLUGIN_FIREBASE_STORAGE_UTILS_H_
+
+#include
+
+#include
+
+#include "firebase/storage.h"
+#include "flutter_types.hpp"
+
+namespace utils {
+
+struct StorageTaskData {
+ int handle;
+ std::string app_name;
+ std::string path;
+ std::string bucket;
+};
+
+firebase::storage::Storage* GetStorage(MethodCallArguments* args);
+
+firebase::storage::StorageReference GetStorageReference(
+ MethodCallArguments* args);
+
+bool StringToByteData(const std::string& input, std::string* output,
+ int format);
+
+firebase::storage::Metadata ParseMetadata(const flutter::EncodableMap& value);
+
+flutter::EncodableValue GetMetadataValue(
+ const firebase::storage::Metadata* metadata);
+
+flutter::EncodableValue GetTaskEventValue(const StorageTaskData& data);
+flutter::EncodableValue GetTaskEventValue(const StorageTaskData& data,
+ const int64_t bytes_transferred,
+ const int64_t total_bytes);
+
+flutter::EncodableValue GetTaskControlEventValue(
+ const bool status, const std::string& path,
+ const firebase::storage::Controller* controller);
+
+flutter::EncodableValue GetPutTaskSuccessEventValue(
+ const StorageTaskData& data, const firebase::storage::Metadata* result);
+
+flutter::EncodableValue GetTaskErrorEventValue(const StorageTaskData& data,
+ const int error_code,
+ const char* error_message);
+
+flutter::EncodableValue ParseListResult(
+ const firebase::storage::ListResult& list_result);
+
+} // namespace utils
+
+#endif
diff --git a/packages/firebase_storage/tizen/src/flutter_types.hpp b/packages/firebase_storage/tizen/src/flutter_types.hpp
new file mode 100644
index 0000000..9d77951
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/flutter_types.hpp
@@ -0,0 +1,64 @@
+// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_PLUGIN_FLUTTER_ARGUMENTS_H_
+#define FLUTTER_PLUGIN_FLUTTER_ARGUMENTS_H_
+
+#include
+#include
+#include
+
+#include
+#include
+
+typedef flutter::MethodChannel FlMethodChannel;
+typedef flutter::MethodResult FlMethodResult;
+
+class MethodCallArguments {
+public:
+ MethodCallArguments(const flutter::EncodableMap *arguments)
+ : arguments_(arguments) {}
+
+ template std::optional GetArg(const char *key) {
+ assert(arguments_);
+
+ auto iter = arguments_->find(flutter::EncodableValue(key));
+ if (iter != arguments_->end() && !iter->second.IsNull()) {
+ if (auto *value = std::get_if(&iter->second)) {
+ return *value;
+ }
+ }
+ return std::nullopt;
+ }
+
+ template T GetRequiredArg(const char *key) {
+ assert(arguments_);
+
+ auto value = GetArg(key);
+ if (value.has_value()) {
+ return value.value();
+ }
+ std::string message =
+ "No " + std::string(key) + " provided or has invalid type or value.";
+ throw std::invalid_argument(message);
+ }
+
+ std::string GetKeyString() {
+ assert(arguments_);
+
+ std::string argument_keys;
+ for (const auto &[key, value] : *arguments_) {
+ argument_keys += std::get(key);
+ argument_keys += ", ";
+ }
+
+ argument_keys.erase(argument_keys.size() - 2);
+ return argument_keys;
+ }
+
+private:
+ const flutter::EncodableMap *arguments_;
+};
+
+#endif
diff --git a/packages/firebase_storage/tizen/src/log.h b/packages/firebase_storage/tizen/src/log.h
new file mode 100644
index 0000000..08d5570
--- /dev/null
+++ b/packages/firebase_storage/tizen/src/log.h
@@ -0,0 +1,24 @@
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#include
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "FirebaseStorageTizenPlugin"
+
+#ifndef __MODULE__
+#define __MODULE__ strrchr("/" __FILE__, '/') + 1
+#endif
+
+#define LOG(prio, fmt, arg...) \
+ dlog_print(prio, LOG_TAG, "%s: %s(%d) > " fmt, __MODULE__, __func__, \
+ __LINE__, ##arg)
+
+#define LOG_DEBUG(fmt, args...) LOG(DLOG_DEBUG, fmt, ##args)
+#define LOG_INFO(fmt, args...) LOG(DLOG_INFO, fmt, ##args)
+#define LOG_WARN(fmt, args...) LOG(DLOG_WARN, fmt, ##args)
+#define LOG_ERROR(fmt, args...) LOG(DLOG_ERROR, fmt, ##args)
+
+#endif // __LOG_H__
diff --git a/packages/firebase_storage/tizen/tar_url.sh b/packages/firebase_storage/tizen/tar_url.sh
new file mode 100755
index 0000000..8ab36df
--- /dev/null
+++ b/packages/firebase_storage/tizen/tar_url.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+
+USAGE=$(cat << EOF
+Usage: $(basename "$0") [DEST_DIR] [STRIP_COMPONENTS_NUMBER]
+
+Description:
+A script to download a tar file from a given URL and extract it. It skips the extraction
+processs if the contents of the file have already been extracted for the same URL.
+
+Arguments:
+ FILE_URL The URL address of the tar file to download.
+ DEST_DIR The directory path to the tar file for extraction. (default: \$HOME/.firebaseSDK)
+ STRIP_COMPONENTS_NUMBER
+ The number of leading components to strip during the tar extraction. (default: 1)
+
+Example:
+ $(basename "$0") https://example.com/file.tar.gz /path/to 0
+EOF
+)
+
+if [ -z "$1" ]; then
+ echo "$USAGE"
+ exit 1
+fi
+
+FILE_URL=$1
+DEST_DIR=${2:-${FLUTTER_BUILD_DIR}/.firebaseSDK}
+TAR_SNUM=${3:-1}
+
+RECORD_FILE="${DEST_DIR}/VERSION"
+
+if [ -e "$RECORD_FILE" ] && [ "$FILE_URL" = $(head -n 1 "$RECORD_FILE") ]; then
+ echo "$RECORD_FILE exists with the same URL."
+else
+ [ ! -d "$DEST_DIR" ] && mkdir -v "$DEST_DIR"
+ curl -L $FILE_URL | tar -xz --strip-components=$TAR_SNUM -C "$DEST_DIR"
+ echo $FILE_URL > "$RECORD_FILE"
+fi