Skip to content

Commit

Permalink
feat: cleanup method 2.1.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
sowens-csd committed May 22, 2020
1 parent 206a477 commit 19c4510
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 50 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2.1.0

### New
* `cleanup` method on `LocalImageProvider` removes all temporary files created by the plugin

### Updates
* fix for `videoFile` call with id that does not exist

## 2.0.0

### Breaking
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Local Image Provider Plugin

[![pub package](https://img.shields.io/badge/pub-v2.0.0-blue)](https://pub.dartlang.org/packages/local_image_provider) [![build status](https://github.com/csdcorp/local_image_provider/workflows/build/badge.svg)](https://github.com/csdcorp/local_image_provider/actions?query=workflow%3Abuild)
[![pub package](https://img.shields.io/badge/pub-v2.1.0-blue)](https://pub.dartlang.org/packages/local_image_provider) [![build status](https://github.com/csdcorp/local_image_provider/workflows/build/badge.svg)](https://github.com/csdcorp/local_image_provider/actions?query=workflow%3Abuild)

A library for searching and retrieving the metadata and contents of the images and
albums on a mobile device.
Expand Down
Binary file modified android/.idea/caches/build_file_checksums.ser
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ public class LocalImageProviderPlugin : FlutterPlugin, MethodCallHandler,
"Missing arg requires id", null)
}
}
"cleanup" -> {
cleanup(result)
}
"images_in_album" -> {
val albumId = call.argument<String>("albumId")
val maxImages = call.argument<Int>("maxImages")
Expand Down Expand Up @@ -545,6 +548,14 @@ public class LocalImageProviderPlugin : FlutterPlugin, MethodCallHandler,
}).start()
}

// Since no temporary files are created on android this method is a no-op
private fun cleanup(result: Result) {
if (isNotInitialized(result)) {
return
}
result.success(true )
}

private fun getImageBytes(id: String, width: Int, height: Int, result: Result) {
if (isNotInitialized(result)) {
return
Expand Down
18 changes: 0 additions & 18 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ PODS:
- Flutter
- local_image_provider (0.0.1):
- Flutter
- native_video_view (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
- path_provider_macos (0.0.1):
- Flutter
- video_player (0.0.1):
- Flutter
- video_player_web (0.0.1):
Expand All @@ -22,9 +16,6 @@ DEPENDENCIES:
- flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- local_image_provider (from `.symlinks/plugins/local_image_provider/ios`)
- native_video_view (from `.symlinks/plugins/native_video_view/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- video_player_web (from `.symlinks/plugins/video_player_web/ios`)

Expand All @@ -37,12 +28,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker/ios"
local_image_provider:
:path: ".symlinks/plugins/local_image_provider/ios"
native_video_view:
:path: ".symlinks/plugins/native_video_view/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
path_provider_macos:
:path: ".symlinks/plugins/path_provider_macos/ios"
video_player:
:path: ".symlinks/plugins/video_player/ios"
video_player_web:
Expand All @@ -53,9 +38,6 @@ SPEC CHECKSUMS:
flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b
image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f
local_image_provider: 05b1af32561482c0ca5a440fbc7f489ad818c962
native_video_view: 02756aebe7ec9d0ab00d039b7aa3756548900bc3
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549
video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7

Expand Down
1 change: 1 addition & 0 deletions example/lib/local_image_body_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class _LocalImageBodyWidgetState extends State<LocalImageBodyWidget> {
}
hasPermission = await localImageProvider.initialize();
if (hasPermission) {
await localImageProvider.cleanup();
// localImages = await localImageProvider.findLatest(50);
localAlbums = await localImageProvider.findAlbums(LocalAlbumType.all);
}
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.0.0"
version: "2.1.0"
matcher:
dependency: transitive
description:
Expand Down
39 changes: 36 additions & 3 deletions ios/Classes/SwiftLocalImageProviderPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum LocalImageProviderMethods: String {
case latest_images
case image_bytes
case video_file
case cleanup
case images_in_album
case albums
case has_permission
Expand Down Expand Up @@ -105,6 +106,8 @@ public class SwiftLocalImageProviderPlugin: NSObject, FlutterPlugin {
return
}
getVideoFile( localId, result)
case LocalImageProviderMethods.cleanup.rawValue:
cleanup( result)
default:
print("Unrecognized method: \(call.method)")
result( FlutterMethodNotImplemented)
Expand Down Expand Up @@ -295,9 +298,8 @@ public class SwiftLocalImageProviderPlugin: NSObject, FlutterPlugin {
}
}
}
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let tempDir = paths[0]
let outputFile = tempDir.appendingPathComponent(UUID().uuidString)
let tempPath = self.getTemporaryPath()
let outputFile = tempPath.appendingPathComponent(UUID().uuidString)
let finalFile = outputFile.appendingPathExtension("mov")
exportSession?.outputURL = finalFile
exportSession?.outputFileType = AVFileType.mov
Expand All @@ -306,8 +308,39 @@ public class SwiftLocalImageProviderPlugin: NSObject, FlutterPlugin {
}
});
}
else {
DispatchQueue.main.async {
flutterResult(FlutterError( code: LocalImageProviderErrors.imgNotFound.rawValue, message:"Video not found: \(id)", details: nil ))
}
}
}

private func cleanup( _ flutterResult: @escaping FlutterResult) {
let tempPath = getTemporaryPath()
guard let filePaths = try? FileManager.default.contentsOfDirectory(at: tempPath, includingPropertiesForKeys: nil, options: []) else { return }
for filePath in filePaths {
try? FileManager.default.removeItem(at: filePath)
}
DispatchQueue.main.async {
flutterResult( true )
}
}

private func getTemporaryPath() -> URL {
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let tempDir = paths[0]
let tempPath = tempDir.appendingPathComponent("csdcorp_lip")
if !FileManager.default.fileExists(atPath: tempPath.path) {
do {
try FileManager.default.createDirectory(atPath: tempPath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create folder in tmp directory")
NSLog("==> directory is: \(tempPath)")
}
}
return tempPath
}

private func getPhotoImage(_ id: String, _ pixelHeight: Int, _ pixelWidth: Int, _ flutterResult: @escaping FlutterResult) {
let fetchOptions = PHFetchOptions()
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: fetchOptions )
Expand Down
22 changes: 21 additions & 1 deletion lib/local_image_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ class LocalImageProvider {
return photoBytes;
}

/// Returns a temporary file path for the image.
/// Returns a temporary file path for the requested video.
///
/// Call this method to get a playable video file where [id] is from
/// a [LocalImage] that has `isVideo` true. These files can be
/// used for video playback using the `video_player` plugin
/// for example. These files should either be moved or deleted by
/// client code, or cleaned up occasionally using the [cleanup] method.
Future<String> videoFile(String id) async {
if (!_initWorked) {
throw LocalImageProviderNotInitializedException();
Expand All @@ -198,6 +203,21 @@ class LocalImageProvider {
return filePath;
}

/// Call this method to cleanup any temporary files that have been
/// created by the image provider.
///
/// After this method completes any file paths returned from
/// [videoFile] or other methods will no longer be valid. Only call
/// this method when you no longer depend on those files. Calling this
/// at program startup is safe, or at a point when you have finished
/// using all returned file paths.
Future cleanup() async {
if (!_initWorked) {
throw LocalImageProviderNotInitializedException();
}
await channel.invokeMethod('cleanup');
}

/// Resets the [totalLoadTime], [lastLaodTime], and [imgBytesLoaded]
/// stats to zero.
void resetStats() {
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: local_image_provider
description: A library for retrieving the metadata and contents of the images and albums on a mobile device.
version: 2.0.0
description: A library for retrieving the metadata and contents of the images, videos, and albums on a mobile device.
version: 2.1.0
homepage: https://github.com/csdcorp/local_image_provider

environment:
Expand Down
110 changes: 86 additions & 24 deletions test/local_image_provider_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import 'package:local_image_provider/local_image_provider.dart';
void main() {
LocalImageProvider localImageProvider;
bool initResponse;
bool cleanupResponse;
bool hasResponse;
bool pluginInvocation;
List<String> photoJsonList = [];
List<String> albumJsonList = [];
const String noSuchImageId = "noSuchImage";
const String firstImageId = "image1";
const String firstVideoId = "video1";
const String firstVideoPath = "/tmp/video1";
const String firstPhotoJson =
'{"id":"$firstImageId","creationDate":"2019-01-01 12:12Z","pixelWidth":1920,"pixelHeight":1024}';
const String secondPhotoJson =
Expand All @@ -33,6 +36,7 @@ void main() {
setUp(() {
initResponse = true;
hasResponse = true;
cleanupResponse = true;
List<int> imgInt = imageBytesStr.codeUnits;
imageBytes = Uint8List.fromList(imgInt);
pluginInvocation = false;
Expand All @@ -41,32 +45,52 @@ void main() {
localImageProvider.channel
.setMockMethodCallHandler((MethodCall methodCall) async {
pluginInvocation = true;
if (methodCall.method == "has_permission") {
return hasResponse;
} else if (methodCall.method == "initialize") {
return initResponse;
} else if (methodCall.method == "latest_images") {
return photoJsonList;
} else if (methodCall.method == "request_permission") {
return true;
} else if (methodCall.method == "image_bytes") {
if (null == methodCall.arguments) {}
String imgId = methodCall.arguments["id"];
if (noSuchImageId == imgId) {
throw PlatformException(
code: "imgNotFound", message: "$noSuchImageId not found");
}
return imageBytes;
} else if (methodCall.method == "albums") {
return albumJsonList;
} else if (methodCall.method == "images_in_album") {
if (methodCall.arguments["albumId"] == emptyAlbumId) {
return [];
} else {
switch (methodCall.method) {
case "has_permission":
return hasResponse;
break;
case "initialize":
return initResponse;
break;
case "cleanup":
return cleanupResponse;
break;
case "latest_images":
return photoJsonList;
}
break;
case "request_permission":
return true;
break;
case "image_bytes":
if (null == methodCall.arguments) {}
String imgId = methodCall.arguments["id"];
if (noSuchImageId == imgId) {
throw PlatformException(
code: "imgNotFound", message: "$noSuchImageId not found");
}
return imageBytes;
break;
case "albums":
return albumJsonList;
break;
case "video_file":
String imgId = methodCall.arguments["id"];
if (noSuchImageId == imgId) {
throw PlatformException(
code: "imgNotFound", message: "$noSuchImageId not found");
}
return firstVideoPath;
break;
case "images_in_album":
if (methodCall.arguments["albumId"] == emptyAlbumId) {
return [];
} else {
return photoJsonList;
}
break;
default:
return Future.value(true);
}
return Future.value(true);
});
});

Expand Down Expand Up @@ -246,6 +270,44 @@ void main() {
}
});

group('videoFile', () {
test('fails if not initialized', () async {
try {
await localImageProvider.videoFile(firstVideoId);
fail("Should have thrown");
} catch (e) {
// expected
}
});
test('Returns expected video file', () async {
await localImageProvider.initialize();
var path = await localImageProvider.videoFile(firstVideoId);
expect(path, firstVideoPath);
});
test('Handles video file not found', () async {
await localImageProvider.initialize();
try {
await localImageProvider.videoFile(noSuchImageId);
fail("Expected PlatformException");
} on PlatformException catch (e) {
expect(e.code, "imgNotFound");
}
});
});
group('cleanup', () {
test('fails if not initialized', () async {
try {
await localImageProvider.cleanup();
fail("Should have thrown");
} catch (e) {
// expected
}
});
test('works silently if initialized', () async {
await localImageProvider.initialize();
await localImageProvider.cleanup();
});
});
group('stats', () {
test('start at 0', () {
expect(localImageProvider.imgBytesLoaded, 0);
Expand Down

0 comments on commit 19c4510

Please sign in to comment.