file download actor
This commit is contained in:
parent
0cdf3d7dbf
commit
0596fdec27
4 changed files with 179 additions and 0 deletions
37
README.md
37
README.md
|
|
@ -208,6 +208,43 @@ print((nb as NeighborsResult).neighbors['NORTH']); // u09tunu
|
|||
|
||||
---
|
||||
|
||||
## FileDownloadActor
|
||||
|
||||
Downloads a file from a URL and saves it to disk via an injected `FileActor`.
|
||||
|
||||
```dart
|
||||
final actor = FileDownloadActor(
|
||||
fileActor: FileActor(),
|
||||
);
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `fileActor` | `FileActor` | required | Actor used to write the downloaded bytes to disk |
|
||||
| `httpClient` | `http.Client?` | new client | Injectable HTTP client; provide a `MockClient` in tests |
|
||||
|
||||
Call `actor.close()` when done to release the HTTP client.
|
||||
|
||||
| Message | Result | Description |
|
||||
|---|---|---|
|
||||
| `DownloadFile(url, savePath)` | `FileDownloaded(savePath)` | GETs the URL and writes the response bytes to `savePath`. Throws if the response status is not 2xx. |
|
||||
|
||||
```dart
|
||||
final actor = FileDownloadActor(fileActor: FileActor());
|
||||
|
||||
final result = await actor.handle(
|
||||
const DownloadFile(
|
||||
url: 'https://example.com/image.jpg',
|
||||
savePath: '/tmp/image.jpg',
|
||||
),
|
||||
);
|
||||
print((result as FileDownloaded).savePath); // /tmp/image.jpg
|
||||
|
||||
await actor.close();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FileUploadActor
|
||||
|
||||
Uploads files to a Nostr-authenticated remote storage service. Handles
|
||||
|
|
|
|||
57
lib/src/file_download_actor.dart
Normal file
57
lib/src/file_download_actor.dart
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:actors/actors.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'file_actor.dart';
|
||||
|
||||
sealed class FileDownloadMessage {
|
||||
const FileDownloadMessage();
|
||||
}
|
||||
|
||||
final class DownloadFile extends FileDownloadMessage {
|
||||
final String url;
|
||||
final String savePath;
|
||||
const DownloadFile({required this.url, required this.savePath});
|
||||
}
|
||||
|
||||
sealed class FileDownloadResult {
|
||||
const FileDownloadResult();
|
||||
}
|
||||
|
||||
final class FileDownloaded extends FileDownloadResult {
|
||||
final String savePath;
|
||||
const FileDownloaded(this.savePath);
|
||||
}
|
||||
|
||||
class FileDownloadActor
|
||||
with Handler<FileDownloadMessage, FileDownloadResult> {
|
||||
final FileActor _fileActor;
|
||||
final http.Client _httpClient;
|
||||
|
||||
FileDownloadActor({
|
||||
required FileActor fileActor,
|
||||
http.Client? httpClient,
|
||||
}) : _fileActor = fileActor,
|
||||
_httpClient = httpClient ?? http.Client();
|
||||
|
||||
@override
|
||||
Future<FileDownloadResult> handle(FileDownloadMessage message) =>
|
||||
switch (message) {
|
||||
DownloadFile(:final url, :final savePath) => _download(url, savePath),
|
||||
};
|
||||
|
||||
@override
|
||||
Future<void> close() async => _httpClient.close();
|
||||
|
||||
Future<FileDownloaded> _download(String url, String savePath) async {
|
||||
final response = await _httpClient.get(Uri.parse(url));
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw Exception('Download failed: HTTP ${response.statusCode}');
|
||||
}
|
||||
await _fileActor.handle(
|
||||
SaveBinaryFile(savePath, Uint8List.fromList(response.bodyBytes)),
|
||||
);
|
||||
return FileDownloaded(savePath);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,5 @@ export 'src/nostr_fetch_actor.dart';
|
|||
export 'src/nostr_signer_actor.dart';
|
||||
export 'src/geo_actor.dart';
|
||||
export 'src/file_upload_actor.dart';
|
||||
export 'src/file_download_actor.dart';
|
||||
export 'src/nostr_account_actor.dart';
|
||||
|
|
|
|||
84
test/file_download_actor_test.dart
Normal file
84
test/file_download_actor_test.dart
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:swarm/swarm.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tmpDir;
|
||||
|
||||
setUp(() async {
|
||||
tmpDir = await Directory.systemTemp.createTemp('file_download_test_');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await tmpDir.delete(recursive: true);
|
||||
});
|
||||
|
||||
FileDownloadActor makeActor(http.Client client) => FileDownloadActor(
|
||||
fileActor: FileActor(),
|
||||
httpClient: client,
|
||||
);
|
||||
|
||||
test('returns FileDownloaded with correct savePath', () async {
|
||||
final client = MockClient(
|
||||
(_) async => http.Response.bytes([1, 2, 3], 200),
|
||||
);
|
||||
final path = '${tmpDir.path}/output.bin';
|
||||
final result = await makeActor(client).handle(
|
||||
DownloadFile(url: 'https://example.com/file', savePath: path),
|
||||
);
|
||||
expect(result, isA<FileDownloaded>());
|
||||
expect((result as FileDownloaded).savePath, equals(path));
|
||||
});
|
||||
|
||||
test('saves correct bytes to disk', () async {
|
||||
final bytes = Uint8List.fromList([0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
final client = MockClient(
|
||||
(_) async => http.Response.bytes(bytes, 200),
|
||||
);
|
||||
final path = '${tmpDir.path}/data.bin';
|
||||
await makeActor(client).handle(
|
||||
DownloadFile(url: 'https://example.com/file', savePath: path),
|
||||
);
|
||||
expect(await File(path).readAsBytes(), equals(bytes));
|
||||
});
|
||||
|
||||
test('sends GET request to the correct URL', () async {
|
||||
http.Request? captured;
|
||||
final client = MockClient((request) async {
|
||||
captured = request;
|
||||
return http.Response.bytes([0], 200);
|
||||
});
|
||||
await makeActor(client).handle(
|
||||
DownloadFile(
|
||||
url: 'https://example.com/assets/image.png',
|
||||
savePath: '${tmpDir.path}/image.png',
|
||||
),
|
||||
);
|
||||
expect(captured!.method, equals('GET'));
|
||||
expect(
|
||||
captured!.url.toString(),
|
||||
equals('https://example.com/assets/image.png'),
|
||||
);
|
||||
});
|
||||
|
||||
test('non-2xx response throws', () async {
|
||||
for (final status in [400, 403, 404, 500]) {
|
||||
final client = MockClient(
|
||||
(_) async => http.Response.bytes([], status),
|
||||
);
|
||||
await expectLater(
|
||||
makeActor(client).handle(
|
||||
DownloadFile(
|
||||
url: 'https://example.com/file',
|
||||
savePath: '${tmpDir.path}/out.bin',
|
||||
),
|
||||
),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue