Skip to content

Commit 5d730b5

Browse files
mvanbeusekomjasonpanelli
authored andcommitted
[cross_file] An abstraction to allow working with files across multiple platforms. (flutter#3260)
* Initial version of x_file package * Renamed from x_file to cross_file * Add back x_file type to file_selector * Fix formatting issues * Update homepage and version * Added README.md * Added missing copyright * Revert "Added missing copyright" This reverts commit cf7e8d5. * Add missing copyright Co-Authored-By: Jason Panelli <[email protected]> * Renamed class implementation back to XFile * Fix formatting issues * Rename to cross_file * Added code owners for cross_file package Co-authored-by: Jason Panelli <[email protected]>
1 parent 7cb8282 commit 5d730b5

15 files changed

+736
-0
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ packages/android_intent/** @mklim @matthew-carroll
99
packages/battery/** @amirh @matthew-carroll
1010
packages/camera/** @bparrishMines
1111
packages/connectivity/** @cyanglaz @matthew-carroll
12+
packages/cross_file/** @ditman @mvanbeusekom
1213
packages/device_info/** @matthew-carroll
1314
packages/espresso/** @collinjackson @adazh
1415
packages/file_selector/** @ditman

packages/cross_file/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.1.0
2+
3+
- Initial open-source release

packages/cross_file/LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright 2020 The Flutter Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above
9+
copyright notice, this list of conditions and the following
10+
disclaimer in the documentation and/or other materials provided
11+
with the distribution.
12+
* Neither the name of Google Inc. nor the names of its
13+
contributors may be used to endorse or promote products derived
14+
from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

packages/cross_file/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# cross_file
2+
3+
An abstraction to allow working with files across multiple platforms.
4+
5+
# Usage
6+
7+
Import `package:cross/cross_info.dart`, instantiate a `CrossFile`
8+
using a path or byte array and use its methods and properties to
9+
access the file and its metadata.
10+
11+
Example:
12+
13+
```dart
14+
import 'package:cross_file/cross_file.dart';
15+
16+
final file = CrossFile('assets/hello.txt');
17+
18+
print('File information:');
19+
print('- Path: ${file.path}');
20+
print('- Name: ${file.name}');
21+
print('- MIME type: ${file.mimeType}');
22+
23+
final fileContent = await file.readAsString();
24+
print('Content of the file: ${fileContent}'); // e.g. "Moto G (4)"
25+
```
26+
27+
You will find links to the API docs on the [pub page](https://pub.dartlang.org/packages/cross_file).
28+
29+
## Getting Started
30+
31+
For help getting started with Flutter, view our online
32+
[documentation](http://flutter.io/).
33+
34+
For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export 'src/x_file.dart';
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:typed_data';
7+
8+
/// The interface for a CrossFile.
9+
///
10+
/// A CrossFile is a container that wraps the path of a selected
11+
/// file by the user and (in some platforms, like web) the bytes
12+
/// with the contents of the file.
13+
///
14+
/// This class is a very limited subset of dart:io [File], so all
15+
/// the methods should seem familiar.
16+
abstract class XFileBase {
17+
/// Construct a CrossFile
18+
XFileBase(String path);
19+
20+
/// Save the CrossFile at the indicated file path.
21+
void saveTo(String path) async {
22+
throw UnimplementedError('saveTo has not been implemented.');
23+
}
24+
25+
/// Get the path of the picked file.
26+
///
27+
/// This should only be used as a backwards-compatibility clutch
28+
/// for mobile apps, or cosmetic reasons only (to show the user
29+
/// the path they've picked).
30+
///
31+
/// Accessing the data contained in the picked file by its path
32+
/// is platform-dependant (and won't work on web), so use the
33+
/// byte getters in the CrossFile instance instead.
34+
String get path {
35+
throw UnimplementedError('.path has not been implemented.');
36+
}
37+
38+
/// The name of the file as it was selected by the user in their device.
39+
///
40+
/// Use only for cosmetic reasons, do not try to use this as a path.
41+
String get name {
42+
throw UnimplementedError('.name has not been implemented.');
43+
}
44+
45+
/// For web, it may be necessary for a file to know its MIME type.
46+
String get mimeType {
47+
throw UnimplementedError('.mimeType has not been implemented.');
48+
}
49+
50+
/// Get the length of the file. Returns a `Future<int>` that completes with the length in bytes.
51+
Future<int> length() {
52+
throw UnimplementedError('.length() has not been implemented.');
53+
}
54+
55+
/// Synchronously read the entire file contents as a string using the given [Encoding].
56+
///
57+
/// By default, `encoding` is [utf8].
58+
///
59+
/// Throws Exception if the operation fails.
60+
Future<String> readAsString({Encoding encoding = utf8}) {
61+
throw UnimplementedError('readAsString() has not been implemented.');
62+
}
63+
64+
/// Synchronously read the entire file contents as a list of bytes.
65+
///
66+
/// Throws Exception if the operation fails.
67+
Future<Uint8List> readAsBytes() {
68+
throw UnimplementedError('readAsBytes() has not been implemented.');
69+
}
70+
71+
/// Create a new independent [Stream] for the contents of this file.
72+
///
73+
/// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0).
74+
///
75+
/// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file.
76+
///
77+
/// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled.
78+
Stream<Uint8List> openRead([int start, int end]) {
79+
throw UnimplementedError('openRead() has not been implemented.');
80+
}
81+
82+
/// Get the last-modified time for the CrossFile
83+
Future<DateTime> lastModified() {
84+
throw UnimplementedError('openRead() has not been implemented.');
85+
}
86+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:typed_data';
7+
8+
import 'package:http/http.dart' as http show readBytes;
9+
import 'package:meta/meta.dart';
10+
import 'dart:html';
11+
12+
import '../web_helpers/web_helpers.dart';
13+
import './base.dart';
14+
15+
/// A CrossFile that works on web.
16+
///
17+
/// It wraps the bytes of a selected file.
18+
class XFile extends XFileBase {
19+
String path;
20+
21+
final String mimeType;
22+
final Uint8List _data;
23+
final int _length;
24+
final String name;
25+
final DateTime _lastModified;
26+
Element _target;
27+
28+
final CrossFileTestOverrides _overrides;
29+
30+
bool get _hasTestOverrides => _overrides != null;
31+
32+
/// Construct a CrossFile object from its ObjectUrl.
33+
///
34+
/// Optionally, this can be initialized with `bytes` and `length`
35+
/// so no http requests are performed to retrieve files later.
36+
///
37+
/// `name` needs to be passed from the outside, since we only have
38+
/// access to it while we create the ObjectUrl.
39+
XFile(
40+
this.path, {
41+
this.mimeType,
42+
this.name,
43+
int length,
44+
Uint8List bytes,
45+
DateTime lastModified,
46+
@visibleForTesting CrossFileTestOverrides overrides,
47+
}) : _data = bytes,
48+
_length = length,
49+
_overrides = overrides,
50+
_lastModified = lastModified,
51+
super(path);
52+
53+
/// Construct an CrossFile from its data
54+
XFile.fromData(
55+
Uint8List bytes, {
56+
this.mimeType,
57+
this.name,
58+
int length,
59+
DateTime lastModified,
60+
this.path,
61+
@visibleForTesting CrossFileTestOverrides overrides,
62+
}) : _data = bytes,
63+
_length = length,
64+
_overrides = overrides,
65+
_lastModified = lastModified,
66+
super(path) {
67+
if (path == null) {
68+
final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType);
69+
this.path = Url.createObjectUrl(blob);
70+
}
71+
}
72+
73+
@override
74+
Future<DateTime> lastModified() async {
75+
if (_lastModified != null) {
76+
return Future.value(_lastModified);
77+
}
78+
return null;
79+
}
80+
81+
Future<Uint8List> get _bytes async {
82+
if (_data != null) {
83+
return Future.value(UnmodifiableUint8ListView(_data));
84+
}
85+
return http.readBytes(path);
86+
}
87+
88+
@override
89+
Future<int> length() async {
90+
return _length ?? (await _bytes).length;
91+
}
92+
93+
@override
94+
Future<String> readAsString({Encoding encoding = utf8}) async {
95+
return encoding.decode(await _bytes);
96+
}
97+
98+
@override
99+
Future<Uint8List> readAsBytes() async {
100+
return Future.value(await _bytes);
101+
}
102+
103+
@override
104+
Stream<Uint8List> openRead([int start, int end]) async* {
105+
final bytes = await _bytes;
106+
yield bytes.sublist(start ?? 0, end ?? bytes.length);
107+
}
108+
109+
/// Saves the data of this CrossFile at the location indicated by path.
110+
/// For the web implementation, the path variable is ignored.
111+
void saveTo(String path) async {
112+
// Create a DOM container where we can host the anchor.
113+
_target = ensureInitialized('__x_file_dom_element');
114+
115+
// Create an <a> tag with the appropriate download attributes and click it
116+
// May be overridden with CrossFileTestOverrides
117+
final AnchorElement element =
118+
(_hasTestOverrides && _overrides.createAnchorElement != null)
119+
? _overrides.createAnchorElement(this.path, this.name)
120+
: createAnchorElement(this.path, this.name);
121+
122+
// Clear the children in our container so we can add an element to click
123+
_target.children.clear();
124+
addElementToContainerAndClick(_target, element);
125+
}
126+
}
127+
128+
/// Overrides some functions to allow testing
129+
@visibleForTesting
130+
class CrossFileTestOverrides {
131+
/// For overriding the creation of the file input element.
132+
Element Function(String href, String suggestedName) createAnchorElement;
133+
134+
/// Default constructor for overrides
135+
CrossFileTestOverrides({this.createAnchorElement});
136+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:typed_data';
6+
import 'package:meta/meta.dart';
7+
8+
import './base.dart';
9+
10+
/// A CrossFile is a cross-platform, simplified File abstraction.
11+
///
12+
/// It wraps the bytes of a selected file, and its (platform-dependant) path.
13+
class XFile extends XFileBase {
14+
/// Construct a CrossFile object from its path.
15+
///
16+
/// Optionally, this can be initialized with `bytes` and `length`
17+
/// so no http requests are performed to retrieve data later.
18+
///
19+
/// `name` may be passed from the outside, for those cases where the effective
20+
/// `path` of the file doesn't match what the user sees when selecting it
21+
/// (like in web)
22+
XFile(
23+
String path, {
24+
String mimeType,
25+
String name,
26+
int length,
27+
Uint8List bytes,
28+
DateTime lastModified,
29+
@visibleForTesting CrossFileTestOverrides overrides,
30+
}) : super(path) {
31+
throw UnimplementedError(
32+
'CrossFile is not available in your current platform.');
33+
}
34+
35+
/// Construct a CrossFile object from its data
36+
XFile.fromData(
37+
Uint8List bytes, {
38+
String mimeType,
39+
String name,
40+
int length,
41+
DateTime lastModified,
42+
String path,
43+
@visibleForTesting CrossFileTestOverrides overrides,
44+
}) : super(path) {
45+
throw UnimplementedError(
46+
'CrossFile is not available in your current platform.');
47+
}
48+
}
49+
50+
/// Overrides some functions of CrossFile for testing purposes
51+
@visibleForTesting
52+
class CrossFileTestOverrides {
53+
/// For overriding the creation of the file input element.
54+
dynamic Function(String href, String suggestedName) createAnchorElement;
55+
56+
/// Default constructor for overrides
57+
CrossFileTestOverrides({this.createAnchorElement});
58+
}

0 commit comments

Comments
 (0)