compatibilty for plugins is now fetched from plugin repository

This commit is contained in:
Michael Starke
2018-10-10 19:23:45 +02:00
parent 74b06ed195
commit 0921cd39d2
19 changed files with 559 additions and 66 deletions

View File

@@ -148,6 +148,8 @@
4C6F228C19A4AA700012310C /* MPAutotypeDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C6F228B19A4AA700012310C /* MPAutotypeDelay.m */; };
4C701CBC178618A000581B88 /* 12_RemoteTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4C701CBB178618A000581B88 /* 12_RemoteTemplate.pdf */; };
4C7155D81A10DB6D00979307 /* IconSelection.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C7155DA1A10DB6D00979307 /* IconSelection.xib */; };
4C71BCB42167B75900B4CBDA /* MPTestPluginVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */; };
4C71BCB72167B79C00B4CBDA /* MPPluginVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */; };
4C735FC02035FCBF00708D53 /* MPPluginEntryActionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */; };
4C73B6F1215E64A7009787F7 /* MPWelcomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C73B6EF215E64A7009787F7 /* MPWelcomeViewController.m */; };
4C76155C1764C04C0015A1A6 /* GeneralSettings.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C76155E1764C04C0015A1A6 /* GeneralSettings.xib */; };
@@ -183,6 +185,7 @@
4C7BD07619FE94C900C7AA5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C7BD07519FE94C900C7AA5C /* Assets.xcassets */; };
4C7F8B681A10B68400CCB83D /* WelcomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C7F8B6A1A10B68400CCB83D /* WelcomeView.xib */; };
4C80304A1E2FBAA300133E4C /* MPTestKeyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */; };
4C81867D216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C81867C216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m */; };
4C82046A1FCDC07800EB24A4 /* MPPickfieldViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8204681FCDC07800EB24A4 /* MPPickfieldViewController.m */; };
4C82046E1FCDC8A100EB24A4 /* MPPickfieldTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C82046D1FCDC8A100EB24A4 /* MPPickfieldTableModel.m */; };
4C83814215BF4677001AE468 /* MPDocumentWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C83814115BF4677001AE468 /* MPDocumentWindowController.m */; };
@@ -573,6 +576,9 @@
4C7155E81A10DB7700979307 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/IconSelection.strings; sourceTree = "<group>"; };
4C7155EA1A10DB7800979307 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/IconSelection.strings; sourceTree = "<group>"; };
4C7155EC1A10DB7900979307 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/IconSelection.strings; sourceTree = "<group>"; };
4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPTestPluginVersion.m; sourceTree = "<group>"; };
4C71BCB52167B79C00B4CBDA /* MPPluginVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginVersion.h; sourceTree = "<group>"; };
4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginVersion.m; sourceTree = "<group>"; };
4C735FBE2035FCBF00708D53 /* MPPluginEntryActionContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginEntryActionContext.h; sourceTree = "<group>"; };
4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginEntryActionContext.m; sourceTree = "<group>"; };
4C73B6EE215E64A7009787F7 /* MPWelcomeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPWelcomeViewController.h; sourceTree = "<group>"; };
@@ -643,6 +649,8 @@
4C7F8B7A1A10B69700CCB83D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/WelcomeView.strings; sourceTree = "<group>"; };
4C7F8B7C1A10B69800CCB83D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/WelcomeView.strings; sourceTree = "<group>"; };
4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestKeyMapper.m; sourceTree = "<group>"; };
4C81867B216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginRepositoryItemVersionInfo.h; sourceTree = "<group>"; };
4C81867C216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginRepositoryItemVersionInfo.m; sourceTree = "<group>"; };
4C8204671FCDC07800EB24A4 /* MPPickfieldViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPickfieldViewController.h; sourceTree = "<group>"; };
4C8204681FCDC07800EB24A4 /* MPPickfieldViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPickfieldViewController.m; sourceTree = "<group>"; };
4C82046C1FCDC8A100EB24A4 /* MPPickfieldTableModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPickfieldTableModel.h; sourceTree = "<group>"; };
@@ -1194,6 +1202,7 @@
4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */,
4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */,
4C8F0C721FCF1B7A00BE157F /* MPTestPickcharsParser.m */,
4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */,
4C45FB1F178E09ED0010007D /* Supporting Files */,
);
path = MacPassTests;
@@ -1663,8 +1672,12 @@
4CA78BFF1FD58C92003C8560 /* MPPluginRepository.m */,
4CAD338D205169D30068587E /* MPPluginRepositoryItem.h */,
4CAD338E205169D30068587E /* MPPluginRepositoryItem.m */,
4C81867B216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.h */,
4C81867C216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m */,
4C735FBE2035FCBF00708D53 /* MPPluginEntryActionContext.h */,
4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */,
4C71BCB52167B79C00B4CBDA /* MPPluginVersion.h */,
4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */,
);
name = Plugin;
path = MacPass;
@@ -1913,6 +1926,7 @@
files = (
4C45FB2D178E0BCB0010007D /* MPDatabaseLoading.m in Sources */,
4C8DEAA21C314D2C00D24C32 /* MPTestAutotypeDelay.m in Sources */,
4C71BCB42167B75900B4CBDA /* MPTestPluginVersion.m in Sources */,
4C8F0C731FCF1B7A00BE157F /* MPTestPickcharsParser.m in Sources */,
4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */,
4C6BC6601A36717E00BDDF3D /* MPDatabaseSearch.m in Sources */,
@@ -1966,6 +1980,7 @@
4C888C9316EB6F5E003D34A1 /* MPToolbarItem.m in Sources */,
4CA182741F963FF600DD4A4A /* MPTitlebarColorAccessoryViewController.m in Sources */,
4C888C9716EB754B003D34A1 /* MPActionHelper.m in Sources */,
4C81867D216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m in Sources */,
4CDA35751EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m in Sources */,
4CE39ABF16ECE34A000FE29D /* MPIconSelectViewController.m in Sources */,
4CE39AC416ECE4F7000FE29D /* MPIconImageView.m in Sources */,
@@ -2034,6 +2049,7 @@
4C1FA07B18231900003A3F8C /* MPDocument+Autotype.m in Sources */,
4C4B7EE917A45EC6000234C7 /* MPDatePickingViewController.m in Sources */,
4C4B7EEE17A467E1000234C7 /* MPGroupInspectorViewController.m in Sources */,
4C71BCB72167B79C00B4CBDA /* MPPluginVersion.m in Sources */,
4C7B63721C0CB51F00D7038C /* TTTCryptographyTransformers.m in Sources */,
4C4B7EF317A467FC000234C7 /* MPEntryInspectorViewController.m in Sources */,
4C1BDF2B1E4392640012A3F0 /* MPPluginDataViewController.m in Sources */,

View File

@@ -189,7 +189,7 @@ Gw
<constraint firstItem="6" firstAttribute="leading" secondItem="9" secondAttribute="leading" id="zeJ-6i-fY3"/>
</constraints>
</view>
<point key="canvasLocation" x="-226" y="45"/>
<point key="canvasLocation" x="-312" y="-21"/>
</window>
</objects>
<resources>

View File

@@ -183,7 +183,7 @@ NSString *const MPDidChangeStoredKeyFilesSettings = @"com.hicknhack.macpass.MPDi
[MPLockDaemon defaultDaemon];
[MPAutotypeDaemon defaultDaemon];
/* Create Plugin Manager */
[MPPluginHost sharedHost];
[MPPluginHost.sharedHost loadPlugins];
#if !defined(DEBUG) && !defined(NO_SPARKLE)
/* Disable updates if in debug or nosparkle */
[SUUpdater sharedUpdater];

View File

@@ -196,17 +196,6 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
}
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError **)outError {
/* FIXME: Lockfile handling
self.lockFileURL = [url URLByAppendingPathExtension:@"lock"];
if([[NSFileManager defaultManager] fileExistsAtPath:[_lockFileURL path]]) {
self.readOnly = YES;
}
else {
[[NSFileManager defaultManager] createFileAtPath:[_lockFileURL path] contents:nil attributes:nil];
_didLockFile = YES;
self.readOnly = NO;
}
*/
/*
Delete our old Tree, and just grab the data
*/
@@ -939,7 +928,7 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
- (void)_cleanupLock {
if(_didLockFile) {
[[NSFileManager defaultManager] removeItemAtURL:_lockFileURL error:nil];
[NSFileManager.defaultManager removeItemAtURL:_lockFileURL error:nil];
_didLockFile = NO;
}
}
@@ -971,11 +960,11 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
#pragma mark MPModelChangeObserving
- (void)willChangeModelProperty {
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentWillChangeModelPropertyNotification object:self];
[NSNotificationCenter.defaultCenter postNotificationName:MPDocumentWillChangeModelPropertyNotification object:self];
}
- (void)didChangeModelProperty {
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidChangeModelPropertyNotification object:self];
[NSNotificationCenter.defaultCenter postNotificationName:MPDocumentDidChangeModelPropertyNotification object:self];
}
@end

View File

@@ -37,7 +37,7 @@
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:didRecoverSelector]];
__block void *contextInfoCopy = contextInfo;
if(error.code == MPErrorNoPasswordOrKeyFile) {
if([delegate isKindOfClass:[MPDocument class]]) {
if([delegate isKindOfClass:MPDocument.class]) {
MPDocument *document = delegate;
BOOL didRecover = NO;
if(recoveryOptionIndex == 0) {
@@ -59,7 +59,6 @@
}
}
}
}
/* Given that an error alert has been presented applicaton-modally to the user, and the user has chosen one of the error's recovery options, attempt recovery from the error, and return YES if error recovery was completely successful, NO otherwise. The recovery option index is an index into the error's array of localized recovery options.

View File

@@ -35,7 +35,8 @@ FOUNDATION_EXPORT NSString *const MPPluginUnkownVersion;
@property (copy, readonly) NSString *identifier;
@property (copy, readonly) NSString *name;
@property (copy, readonly) NSString *version;
@property (nonatomic, copy, readonly, nullable) NSString *humanVersionString;
@property (nonatomic, copy, readonly) NSString *versionString;
@property (nonatomic, strong, readonly) NSBundle *bundle;
/**

View File

@@ -68,9 +68,13 @@ NSString *const MPPluginUnkownVersion = @"unkown.plugin.version";
return nil == name ? @"Unkown Plugin" : name;
}
- (NSString *)version {
- (NSString *)humanVersionString {
return self.bundle.infoDictionary[@"CFBundleShortVersionString"];
}
- (NSString *)versionString {
if(self.bundle) {
NSString *humanVersion = self.bundle.infoDictionary[@"CFBundleShortVersionString"];
NSString *humanVersion = self.humanVersionString;
NSString *version = self.bundle.infoDictionary[(NSString *)kCFBundleVersionKey];
if(humanVersion && version) {
return [NSString stringWithFormat:@"%@ (%@)", humanVersion, version];

View File

@@ -48,6 +48,8 @@ FOUNDATION_EXPORT NSString *const MPPluginHostPluginBundleIdentifiyerKey;
- (void)disablePlugin:(MPPlugin *)plugin;
- (void)enablePlugin:(MPPlugin *)plugin;
- (void)loadPlugins;
/*
- (NSArray <MPPlugin __kindof*>*)autotypePlugins;
- (NSArray <MPPlugin __kindof*>*)entryContextMenuPlugins;

View File

@@ -26,6 +26,8 @@
#import "MPPlugin_Private.h"
#import "MPPluginConstants.h"
#import "MPPluginEntryActionContext.h"
#import "MPPluginRepository.h"
#import "MPPluginRepositoryItem.h"
#import "NSApplication+MPAdditions.h"
#import "MPSettingsHelper.h"
@@ -79,8 +81,6 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun
_entryActionPluginIdentifiers = [[NSMutableArray alloc] init];
_customAttributePluginIdentifiers = [[NSMutableArray alloc] init];
[self _loadPlugins];
[self bind:NSStringFromSelector(@selector(loadUnsecurePlugins))
toObject:NSUserDefaultsController.sharedUserDefaultsController
withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins]
@@ -94,7 +94,7 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun
}
- (NSString *)version {
NSString *version = NSBundle.mainBundle.infoDictionary[(NSString *)kCFBundleVersionKey];
NSString *version = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"];
return version;
}
@@ -131,7 +131,13 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun
#pragma mark - Plugin Loading
- (void)_loadPlugins {
- (void)loadPlugins {
[MPPluginRepository.defaultRepository fetchRepositoryDataCompletionHandler:^(NSArray<MPPluginRepositoryItem *> * _Nonnull availablePlugins) {
[self _loadPlugins:availablePlugins];
}];
}
- (void)_loadPlugins:(NSArray<MPPluginRepositoryItem *> *)availablePlugins {
NSURL *appSupportDir = [NSApp applicationSupportDirectoryURL:YES];
NSError *error;
NSLog(@"Looking for external plugins at %@.", appSupportDir.path);
@@ -190,6 +196,11 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun
continue;
}
if(![self _isCompatiblePluginBundle:pluginBundle avaiablePlugins:availablePlugins ]) {
[self _addPluginForBundle:pluginBundle error:NSLocalizedString(@"PLUGIN_ERROR_HOST_VERSION_NOT_SUPPORTED", "Plugin is not with this version of MacPass")];
continue;
}
if(![pluginBundle loadAndReturnError:&error]) {
NSLog(@"Bundle Loading Error %@ %@", error.localizedDescription, error.localizedFailureReason);
[self _addPluginForBundle:pluginBundle error:error.localizedDescription];
@@ -249,6 +260,17 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun
return NO;
}
- (BOOL)_isCompatiblePluginBundle:(NSBundle *)bundle avaiablePlugins:(NSArray<MPPluginRepositoryItem *> *)availablePlugins {
MPPluginRepositoryItem *repoItem;
for(MPPluginRepositoryItem *item in availablePlugins) {
if([item.bundleIdentifier isEqualToString:bundle.bundleIdentifier]) {
repoItem = item;
}
}
NSString *shortVersion = bundle.infoDictionary[@"CFBundleShortVersionString"];
return [repoItem isPluginVersion:shortVersion compatibleWithHost:self];
}
- (BOOL)_isValidPluginURL:(NSURL *)url {
return (NSOrderedSame == [url.pathExtension compare:MPPluginFileExtension options:NSCaseInsensitiveSearch]);
}

View File

@@ -20,6 +20,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
NS_ASSUME_NONNULL_BEGIN
@import Foundation;
@class MPPluginRepositoryItem;
@@ -27,7 +29,9 @@
@interface MPPluginRepository : NSObject
@property (class, strong, readonly) MPPluginRepository *defaultRepository;
@property (nonatomic, copy) NSArray<MPPluginRepositoryItem *> *availablePlugins;
- (void)fetchRepositoryDataCompletionHandler:(void (^)(NSArray<MPPluginRepositoryItem *> *availablePlugins))completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -24,9 +24,16 @@
#import "MPConstants.h"
#import "MPPluginRepositoryItem.h"
@implementation MPPluginRepository
const NSTimeInterval MPPluginRepositoryCacheTimeOut = 60*3; // 1 Minute cache time
@dynamic availablePlugins;
@interface MPPluginRepository ()
@property NSTimeInterval lastPluginCheckTime;
@property BOOL didLoadData;
@end
@implementation MPPluginRepository
+ (instancetype)defaultRepository {
static MPPluginRepository *instance;
@@ -39,26 +46,48 @@
- (instancetype)init {
self = [super init];
if(self) {
self.lastPluginCheckTime = NSDate.distantPast.timeIntervalSinceReferenceDate;
}
return self;
}
- (NSArray<MPPluginRepositoryItem *> *)availablePlugins {
- (void)fetchRepositoryDataCompletionHandler:(void (^)(NSArray<MPPluginRepositoryItem *> * _Nonnull))completionHandler {
NSString *urlString = NSBundle.mainBundle.infoDictionary[MPBundlePluginRepositoryURLKey];
if(!urlString) {
return @[];
if(completionHandler) {
completionHandler(@[]);
}
return;
}
NSURL *jsonURL = [NSURL URLWithString:urlString];
if(!jsonURL) {
return @[];
if(completionHandler) {
completionHandler(@[]);
}
NSError *error;
NSData *jsonData = [NSData dataWithContentsOfURL:jsonURL options:0 error:&error];
if(!jsonData) {
return @[];
return;
}
NSURLSessionTask *downloadTask = [NSURLSession.sharedSession dataTaskWithURL:jsonURL completionHandler:^(NSData * _Nullable jsonData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(![response isKindOfClass:NSHTTPURLResponse.class]) {
if(completionHandler) {
completionHandler(@[]);
}
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode != 200 || jsonData.length == 0) {
if(completionHandler) {
completionHandler(@[]);
}
return;
}
id jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if(!jsonRoot || ![jsonRoot isKindOfClass:NSArray.class]) {
return @[];
if(completionHandler) {
completionHandler(@[]);
}
return;
}
NSMutableArray *items = [[NSMutableArray alloc] init];
for(id item in jsonRoot) {
@@ -70,7 +99,12 @@
[items addObject:pluginItem];
}
}
return [items copy];
if(completionHandler) {
completionHandler([items copy]);
}
}];
[downloadTask resume];
}
@end

View File

@@ -22,18 +22,26 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class MPPluginHost;
@interface MPPluginRepositoryItem : NSObject
@property (copy,readonly) NSString *name;
@property (copy,readonly) NSString *currentVersion;
@property (copy,readonly) NSString *descriptionText;
@property (copy,readonly) NSURL *sourceURL;
@property (copy,readonly) NSURL *downloadURL;
@property (copy,readonly) NSURL *bundleIdentifier;
@property (copy,readonly, nullable) NSString *name;
@property (copy,readonly, nullable) NSString *currentVersion;
@property (copy,readonly, nullable) NSString *descriptionText;
@property (copy,readonly, nullable) NSURL *sourceURL;
@property (copy,readonly, nullable) NSURL *downloadURL;
@property (copy,readonly, nullable) NSString *bundleIdentifier;
@property (readonly, nonatomic, getter=isVaid) BOOL valid;
+ (instancetype)pluginItemFromDictionary:(NSDictionary *)dict;
- (instancetype)initWithDictionary:(NSDictionary *)dict;
- (BOOL)isPluginVersion:(NSString * _Nullable )pluginVersionString compatibleWithHost:(MPPluginHost *)host;
@end
NS_ASSUME_NONNULL_END

View File

@@ -21,6 +21,11 @@
//
#import "MPPluginRepositoryItem.h"
#import "MPPluginRepositoryItemVersionInfo.h"
#import "MPPluginVersion.h"
#import "MPPluginHost.h"
#import "MPPlugin.h"
#import "NSError+Messages.h"
NSString *const MPPluginItemNameKey = @"name";
@@ -29,6 +34,7 @@ NSString *const MPPluginItemDownloadURLKey = @"download";
NSString *const MPPluginItemSourceURLKey = @"source";
NSString *const MPPluginItemCurrentVersionKey = @"currentVersion";
NSString *const MPPluginItemBundleIdentifierKey = @"bundleIdentifier";
NSString *const MPPluginItemCompatibiltyKey = @"compatibilty";
@interface MPPluginRepositoryItem ()
@@ -37,7 +43,9 @@ NSString *const MPPluginItemBundleIdentifierKey = @"bundleIdentifier";
@property (copy) NSString *descriptionText;
@property (copy) NSURL *sourceURL;
@property (copy) NSURL *downloadURL;
@property (copy) NSURL *bundleIdentifier;
@property (copy) NSString *bundleIdentifier;
@property (copy) NSArray *compatibilty;
@end
@@ -58,6 +66,8 @@ NSString *const MPPluginItemBundleIdentifierKey = @"bundleIdentifier";
self.sourceURL = [NSURL URLWithString:dict[MPPluginItemSourceURLKey]];
self.currentVersion = dict[MPPluginItemCurrentVersionKey];
self.bundleIdentifier = dict[MPPluginItemBundleIdentifierKey];
[self _buildVersionInfos:dict[MPPluginItemCompatibiltyKey]];
}
return self;
}
@@ -67,4 +77,50 @@ NSString *const MPPluginItemBundleIdentifierKey = @"bundleIdentifier";
return (self.name.length > 0 && self.downloadURL);
}
- (BOOL)isPluginVersion:(NSString *)pluginVersionString compatibleWithHost:(MPPluginHost *)host {
if(pluginVersionString.length == 0) {
return NO;
}
MPPluginVersion *pluginVersion = [MPPluginVersion versionWithVersionString:pluginVersionString];
if(!pluginVersion) {
return NO;
}
if(host.version.length == 0) {
return NO;
}
MPPluginVersion *hostVersion = [MPPluginVersion versionWithVersionString:host.version];
if(!hostVersion) {
return NO;
}
NSMutableArray<MPPluginRepositoryItemVersionInfo *> *matches = [[NSMutableArray alloc] init];
for(MPPluginRepositoryItemVersionInfo *info in self.compatibilty) {
if(NSOrderedSame == [info.version compare:pluginVersion]) {
[matches addObject:info];
}
}
if(matches.count != 1) {
return NO;
}
MPPluginRepositoryItemVersionInfo *matchingInfo = matches.firstObject;
return [matchingInfo isCompatibleWithHostVersion:hostVersion];
}
- (void)_buildVersionInfos:(NSArray<NSDictionary *>*)infos {
NSMutableArray *tmp = [[NSMutableArray alloc] init];
for(NSDictionary *dict in infos) {
if(![dict isKindOfClass:NSDictionary.class]) {
continue;
}
MPPluginRepositoryItemVersionInfo *info = [MPPluginRepositoryItemVersionInfo versionInfoWithDict:dict];
if(info){
[tmp addObject:info];
}
}
self.compatibilty = [tmp copy];
}
@end

View File

@@ -10,11 +10,19 @@
NS_ASSUME_NONNULL_BEGIN
@class MPPluginVersion;
@interface MPPluginRepositoryItemVersionInfo : NSObject
@property (copy, readonly) MPPluginVersion *version;
+ (instancetype)versionInfoWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (BOOL)isCompatibleWithHostVersion:(MPPluginVersion *)hostVersion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -7,30 +7,75 @@
//
#import "MPPluginRepositoryItemVersionInfo.h"
#import "MPPluginVersion.h"
NSString *const MPPluginItemCompatibiltyVersionKey = @"pluginVersion";
NSString *const MPPluginItemCompatibiltyMinimumHostVersionKey = @"minimumHostVersion";
NSString *const MPPluginItemCompatibiltyMaxiumumHostVersionKey = @"maxiumumHostVersion";
NSString *const MPPluginItemCompatibiltyMaxiumumHostVersionKey = @"maximumHostVersion";
@interface MPPluginRepositoryItemVersionInfo ()
@property (copy) NSString *version;
@property (copy) NSString *minimumHostVersion;
@property (copy) NSString *maxiumHostVersion;
@property (copy) MPPluginVersion *version;
@property (copy) MPPluginVersion *minimumHostVersion;
@property (copy) MPPluginVersion *maxiumHostVersion;
@end
@implementation MPPluginRepositoryItemVersionInfo
+ (instancetype)versionInfoWithDict:(NSDictionary *)dict {
return [[MPPluginRepositoryItemVersionInfo alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];
if(self) {
self.version = dict[MPPluginItemCompatibiltyVersionKey];
self.minimumHostVersion = dict[MPPluginItemCompatibiltyMinimumHostVersionKey];
self.maxiumHostVersion = dict[MPPluginItemCompatibiltyMaxiumumHostVersionKey];
NSString *versionString = dict[MPPluginItemCompatibiltyVersionKey];
if(!versionString) {
NSLog(@"Version information is missing required %@ key.", MPPluginItemCompatibiltyVersionKey);
self = nil;
return self;
}
self.version = [MPPluginVersion versionWithVersionString:versionString];
if(!self.version) {
NSLog(@"Malformed plugin version information: %@.", versionString);
self = nil;
return self;
}
NSString *minimumHostVersionString = dict[MPPluginItemCompatibiltyMinimumHostVersionKey];
if(!minimumHostVersionString) {
NSLog(@"Version information is missing required %@ key.", MPPluginItemCompatibiltyMinimumHostVersionKey);
self = nil;
return self;
}
self.minimumHostVersion = [MPPluginVersion versionWithVersionString:minimumHostVersionString];
if(!self.minimumHostVersion) {
NSLog(@"Malformed minimum host version information: %@.", minimumHostVersionString);
self = nil;
return self;
}
NSString *maxiumHostVersionString = dict[MPPluginItemCompatibiltyMaxiumumHostVersionKey];
if(maxiumHostVersionString) {
self.maxiumHostVersion = [MPPluginVersion versionWithVersionString:maxiumHostVersionString];
if(!self.maxiumHostVersion) {
NSLog(@"Malformed maxium host version information: %@.", maxiumHostVersionString);
self = nil;
return self;
}
}
}
return self;
}
- (BOOL)isCompatibleWithHostVersion:(MPPluginVersion *)hostVersion {
if(NSOrderedDescending == [self.minimumHostVersion compare:hostVersion]) {
return NO;
}
if(!self.maxiumHostVersion) {
return YES;
}
return (NSOrderedAscending != [self.maxiumHostVersion compare:hostVersion]);
}
@end

View File

@@ -26,6 +26,8 @@
#import "MPPlugin.h"
#import "MPPlugin_Private.h"
#import "MPPluginConstants.h"
#import "MPPluginRepository.h"
#import "MPPluginRepositoryItem.h"
#import "MPConstants.h"
#import "MPSettingsHelper.h"
@@ -96,7 +98,7 @@ typedef NS_ENUM(NSUInteger, MPPluginSegmentType) {
? [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_NAME_ERROR_%@", "Name for unloaded plugin with errors"), plugin.name]
: [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_NAME_DISABLED_%@", "name for disabled unloaded plugin"), plugin.name]);
}
view.addionalTextField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_VERSION_%@", "Plugin version. Include a %@ placeholder for version string"), plugin.version];
view.addionalTextField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_VERSION_%@", "Plugin version. Include a %@ placeholder for version string"), plugin.versionString];
return view;
}

31
MacPass/MPPluginVersion.h Normal file
View File

@@ -0,0 +1,31 @@
//
// MPPluginVersion.h
// MacPass
//
// Created by Michael Starke on 05.10.18.
// Copyright © 2018 HicknHack Software GmbH. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *MPPluginVersionWildcard;
@interface MPPluginVersion : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *versionString;
@property (nonatomic, copy, readonly) NSString *mayorVersion;
@property (nonatomic, copy, readonly) NSString *minorVersion;
@property (nonatomic, copy, readonly) NSString *patchVersion;
+ (instancetype)versionWithVersionString:(NSString *)versionString;
- (instancetype)initWithVersionString:(NSString *)versionString NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (NSComparisonResult)compare:(MPPluginVersion *)version;
@end
NS_ASSUME_NONNULL_END

122
MacPass/MPPluginVersion.m Normal file
View File

@@ -0,0 +1,122 @@
//
// MPPluginVersion.m
// MacPass
//
// Created by Michael Starke on 05.10.18.
// Copyright © 2018 HicknHack Software GmbH. All rights reserved.
//
#import "MPPluginVersion.h"
NSString *MPPluginVersionWildcard = @"*";
@interface MPPluginVersion ()
@property (nonatomic, copy) NSString *versionString;
@property (nonatomic, copy) NSString *mayorVersion;
@property (nonatomic, copy) NSString *minorVersion;
@property (nonatomic, copy) NSString *patchVersion;
@end
@implementation MPPluginVersion
+ (instancetype)versionWithVersionString:(NSString *)versionString {
return [[MPPluginVersion alloc] initWithVersionString:versionString];
}
- (instancetype)initWithVersionString:(NSString *)versionString {
self = [super init];
if(self) {
self.mayorVersion = @"0";
self.minorVersion = @"0";
self.patchVersion = @"0";
NSArray<NSString *>* components = [versionString componentsSeparatedByString:@"."];
if(components.count >= 1) {
NSString *mayorVersion = components[0].length > 0 ? components[0] : @"0";
if([mayorVersion isEqualToString:MPPluginVersionWildcard]) {
self.mayorVersion = MPPluginVersionWildcard;
self.minorVersion = MPPluginVersionWildcard;
self.patchVersion = MPPluginVersionWildcard;
self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion];
return self;
}
NSCharacterSet *invalidSet = NSCharacterSet.decimalDigitCharacterSet.invertedSet;
NSRange mayorRange = [mayorVersion rangeOfCharacterFromSet:invalidSet];
if(mayorRange.location != NSNotFound) {
NSLog(@"Invalid Format for Mayor Version");
self = nil;
return self;
}
self.mayorVersion = mayorVersion;
if(components.count >= 2) {
NSString *minorVersion = components[1].length > 0 ? components[1] : @"0";
if([minorVersion isEqualToString:MPPluginVersionWildcard]) {
self.minorVersion = MPPluginVersionWildcard;
self.patchVersion = MPPluginVersionWildcard;
self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion];
return self;
}
NSRange minorRange = [minorVersion rangeOfCharacterFromSet:invalidSet];
if(minorRange.location != NSNotFound) {
NSLog(@"Invalid Format for Minor Version");
self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion];
self = nil;
return self;
}
self.minorVersion = minorVersion;
if(components.count == 3) {
NSString *patchVersion = components[2].length > 0 ? components[2] : @"0";
if([patchVersion isEqualToString:MPPluginVersionWildcard]) {
self.patchVersion = MPPluginVersionWildcard;
self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion];
return self;
}
NSRange patchRange = [patchVersion rangeOfCharacterFromSet:invalidSet];
if(patchRange.location != NSNotFound) {
NSLog(@"Invalid Format for Patch Version");
self = nil;
return self;
}
self.patchVersion = patchVersion;
}
}
}
self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (NSComparisonResult)compare:(MPPluginVersion *)version {
if([self.versionString isEqualToString:version.versionString]) {
return NSOrderedSame;
}
NSArray<NSString *> *myVersions = @[self.mayorVersion, self.minorVersion, self.patchVersion];
NSArray<NSString *> *otherVersions = @[version.mayorVersion, version.minorVersion, version.patchVersion];
for(NSUInteger index = 0; index < 3; index++) {
NSString *myVersion = myVersions[index];
NSString *otherVersion = otherVersions[index];
if([myVersion isEqualToString:MPPluginVersionWildcard] || [otherVersion isEqualToString:MPPluginVersionWildcard]) {
return NSOrderedSame;
}
NSComparisonResult compare = [myVersion compare:otherVersion options:NSNumericSearch|NSCaseInsensitiveSearch];
if(compare != NSOrderedSame) {
return compare;
}
}
return NSOrderedSame;
}
@end

View File

@@ -0,0 +1,150 @@
//
// MPTestPluginVersion.m
// MacPassTests
//
// Created by Michael Starke on 05.10.18.
// Copyright © 2018 HicknHack Software GmbH. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "MPPluginVersion.h"
@interface MPTestPluginVersion : XCTestCase
@end
@implementation MPTestPluginVersion
- (void)testVersionExtraction {
MPPluginVersion *version = [[MPPluginVersion alloc] initWithVersionString:@"1."];
XCTAssertEqualObjects(@"1", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"1.0.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"0.5"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"5", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"0.5.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@".5"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"5", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"0.5.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"5"];
XCTAssertEqualObjects(@"5", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"5.0.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@".1."];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"0.1.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@".1.1"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"1", version.patchVersion);
XCTAssertEqualObjects(@"0.1.1", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"..1"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"1", version.patchVersion);
XCTAssertEqualObjects(@"0.0.1", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"1.0.0"];
XCTAssertEqualObjects(@"1", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"1.0.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"0.1.0"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"0", version.patchVersion);
XCTAssertEqualObjects(@"0.1.0", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"1.1.1"];
XCTAssertEqualObjects(@"1", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"1", version.patchVersion);
XCTAssertEqualObjects(@"1.1.1", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"0.0.5"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"5", version.patchVersion);
XCTAssertEqualObjects(@"0.0.5", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"1.0.3"];
XCTAssertEqualObjects(@"1", version.mayorVersion);
XCTAssertEqualObjects(@"0", version.minorVersion);
XCTAssertEqualObjects(@"3", version.patchVersion);
XCTAssertEqualObjects(@"1.0.3", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"0.1.4"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"4", version.patchVersion);
XCTAssertEqualObjects(@"0.1.4", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"0.1.*"];
XCTAssertEqualObjects(@"0", version.mayorVersion);
XCTAssertEqualObjects(@"1", version.minorVersion);
XCTAssertEqualObjects(@"*", version.patchVersion);
XCTAssertEqualObjects(@"0.1.*", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"*.1.0"];
XCTAssertEqualObjects(@"*", version.mayorVersion);
XCTAssertEqualObjects(@"*", version.minorVersion);
XCTAssertEqualObjects(@"*", version.patchVersion);
XCTAssertEqualObjects(@"*.*.*", version.versionString);
version = [[MPPluginVersion alloc] initWithVersionString:@"1.*.0"];
XCTAssertEqualObjects(@"1", version.mayorVersion);
XCTAssertEqualObjects(@"*", version.minorVersion);
XCTAssertEqualObjects(@"*", version.patchVersion);
XCTAssertEqualObjects(@"1.*.*", version.versionString);
}
- (void)testeVersionCompare {
NSArray *data = @[
@[ @"*", @"*", @(NSOrderedSame)],
@[ @"1.0.1", @"*", @(NSOrderedSame)],
@[ @"*", @"1.0.1", @(NSOrderedSame)],
@[ @"1.*", @"1.*", @(NSOrderedSame)],
@[ @"1.1.*", @"1.1.*", @(NSOrderedSame)],
@[ @"1.1.1", @"1.1.1", @(NSOrderedSame)],
@[ @"1.0.1", @"*", @(NSOrderedSame)],
@[ @"0.10.*", @"0.10.1", @(NSOrderedSame)],
@[ @"10.*.1", @"10.99.*", @(NSOrderedSame)],
@[ @"0.9.89", @"0.9.89", @(NSOrderedSame)],
@[ @"0.0.1", @"0.0.2", @(NSOrderedAscending)],
@[ @"1.10.10", @"1.12.10", @(NSOrderedAscending)],
@[ @"20.0.1", @"20.1.0", @(NSOrderedAscending)],
@[ @"1.1.1", @"2.*", @(NSOrderedAscending)],
@[ @"1.*", @"2.0.*", @(NSOrderedAscending)],
@[ @"2.1.1", @"2.0.0", @(NSOrderedDescending)],
@[ @"0.1.1", @"0.1.0", @(NSOrderedDescending)],
@[ @"1.2.*", @"1.1.*", @(NSOrderedDescending)],
@[ @"2.*", @"1.*", @(NSOrderedDescending)]
];
for(NSUInteger index = 0; index < data.count; index++) {
NSArray *set = data[index];
MPPluginVersion *versionA = [[MPPluginVersion alloc] initWithVersionString:set[0]];
MPPluginVersion *versionB = [[MPPluginVersion alloc] initWithVersionString:set[1]];
NSComparisonResult result = [set[2] integerValue];
XCTAssertEqual(result, [versionA compare:versionB]);
}
}
@end