mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
432 lines
14 KiB
Objective-C
432 lines
14 KiB
Objective-C
#import "TGMediaAssetsLegacyLibrary.h"
|
|
|
|
#import <AssetsLibrary/AssetsLibrary.h>
|
|
|
|
#import <LegacyComponents/TGObserverProxy.h>
|
|
|
|
@interface TGMediaAssetsLegacyLibrary ()
|
|
{
|
|
ALAssetsLibrary *_assetsLibrary;
|
|
TGObserverProxy *_assetsChangeObserver;
|
|
SPipe *_libraryChangePipe;
|
|
}
|
|
@end
|
|
|
|
@implementation TGMediaAssetsLegacyLibrary
|
|
|
|
- (instancetype)initForAssetType:(TGMediaAssetType)assetType
|
|
{
|
|
self = [super initForAssetType:assetType];
|
|
if (self != nil)
|
|
{
|
|
_assetsLibrary = [[ALAssetsLibrary alloc] init];
|
|
_assetsChangeObserver = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(assetsLibraryDidChange:) name:ALAssetsLibraryChangedNotification];
|
|
_libraryChangePipe = [[SPipe alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (SSignal *)assetWithIdentifier:(NSString *)identifier
|
|
{
|
|
if (identifier.length == 0)
|
|
return [SSignal fail:nil];
|
|
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary assetForURL:[NSURL URLWithString:identifier] resultBlock:^(ALAsset *asset)
|
|
{
|
|
if (asset != nil)
|
|
{
|
|
[subscriber putNext:[[TGMediaAsset alloc] initWithALAsset:asset]];
|
|
[subscriber putCompletion];
|
|
}
|
|
else
|
|
{
|
|
[subscriber putError:nil];
|
|
}
|
|
} failureBlock:^(__unused NSError *error)
|
|
{
|
|
[subscriber putError:nil];
|
|
}];
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
- (SSignal *)assetGroups
|
|
{
|
|
SSignal *(^groupsSignal)(void) = ^
|
|
{
|
|
return [[[[[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *assetsGroup, __unused BOOL *stop)
|
|
{
|
|
if (assetsGroup != nil)
|
|
{
|
|
if (self.assetType != TGMediaAssetAnyType)
|
|
[assetsGroup setAssetsFilter:[TGMediaAssetsLegacyLibrary _assetsFilterForAssetType:self.assetType]];
|
|
|
|
TGMediaAssetGroup *group = [[TGMediaAssetGroup alloc] initWithALAssetsGroup:assetsGroup];
|
|
[subscriber putNext:group];
|
|
}
|
|
else
|
|
{
|
|
[subscriber putCompletion];
|
|
}
|
|
} failureBlock:^(NSError *error)
|
|
{
|
|
[subscriber putError:error];
|
|
}];
|
|
|
|
return nil;
|
|
}] then:[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *assetsGroup, __unused BOOL *stop)
|
|
{
|
|
if (assetsGroup != nil)
|
|
{
|
|
if ([[assetsGroup valueForProperty:ALAssetsGroupPropertyType] integerValue] == ALAssetsGroupSavedPhotos)
|
|
{
|
|
TGMediaAssetGroup *group = [[TGMediaAssetGroup alloc] initWithALAssetsGroup:assetsGroup subtype:TGMediaAssetGroupSubtypeVideos];
|
|
[subscriber putNext:group];
|
|
[subscriber putCompletion];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[subscriber putCompletion];
|
|
}
|
|
} failureBlock:^(NSError *error)
|
|
{
|
|
[subscriber putError:error];
|
|
}];
|
|
|
|
return nil;
|
|
}]] reduceLeft:[[NSMutableArray alloc] init] with:^id(NSMutableArray *groups, id group)
|
|
{
|
|
[groups addObject:group];
|
|
return groups;
|
|
}] map:^NSMutableArray *(NSMutableArray *groups)
|
|
{
|
|
[groups sortUsingFunction:TGMediaAssetGroupComparator context:nil];
|
|
return groups;
|
|
}] startOn:_queue];
|
|
};
|
|
|
|
SSignal *updateSignal = [[self libraryChanged] mapToSignal:^SSignal *(__unused id change)
|
|
{
|
|
return groupsSignal();
|
|
}];
|
|
|
|
return [groupsSignal() then:updateSignal];
|
|
}
|
|
|
|
- (SSignal *)cameraRollGroup
|
|
{
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
|
|
{
|
|
if (group != nil)
|
|
{
|
|
if (self.assetType != TGMediaAssetAnyType)
|
|
[group setAssetsFilter:[TGMediaAssetsLegacyLibrary _assetsFilterForAssetType:self.assetType]];
|
|
|
|
[subscriber putNext:[[TGMediaAssetGroup alloc] initWithALAssetsGroup:group]];
|
|
[subscriber putCompletion];
|
|
|
|
if (stop != NULL)
|
|
*stop = true;
|
|
}
|
|
else
|
|
{
|
|
[subscriber putError:nil];
|
|
}
|
|
} failureBlock:^(NSError *error)
|
|
{
|
|
[subscriber putError:error];
|
|
}];
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
- (SSignal *)assetsOfAssetGroup:(TGMediaAssetGroup *)assetGroup reversed:(bool)reversed
|
|
{
|
|
NSParameterAssert(assetGroup);
|
|
|
|
SSignal *(^fetchSignal)(TGMediaAssetGroup *) = ^SSignal *(TGMediaAssetGroup *group)
|
|
{
|
|
return [[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
TGMediaAssetFetchResult *mediaFetchResult = [[TGMediaAssetFetchResult alloc] initForALAssetsReversed:reversed];
|
|
|
|
NSEnumerationOptions options = kNilOptions;
|
|
if (group.isReversed)
|
|
options = NSEnumerationReverse;
|
|
|
|
[group.backingAssetsGroup enumerateAssetsWithOptions:options usingBlock:^(ALAsset *asset, __unused NSUInteger index, __unused BOOL *stop)
|
|
{
|
|
if (asset != nil && (assetGroup.subtype != TGMediaAssetGroupSubtypeVideos || [[asset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]))
|
|
{
|
|
[mediaFetchResult _appendALAsset:asset];
|
|
}
|
|
}];
|
|
|
|
[subscriber putNext:mediaFetchResult];
|
|
[subscriber putCompletion];
|
|
|
|
return nil;
|
|
}] startOn:_queue];
|
|
};
|
|
|
|
SSignal *updateSignal = [[self libraryChanged] mapToSignal:^SSignal *(__unused id change)
|
|
{
|
|
return fetchSignal(assetGroup);
|
|
}];
|
|
|
|
return [fetchSignal(assetGroup) then:updateSignal];
|
|
}
|
|
|
|
- (SSignal *)updatedAssetsForAssets:(NSArray *)assets
|
|
{
|
|
SSignal *(^updatedAssetSignal)(TGMediaAsset *) = ^SSignal *(TGMediaAsset *asset)
|
|
{
|
|
return [[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary assetForURL:asset.url resultBlock:^(ALAsset *asset)
|
|
{
|
|
if (asset != nil)
|
|
{
|
|
TGMediaAsset *updatedAsset = [[TGMediaAsset alloc] initWithALAsset:asset];
|
|
[subscriber putNext:updatedAsset];
|
|
[subscriber putCompletion];
|
|
}
|
|
else
|
|
{
|
|
[subscriber putError:nil];
|
|
}
|
|
} failureBlock:^(__unused NSError *error)
|
|
{
|
|
[subscriber putError:nil];
|
|
}];
|
|
|
|
return nil;
|
|
}] catch:^SSignal *(__unused id error)
|
|
{
|
|
return [SSignal complete];
|
|
}];
|
|
};
|
|
|
|
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
|
for (TGMediaAsset *asset in assets)
|
|
[signals addObject:updatedAssetSignal(asset)];
|
|
|
|
SSignal *combinedSignal = nil;
|
|
for (SSignal *signal in signals)
|
|
{
|
|
if (combinedSignal == nil)
|
|
combinedSignal = signal;
|
|
else
|
|
combinedSignal = [combinedSignal then:signal];
|
|
}
|
|
|
|
return [combinedSignal reduceLeft:[[NSMutableArray alloc] init] with:^id(NSMutableArray *array, TGMediaAsset *updatedAsset)
|
|
{
|
|
[array addObject:updatedAsset];
|
|
return array;
|
|
}];
|
|
}
|
|
|
|
- (SSignal *)filterDeletedAssets:(NSArray *)assets
|
|
{
|
|
SSignal *(^assetDeletedSignal)(TGMediaAsset *) = ^SSignal *(TGMediaAsset *asset)
|
|
{
|
|
return [[[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary assetForURL:asset.url resultBlock:^(ALAsset *asset)
|
|
{
|
|
[subscriber putNext:@(asset != nil)];
|
|
[subscriber putCompletion];
|
|
} failureBlock:^(__unused NSError *error)
|
|
{
|
|
[subscriber putNext:@(false)];
|
|
[subscriber putCompletion];
|
|
}];
|
|
|
|
return nil;
|
|
}] filter:^bool(NSNumber *exists)
|
|
{
|
|
return !exists.boolValue;
|
|
}] map:^TGMediaAsset *(__unused id exists)
|
|
{
|
|
return asset;
|
|
}];
|
|
};
|
|
|
|
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
|
for (TGMediaAsset *asset in assets)
|
|
[signals addObject:assetDeletedSignal(asset)];
|
|
|
|
SSignal *combinedSignal = nil;
|
|
for (SSignal *signal in signals)
|
|
{
|
|
if (combinedSignal == nil)
|
|
combinedSignal = signal;
|
|
else
|
|
combinedSignal = [combinedSignal then:signal];
|
|
}
|
|
|
|
return [combinedSignal reduceLeft:[[NSMutableArray alloc] init] with:^id(NSMutableArray *array, TGMediaAsset *deletedAsset)
|
|
{
|
|
[array addObject:deletedAsset];
|
|
return array;
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)assetsLibraryDidChange:(NSNotification *)__unused notification
|
|
{
|
|
__strong TGMediaAssetsLegacyLibrary *strongSelf = self;
|
|
if (strongSelf != nil)
|
|
strongSelf->_libraryChangePipe.sink([SSignal single:@(true)]);
|
|
}
|
|
|
|
- (SSignal *)libraryChanged
|
|
{
|
|
return [[_libraryChangePipe.signalProducer() map:^SSignal *(id data) {
|
|
return [[SSignal single:data] delay:0.5 onQueue:_queue];
|
|
}] switchToLatest];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (SSignal *)saveAssetWithImage:(UIImage *)image
|
|
{
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error)
|
|
{
|
|
if (assetURL != nil && error == nil)
|
|
[subscriber putCompletion];
|
|
else
|
|
[subscriber putError:error];
|
|
}];
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
- (SSignal *)saveAssetWithImageData:(NSData *)imageData
|
|
{
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
[_assetsLibrary writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error)
|
|
{
|
|
if (assetURL != nil && error == nil)
|
|
[subscriber putCompletion];
|
|
else
|
|
[subscriber putError:error];
|
|
}];
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
- (SSignal *)_saveAssetWithUrl:(NSURL *)url isVideo:(bool)isVideo
|
|
{
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
void (^writeCompletionBlock)(NSURL *, NSError *) = ^(NSURL *assetURL, NSError *error)
|
|
{
|
|
if (assetURL != nil && error == nil)
|
|
[subscriber putCompletion];
|
|
else
|
|
[subscriber putError:error];
|
|
};
|
|
|
|
if (!isVideo)
|
|
{
|
|
NSData *data = [[NSData alloc] initWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:nil];
|
|
[_assetsLibrary writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:writeCompletionBlock];
|
|
}
|
|
else
|
|
{
|
|
[_assetsLibrary writeVideoAtPathToSavedPhotosAlbum:url completionBlock:writeCompletionBlock];
|
|
}
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
+ (ALAssetsFilter *)_assetsFilterForAssetType:(TGMediaAssetType)assetType
|
|
{
|
|
switch (assetType)
|
|
{
|
|
case TGMediaAssetPhotoType:
|
|
return [ALAssetsFilter allPhotos];
|
|
|
|
case TGMediaAssetVideoType:
|
|
return [ALAssetsFilter allVideos];
|
|
|
|
default:
|
|
return [ALAssetsFilter allAssets];
|
|
}
|
|
}
|
|
|
|
+ (SSignal *)authorizationStatusSignal
|
|
{
|
|
if (TGMediaLibraryCachedAuthorizationStatus != TGMediaLibraryAuthorizationStatusNotDetermined)
|
|
return [SSignal single:@(TGMediaLibraryCachedAuthorizationStatus)];
|
|
|
|
return [SSignal single:@(TGMediaLibraryAuthorizationStatusAuthorized)];
|
|
}
|
|
|
|
+ (void)requestAuthorizationForAssetType:(TGMediaAssetType)assetType completion:(void (^)(TGMediaLibraryAuthorizationStatus, TGMediaAssetGroup *))completion
|
|
{
|
|
TGMediaLibraryAuthorizationStatus currentStatus = [self authorizationStatus];
|
|
if (currentStatus == TGMediaLibraryAuthorizationStatusDenied || currentStatus == TGMediaLibraryAuthorizationStatusRestricted)
|
|
{
|
|
completion(currentStatus, nil);
|
|
}
|
|
else
|
|
{
|
|
TGMediaAssetsLibrary *library = [self libraryForAssetType:assetType];
|
|
[[library cameraRollGroup] startWithNext:^(TGMediaAssetGroup *group)
|
|
{
|
|
TGMediaLibraryCachedAuthorizationStatus = [self authorizationStatus];
|
|
completion([self authorizationStatus], group);
|
|
} error:^(__unused id error)
|
|
{
|
|
TGMediaLibraryCachedAuthorizationStatus = [self authorizationStatus];
|
|
completion([self authorizationStatus], nil);
|
|
} completed:nil];
|
|
}
|
|
}
|
|
|
|
+ (TGMediaLibraryAuthorizationStatus)authorizationStatus
|
|
{
|
|
return [self _authorizationStatusForALAuthorizationStatus:[ALAssetsLibrary authorizationStatus]];
|
|
}
|
|
|
|
+ (TGMediaLibraryAuthorizationStatus)_authorizationStatusForALAuthorizationStatus:(ALAuthorizationStatus)status
|
|
{
|
|
switch (status)
|
|
{
|
|
case ALAuthorizationStatusRestricted:
|
|
return TGMediaLibraryAuthorizationStatusRestricted;
|
|
|
|
case ALAuthorizationStatusDenied:
|
|
return TGMediaLibraryAuthorizationStatusDenied;
|
|
|
|
case ALAuthorizationStatusAuthorized:
|
|
return TGMediaLibraryAuthorizationStatusAuthorized;
|
|
|
|
default:
|
|
return TGMediaLibraryAuthorizationStatusNotDetermined;
|
|
}
|
|
}
|
|
|
|
@end
|