From 2cfa2f638606c68c7aa26f3120dbedaac8d158fb Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 27 Sep 2023 10:59:42 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20Add=20locks=20to=20the=20image?= =?UTF-8?q?=20provider=20(#993)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++ lib/src/internal/image_provider.dart | 113 +++++++++++++++------------ pubspec.yaml | 2 +- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 084945bd..34ba6a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ that can be found in the LICENSE file. --> # CHANGELOG +## 2.7.2 + +### Improvements + +- Add locks to the image provider. + ## 2.7.1 ### Fixes diff --git a/lib/src/internal/image_provider.dart b/lib/src/internal/image_provider.dart index 2c72e0f5..99ebfa9c 100644 --- a/lib/src/internal/image_provider.dart +++ b/lib/src/internal/image_provider.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. +import 'dart:async'; import 'dart:io'; import 'dart:typed_data' as typed_data; import 'dart:ui' as ui; @@ -14,6 +15,8 @@ import '../types/thumbnail.dart'; import 'constants.dart'; import 'enums.dart'; +final _providerLocks = >{}; + /// The [ImageProvider] that handles [AssetEntity]. /// /// Only support [AssetType.image] and [AssetType.video], @@ -91,59 +94,73 @@ class AssetEntityImageProvider extends ImageProvider { Future _loadAsync( AssetEntityImageProvider key, DecoderCallback decode, // ignore: deprecated_member_use - ) async { - try { - assert(key == this); - if (key.entity.type == AssetType.audio || - key.entity.type == AssetType.other) { - throw UnsupportedError( - 'Image data for the ${key.entity.type} is not supported.', - ); - } + ) { + if (_providerLocks.containsKey(key)) { + return _providerLocks[key]!.future; + } + final lock = Completer(); + _providerLocks[key] = lock; + Future(() async { + try { + assert(key == this); + if (key.entity.type == AssetType.audio || + key.entity.type == AssetType.other) { + throw UnsupportedError( + 'Image data for the ${key.entity.type} is not supported.', + ); + } - // Define the image type. - final ImageFileType type; - if (key.imageFileType == ImageFileType.other) { - // Assume the title is invalid here, try again with the async getter. - type = _getType(await key.entity.titleAsync); - } else { - type = key.imageFileType; - } + // Define the image type. + final ImageFileType type; + if (key.imageFileType == ImageFileType.other) { + // Assume the title is invalid here, try again with the async getter. + type = _getType(await key.entity.titleAsync); + } else { + type = key.imageFileType; + } - typed_data.Uint8List? data; - if (isOriginal) { - if (key.entity.type == AssetType.video) { - data = await key.entity.thumbnailData; - } else if (type == ImageFileType.heic) { - data = await (await key.entity.file)?.readAsBytes(); + typed_data.Uint8List? data; + if (isOriginal) { + if (key.entity.type == AssetType.video) { + data = await key.entity.thumbnailData; + } else if (type == ImageFileType.heic) { + data = await (await key.entity.file)?.readAsBytes(); + } else { + data = await key.entity.originBytes; + } } else { - data = await key.entity.originBytes; + data = await key.entity.thumbnailDataWithOption( + _thumbOption(thumbnailSize!), + ); } - } else { - data = await key.entity.thumbnailDataWithOption( - _thumbOption(thumbnailSize!), - ); - } - if (data == null) { - throw StateError('The data of the entity is null: $entity'); - } - return decode(data); - } catch (e, s) { - if (kDebugMode) { - FlutterError.presentError( - FlutterErrorDetails( - exception: e, - stack: s, - library: PMConstants.libraryName, - ), - ); + if (data == null) { + throw StateError('The data of the entity is null: $entity'); + } + return decode(data); + } catch (e, s) { + if (kDebugMode) { + FlutterError.presentError( + FlutterErrorDetails( + exception: e, + stack: s, + library: PMConstants.libraryName, + ), + ); + } + // Depending on where the exception was thrown, the image cache may not + // have had a chance to track the key in the cache at all. + // Schedule a microtask to give the cache a chance to add the key. + Future.microtask(() => _evictCache(key)); + rethrow; } - // Depending on where the exception was thrown, the image cache may not - // have had a chance to track the key in the cache at all. - // Schedule a microtask to give the cache a chance to add the key. - Future.microtask(() => _evictCache(key)); - rethrow; - } + }).then((codec) { + lock.complete(codec); + }).catchError((e, s) { + lock.completeError(e, s); + }).whenComplete(() { + _providerLocks.remove(key); + }); + return lock.future; } ThumbnailOption _thumbOption(ThumbnailSize size) { diff --git a/pubspec.yaml b/pubspec.yaml index c8d0639b..45b1e7ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: photo_manager description: A Flutter plugin that provides assets abstraction management APIs on Android, iOS, and macOS. repository: https://github.com/fluttercandies/flutter_photo_manager -version: 2.7.1 +version: 2.7.2 environment: sdk: ">=2.13.0 <3.0.0" From f507880f7b6d573dcfeb9d73b301ccc277b5a654 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 2 Oct 2023 16:47:44 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=92=9A=20Fix=20`Podfile`=20when=20fin?= =?UTF-8?q?ding=20podspecs=20(#998)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check-compatibility.yml | 36 +++++++++++------------ example/ios/Podfile | 6 ++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.github/workflows/check-compatibility.yml b/.github/workflows/check-compatibility.yml index 9a521d4a..568674f7 100644 --- a/.github/workflows/check-compatibility.yml +++ b/.github/workflows/check-compatibility.yml @@ -5,14 +5,14 @@ on: push: jobs: - build-for-android-3-10-6: - name: Build for android with 3.10.6 on ubuntu-latest + build-for-android: + name: Build for Android on ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - uses: actions/setup-java@v3 @@ -32,14 +32,14 @@ jobs: - run: flutter build apk --debug working-directory: ${{ github.workspace }}/new_project name: Build example - build-for-ios-3-10-6: - name: Build for ios with 3.10.6 on macos-latest + build-for-ios: + name: Build for ios on macos-latest runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - run: flutter doctor -v @@ -60,14 +60,14 @@ jobs: - run: flutter build ios --release --no-codesign working-directory: ${{ github.workspace }}/new_project name: Build example - build-for-web-3-10-6: - name: Build for web with 3.10.6 on ubuntu-latest + build-for-web: + name: Build for Web on ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - run: flutter doctor -v @@ -83,14 +83,14 @@ jobs: - run: flutter build web --release working-directory: ${{ github.workspace }}/new_project name: Build example - build-for-linux-3-10-6: - name: Build for linux with 3.10.6 on ubuntu-latest + build-for-linux: + name: Build for Linux on ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install required packages @@ -112,14 +112,14 @@ jobs: - run: flutter build linux --release working-directory: ${{ github.workspace }}/new_project name: Build example - build-for-macos-3-10-6: - name: Build for macos with 3.10.6 on macos-latest + build-for-macos: + name: Build for macOS on macos-latest runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - run: flutter doctor -v @@ -140,14 +140,14 @@ jobs: - run: flutter build macos --release working-directory: ${{ github.workspace }}/new_project name: Build example - build-for-windows-3-10-6: - name: Build for windows with 3.10.6 on windows-latest + build-for-windows: + name: Build for Windows on windows-latest runs-on: windows-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.10.6 + channel: 'stable' cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - run: flutter doctor -v diff --git a/example/ios/Podfile b/example/ios/Podfile index fb9d86a0..21ed6aee 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,3 +1,5 @@ +require 'fileutils' + # Uncomment this line to define a global platform for your project platform :ios, '11.0' @@ -48,6 +50,10 @@ def install_plugin_pods(application_path = nil, relative_symlink_dir, platform) plugin_path = plugin_hash['path'] if (plugin_name && plugin_path) specPath = "#{plugin_path}/#{platform}/#{plugin_name}.podspec" + unless File.exist?(specPath) + puts "#{specPath} does not exists. Fallback to the Darwin one." + specPath = "#{plugin_path}/darwin/#{plugin_name}.podspec" + end pod plugin_name, :path => specPath end end From bc582920b06c4423798d3713019eb71d15859383 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 2 Oct 2023 17:11:55 +0800 Subject: [PATCH 3/5] [Android] Correct the key when fetching video info with MMR (#997) --- CHANGELOG.md | 4 ++++ .../com/fluttercandies/photo_manager/core/utils/IDBUtils.kt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ba6a2f..f6b7d4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ that can be found in the LICENSE file. --> ## 2.7.2 +### Fixes + +- Correct the key when fetching video info with MMR on Android. (#997) + ### Improvements - Add locks to the image provider. diff --git a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/IDBUtils.kt b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/IDBUtils.kt index 059ac9d8..cd741e07 100644 --- a/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/IDBUtils.kt +++ b/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/IDBUtils.kt @@ -197,7 +197,7 @@ interface IDBUtils { mmr.setDataSource(path) width = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) ?.toInt() ?: 0 - height = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) + height = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) ?.toInt() ?: 0 orientation = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) From e096d5e1a53030a2c2aba88d2584ac60eb8f3e85 Mon Sep 17 00:00:00 2001 From: xick Date: Mon, 2 Oct 2023 21:36:00 +0200 Subject: [PATCH 4/5] [IOS] FIX: Return original filename instead of FullSizeRender (#990) --- CHANGELOG.md | 2 ++ ios/Classes/core/PHAsset+PM_COMMON.h | 1 + ios/Classes/core/PHAsset+PM_COMMON.m | 27 ++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b7d4cb..3a6611ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ that can be found in the LICENSE file. --> ### Fixes - Correct the key when fetching video info with MMR on Android. (#997) +- Retrieve original media instead of one with adjustments/filters for subtype files on iOS. (#976) +- Returns original file name instead of `FullSizeRender.*` if this has adjustments on iOS. (#976) ### Improvements diff --git a/ios/Classes/core/PHAsset+PM_COMMON.h b/ios/Classes/core/PHAsset+PM_COMMON.h index 0a4273e2..9a91c41d 100644 --- a/ios/Classes/core/PHAsset+PM_COMMON.h +++ b/ios/Classes/core/PHAsset+PM_COMMON.h @@ -29,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString*)mimeType; - (BOOL)isAdjust; - (PHAssetResource *)getAdjustResource; +- (PHAssetResource *)getUntouchedResource; - (void)requestAdjustedData:(void (^)(NSData *_Nullable result))block; - (PHAssetResource *)getLivePhotosResource; diff --git a/ios/Classes/core/PHAsset+PM_COMMON.m b/ios/Classes/core/PHAsset+PM_COMMON.m index aafee1e1..f0946893 100644 --- a/ios/Classes/core/PHAsset+PM_COMMON.m +++ b/ios/Classes/core/PHAsset+PM_COMMON.m @@ -68,7 +68,7 @@ - (NSString *)originalFilenameWithSubtype:(int)subtype { return [self getLivePhotosResource].originalFilename; } } - PHAssetResource *resource = [self getAdjustResource]; + PHAssetResource *resource = [self getUntouchedResource]; if (resource) { return resource.originalFilename; } @@ -120,6 +120,31 @@ - (BOOL)videoIsAdjust:(NSArray *)resources { return NO; } +- (PHAssetResource *)getUntouchedResource { + NSArray *resources = [PHAssetResource assetResourcesForAsset:self]; + if (resources.count == 0) { + return nil; + } + + if (resources.count == 1) { + return resources[0]; + } + + for (PHAssetResource *res in resources) { + if (self.mediaType == PHAssetMediaTypeImage + && res.type == PHAssetResourceTypePhoto) { + return res; + } + + if (self.mediaType == PHAssetMediaTypeVideo + && res.type == PHAssetResourceTypeVideo) { + return res; + } + } + + return nil; +} + - (PHAssetResource *)getAdjustResource { NSArray *resources = [PHAssetResource assetResourcesForAsset:self]; if (resources.count == 0) { From b3f27d3ba3093c6852d3d7e4b01392b7c6ce09d7 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 4 Oct 2023 14:32:35 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=A7=20Update=20`Podfile`s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/ios/Podfile | 12 +++++------- example/macos/Podfile | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/example/ios/Podfile b/example/ios/Podfile index 21ed6aee..0cf927fd 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,3 @@ -require 'fileutils' - # Uncomment this line to define a global platform for your project platform :ios, '11.0' @@ -48,12 +46,12 @@ def install_plugin_pods(application_path = nil, relative_symlink_dir, platform) plugin_pods.each do |plugin_hash| plugin_name = plugin_hash['name'] plugin_path = plugin_hash['path'] + # iOS and macOS code can be shared in "darwin" directory, otherwise + # respectively in "ios" or "macos" directories. + shared_darwin_source = plugin_hash.fetch('shared_darwin_source', false) + platform_directory = shared_darwin_source ? 'darwin' : platform if (plugin_name && plugin_path) - specPath = "#{plugin_path}/#{platform}/#{plugin_name}.podspec" - unless File.exist?(specPath) - puts "#{specPath} does not exists. Fallback to the Darwin one." - specPath = "#{plugin_path}/darwin/#{plugin_name}.podspec" - end + specPath = "#{plugin_path}/#{platform_directory}/#{plugin_name}.podspec" pod plugin_name, :path => specPath end end diff --git a/example/macos/Podfile b/example/macos/Podfile index 9ec46f8c..9ce47397 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -26,11 +26,42 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_macos_podfile_setup +def install_plugin_pods(application_path = nil, relative_symlink_dir, platform) + # defined_in_file is set by CocoaPods and is a Pathname to the Podfile. + application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file) + raise 'Could not find application path' unless application_path + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + + symlink_dir = File.expand_path(relative_symlink_dir, application_path) + system('rm', '-rf', symlink_dir) # Avoid the complication of dependencies like FileUtils. + + symlink_plugins_dir = File.expand_path('plugins', symlink_dir) + system('mkdir', '-p', symlink_plugins_dir) + + plugins_file = File.join(application_path, '..', '.flutter-plugins-dependencies') + plugin_pods = flutter_parse_plugins_file(plugins_file, platform) + plugin_pods.each do |plugin_hash| + plugin_name = plugin_hash['name'] + plugin_path = plugin_hash['path'] + # iOS and macOS code can be shared in "darwin" directory, otherwise + # respectively in "ios" or "macos" directories. + shared_darwin_source = plugin_hash.fetch('shared_darwin_source', false) + platform_directory = shared_darwin_source ? 'darwin' : platform + if (plugin_name && plugin_path) + specPath = "#{plugin_path}/#{platform_directory}/#{plugin_name}.podspec" + pod plugin_name, :path => specPath + end + end +end + target 'Runner' do use_frameworks! use_modular_headers! - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + flutter_install_macos_engine_pod(File.dirname(File.realpath(__FILE__))) + install_plugin_pods(File.dirname(File.realpath(__FILE__)), '.symlinks', 'macos') end post_install do |installer|