diff options
Diffstat (limited to 'QSThingsPlugin')
| -rw-r--r-- | QSThingsPlugin/Actions/SearchThingsAction.h | 11 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/SearchThingsAction.m | 24 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/ShowInThingsAction.h | 11 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/ShowInThingsAction.m | 33 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/SQLHelper.h | 16 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/SQLHelper.m | 84 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsAreasParser.h | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsAreasParser.m | 33 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsProjectsParser.h | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsProjectsParser.m | 42 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsTasksParser.h | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsTasksParser.m | 43 | ||||
| -rw-r--r-- | QSThingsPlugin/QSThingsPluginSource.h | 10 | ||||
| -rw-r--r-- | QSThingsPlugin/QSThingsPluginSource.m | 59 | ||||
| -rw-r--r-- | QSThingsPlugin/QSThingsPluginSource.xib | 50 | ||||
| -rw-r--r-- | QSThingsPlugin/Types/Constants.h | 7 | ||||
| -rw-r--r-- | QSThingsPlugin/Types/Constants.m | 7 | ||||
| -rw-r--r-- | QSThingsPlugin/en.lproj/Localizable.strings | 1 |
18 files changed, 443 insertions, 0 deletions
diff --git a/QSThingsPlugin/Actions/SearchThingsAction.h b/QSThingsPlugin/Actions/SearchThingsAction.h new file mode 100644 index 0000000..5f0ed48 --- /dev/null +++ b/QSThingsPlugin/Actions/SearchThingsAction.h @@ -0,0 +1,11 @@ +#import "Constants.h" +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface SearchThingsAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/SearchThingsAction.m b/QSThingsPlugin/Actions/SearchThingsAction.m new file mode 100644 index 0000000..9b042b3 --- /dev/null +++ b/QSThingsPlugin/Actions/SearchThingsAction.m @@ -0,0 +1,24 @@ +#import "SearchThingsAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SearchThingsAction + +- (QSObject *)searchFor:(QSObject *)directObject +{ + + NSString *contents = [directObject displayName]; + if (contents) { + NSString *urlString = [NSString stringWithFormat:@"things:///search?query=%@", contents]; + NSURL *url = [NSURL URLWithString:urlString]; + + [[NSWorkspace sharedWorkspace] openURL:url]; + } else { + NSLog(@"Could not get the text of the Things object."); + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/ShowInThingsAction.h b/QSThingsPlugin/Actions/ShowInThingsAction.h new file mode 100644 index 0000000..8d7790c --- /dev/null +++ b/QSThingsPlugin/Actions/ShowInThingsAction.h @@ -0,0 +1,11 @@ +#import "Constants.h" +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface ShowInThingsAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/ShowInThingsAction.m b/QSThingsPlugin/Actions/ShowInThingsAction.m new file mode 100644 index 0000000..91fe3ec --- /dev/null +++ b/QSThingsPlugin/Actions/ShowInThingsAction.m @@ -0,0 +1,33 @@ +#import "ShowInThingsAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation ShowInThingsAction + +- (QSObject *)showObject:(QSObject *)directObject +{ + NSSet *validTypes = [NSSet setWithObjects:kAppSectionType, kTagType, kAreaType, kProjectType, kTaskType, nil]; + + + if ([validTypes containsObject:directObject.primaryType]) { + NSDictionary *contents = [directObject objectForType:directObject.primaryType]; + if (contents) { + NSString *uuid = [contents objectForKey:@"uuid"]; + if (uuid) { + NSString *urlString = [NSString stringWithFormat:@"things:///show?id=%@", uuid]; + NSURL *url = [NSURL URLWithString:urlString]; + + [[NSWorkspace sharedWorkspace] openURL:url]; + } else { + NSLog(@"Things object did not have a uuid."); + } + } else { + NSLog(@"Could not get the contents of the Things object."); + } + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/SQLHelper.h b/QSThingsPlugin/Helpers/SQLHelper.h new file mode 100644 index 0000000..fc63650 --- /dev/null +++ b/QSThingsPlugin/Helpers/SQLHelper.h @@ -0,0 +1,16 @@ +/** + Helper class to access the things sqlite database. + */ +@interface SQLHelper : NSObject +{ +} + +/** + Execute an SQL query on the things database + @param query the SQL query. Must contain output fieldnames "url" and "title". + @param path file path to the database + @returns an array of Task objects. Will return empty if an error occurs. + */ ++ (NSArray *) executeSql:(NSString *)query onFile:(NSString *)path; + +@end diff --git a/QSThingsPlugin/Helpers/SQLHelper.m b/QSThingsPlugin/Helpers/SQLHelper.m new file mode 100644 index 0000000..6d94bb5 --- /dev/null +++ b/QSThingsPlugin/Helpers/SQLHelper.m @@ -0,0 +1,84 @@ +#import "SQLHelper.h" +#import <QSFoundation/FMDatabase.h> + +@implementation SQLHelper + ++ (NSArray *) executeSql:(NSString *)query onFile:(NSString *)path { + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:0]; + NSFileManager *manager = [NSFileManager defaultManager]; + + FMDatabase *database = [FMDatabase databaseWithPath:path]; + if (![database openWithFlags:0x00000001]) { // 1 = SQLITE_OPEN_READONLY + NSLog(@"Could not open Firefox's places.sqlite DB."); + return @[]; + } + [database goodConnection]; + + // execute SQL query + FMResultSet *resultSet = [database executeQuery:query]; + if ([database hadError]) { + NSLog(@"Error while reading Things database. (%d): %@", [database lastErrorCode], [database lastErrorMessage]); + return @[]; + } + + while ([resultSet next]) { + // Create a completely independent dictionary with string copies + NSMutableDictionary *resultDictionary = [NSMutableDictionary dictionary]; + + // Manually copy each field to ensure we have independent string objects + NSDictionary *originalDict = resultSet.resultDictionary; + for (NSString *key in originalDict.allKeys) { + id value = [originalDict objectForKey:key]; + if (value && value != [NSNull null]) { + // Make sure we have independent copies of strings + if ([value isKindOfClass:[NSString class]]) { + resultDictionary[key] = [NSString stringWithString:value]; + } else { + resultDictionary[key] = value; + } + } + } + + [objects addObject:[resultDictionary copy]]; // Make immutable copy + } + + [resultSet close]; + [database close]; + + return objects; +} + ++ (BOOL)appendString:(NSString *)string toFile:(NSString *)filePath { + // Create file manager + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Check if file exists, create it if it doesn't + if (![fileManager fileExistsAtPath:filePath]) { + // Create empty file + [@"" writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; + } + + // Create file handle for appending + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath]; + + if (!fileHandle) { + NSLog(@"Failed to open file for writing: %@", filePath); + return NO; + } + + // Move to end of file + [fileHandle seekToEndOfFile]; + + // Convert string to data and write + NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + [fileHandle writeData:stringData]; + + // Close file handle + [fileHandle closeFile]; + + NSLog(@"Successfully appended string to: %@", filePath); + return YES; +} + +@end diff --git a/QSThingsPlugin/Parsers/QSThingsAreasParser.h b/QSThingsPlugin/Parsers/QSThingsAreasParser.h new file mode 100644 index 0000000..74f829b --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsAreasParser.h @@ -0,0 +1,4 @@ +@interface QSThingsAreasParser : QSParser +{ +} +@end diff --git a/QSThingsPlugin/Parsers/QSThingsAreasParser.m b/QSThingsPlugin/Parsers/QSThingsAreasParser.m new file mode 100644 index 0000000..d4f9b5f --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsAreasParser.m @@ -0,0 +1,33 @@ +#import "Constants.h" +#import "QSThingsAreasParser.h" +#import "SQLHelper.h" + +@implementation QSThingsAreasParser +- (BOOL)validParserForPath:(NSString *)path { + return [[path lastPathComponent] isEqualToString:@"main.sqlite"]; +} + +- (NSArray *)objectsFromPath:(NSString *)path withSettings:(NSDictionary *)settings { + NSString *query = @"SELECT " + "uuid, " + "title " + "FROM TMArea " + "ORDER BY title;"; + + NSArray *results = [SQLHelper executeSql:query onFile:path]; + + NSMutableArray *objects = [NSMutableArray array]; + [results enumerateObjectsUsingBlock:^(NSDictionary *dictionary, NSUInteger i, BOOL *stop) { + + QSObject *newObject = [QSObject + makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]]; + [newObject setObject:dictionary forType:kProjectType]; + [newObject setName:[dictionary objectForKey:@"title"]]; + [newObject setPrimaryType:kProjectType]; + + [objects addObject:newObject]; + }]; + + return objects; +} +@end diff --git a/QSThingsPlugin/Parsers/QSThingsProjectsParser.h b/QSThingsPlugin/Parsers/QSThingsProjectsParser.h new file mode 100644 index 0000000..cee939d --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsProjectsParser.h @@ -0,0 +1,4 @@ +@interface QSThingsProjectsParser : QSParser +{ +} +@end diff --git a/QSThingsPlugin/Parsers/QSThingsProjectsParser.m b/QSThingsPlugin/Parsers/QSThingsProjectsParser.m new file mode 100644 index 0000000..1a7956e --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsProjectsParser.m @@ -0,0 +1,42 @@ +#import "Constants.h" +#import "QSThingsProjectsParser.h" +#import "SQLHelper.h" + +@implementation QSThingsProjectsParser +- (BOOL)validParserForPath:(NSString *)path { + return [[path lastPathComponent] isEqualToString:@"main.sqlite"]; +} + +- (NSArray *)objectsFromPath:(NSString *)path withSettings:(NSDictionary *)settings { + NSString *query = @"SELECT " + "uuid, " + "creationDate, " + "status, " + "title, " + "notes, " + "area, " + "project " + "FROM TMTask " + "WHERE trashed = 0 " + "AND type = 1 " + "AND status < 3 " + "ORDER BY title;"; + + NSArray *results = [SQLHelper executeSql:query onFile:path]; + + NSMutableArray *objects = [NSMutableArray array]; + [results enumerateObjectsUsingBlock:^(NSDictionary *dictionary, NSUInteger i, BOOL *stop) { + + QSObject *newObject = [QSObject + makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]]; + [newObject setObject:dictionary forType:kProjectType]; + [newObject setName:[dictionary objectForKey:@"title"]]; + [newObject setDetails:[dictionary objectForKey:@"notes"]]; + [newObject setPrimaryType:kProjectType]; + + [objects addObject:newObject]; + }]; + + return objects; +} +@end diff --git a/QSThingsPlugin/Parsers/QSThingsTasksParser.h b/QSThingsPlugin/Parsers/QSThingsTasksParser.h new file mode 100644 index 0000000..7e3f1f2 --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsTasksParser.h @@ -0,0 +1,4 @@ +@interface QSThingsTasksParser : QSParser +{ +} +@end diff --git a/QSThingsPlugin/Parsers/QSThingsTasksParser.m b/QSThingsPlugin/Parsers/QSThingsTasksParser.m new file mode 100644 index 0000000..b29d011 --- /dev/null +++ b/QSThingsPlugin/Parsers/QSThingsTasksParser.m @@ -0,0 +1,43 @@ +#import "Constants.h" +#import "QSThingsTasksParser.h" +#import "SQLHelper.h" +#import <os/log.h> + +@implementation QSThingsTasksParser +- (BOOL)validParserForPath:(NSString *)path { + return [[path lastPathComponent] isEqualToString:@"main.sqlite"]; +} + +- (NSArray *)objectsFromPath:(NSString *)path withSettings:(NSDictionary *)settings { + NSString *query = @"SELECT " + "uuid, " + "creationDate, " + "status, " + "title, " + "notes, " + "area, " + "project " + "FROM TMTask " + "WHERE trashed = 0 " + "AND type = 0 " + "AND status < 3 " + "ORDER BY title;"; + + NSArray *results = [SQLHelper executeSql:query onFile:path]; + + NSMutableArray *objects = [NSMutableArray array]; + [results enumerateObjectsUsingBlock:^(NSDictionary *dictionary, NSUInteger i, BOOL *stop) { + + QSObject *newObject = [QSObject + makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]]; + [newObject setObject:dictionary forType:kTaskType]; + [newObject setName:[dictionary objectForKey:@"title"]]; + [newObject setDetails:[dictionary objectForKey:@"notes"]]; + [newObject setPrimaryType:kTaskType]; + + [objects addObject:newObject]; + }]; + + return objects; +} +@end diff --git a/QSThingsPlugin/QSThingsPluginSource.h b/QSThingsPlugin/QSThingsPluginSource.h new file mode 100644 index 0000000..95daae8 --- /dev/null +++ b/QSThingsPlugin/QSThingsPluginSource.h @@ -0,0 +1,10 @@ +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> + +@interface QSThingsPluginSource : QSObjectSource +@end + +@interface QSCatalogEntry (OldStyleSourceSupport) +@property NSMutableDictionary *info; +- (id)objectForKey:(NSString *)key; +@end diff --git a/QSThingsPlugin/QSThingsPluginSource.m b/QSThingsPlugin/QSThingsPluginSource.m new file mode 100644 index 0000000..3f5f06e --- /dev/null +++ b/QSThingsPlugin/QSThingsPluginSource.m @@ -0,0 +1,59 @@ +#import "QSThingsPluginSource.h" +#import "Constants.h" +#import <QSCore/QSCore.h> + +@implementation QSThingsPluginSource + +#pragma mark - Lifecycle + +// This method will get called whenever we change which +// active entry is selected. +- (void)setSelectedEntry:(id)selectedEntry { +} + +#pragma mark - Quicksilver Source Methods + +- (BOOL)indexIsValidFromDate:(NSDate *)indexDate + forEntry:(NSDictionary *)theEntry { + return -[indexDate timeIntervalSinceNow] < 24 * 60 * 60; +} + +- (BOOL)isVisibleSource { + return YES; +} + +- (NSImage *)iconForEntry:(NSDictionary *)dict { + return [[NSBundle bundleForClass:[self class]] imageNamed:@"things"]; +} + +- (NSView *)settingsView { + if (![super settingsView]) { + [[NSBundle bundleForClass:[self class]] + loadNibNamed:NSStringFromClass([self class]) + owner:self + topLevelObjects:NULL]; + } + return [super settingsView]; +} + +#pragma mark - Objects For Entry + +- (NSArray *)objectsForEntry:(QSCatalogEntry *)theEntry { + return @[]; +} + +#pragma mark - Object Handler Methods + +- (void)setQuickIconForObject:(QSObject *)object { + // Not yet +} + +- (BOOL)objectHasChildren:(QSObject *)object { + return YES; +} + +- (BOOL)loadChildrenForObject:(QSObject *)object { + return YES; +} + +@end diff --git a/QSThingsPlugin/QSThingsPluginSource.xib b/QSThingsPlugin/QSThingsPluginSource.xib new file mode 100644 index 0000000..ac6254e --- /dev/null +++ b/QSThingsPlugin/QSThingsPluginSource.xib @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="QSThingsPluginSource"> + <connections> + <outlet property="settingsView" destination="settings-view" id="settings-view-outlet"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView translatesAutoresizingMaskIntoConstraints="NO" id="settings-view"> + <rect key="frame" x="0.0" y="0.0" width="249" height="252"/> + <subviews> + <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="site-label"> + <rect key="frame" x="58" y="118" width="133" height="16"/> + <constraints> + <constraint firstAttribute="height" constant="16" id="Eoo-3h-Uin"/> + </constraints> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Nothing to configure!" id="site-label-cell"> + <font key="font" metaFont="system"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <constraints> + <constraint firstItem="site-label" firstAttribute="centerX" secondItem="settings-view" secondAttribute="centerX" id="D0Z-9a-PUk"/> + <constraint firstItem="site-label" firstAttribute="centerY" secondItem="settings-view" secondAttribute="centerY" id="KkB-XD-j1l"/> + </constraints> + <point key="canvasLocation" x="155.5" y="180"/> + </customView> + <objectController id="JpK-62-U8F" userLabel="Settings"> + <declaredKeys> + <string>info.username</string> + <string>info.site</string> + <string>info.includeTags</string> + <string>info.host</string> + </declaredKeys> + <connections> + <binding destination="-2" name="contentObject" keyPath="self.selectedEntry" id="r1i-y5-4Mi"/> + <outlet property="content" destination="-2" id="UlK-eW-ayD"/> + </connections> + </objectController> + </objects> +</document> diff --git a/QSThingsPlugin/Types/Constants.h b/QSThingsPlugin/Types/Constants.h new file mode 100644 index 0000000..e365a8f --- /dev/null +++ b/QSThingsPlugin/Types/Constants.h @@ -0,0 +1,7 @@ +#pragma mark - Things Object Types + +extern NSString *const kAppSectionType; +extern NSString *const kProjectType; +extern NSString *const kAreaType; +extern NSString *const kTagType; +extern NSString *const kTaskType; diff --git a/QSThingsPlugin/Types/Constants.m b/QSThingsPlugin/Types/Constants.m new file mode 100644 index 0000000..4abf9f7 --- /dev/null +++ b/QSThingsPlugin/Types/Constants.m @@ -0,0 +1,7 @@ +#pragma mark - Things Object Types + +NSString *const kAppSectionType = @"things.app-section"; +NSString *const kProjectType = @"things.project"; +NSString *const kAreaType = @"things.area"; +NSString *const kTagType = @"things.tag"; +NSString *const kTaskType = @"things.task"; diff --git a/QSThingsPlugin/en.lproj/Localizable.strings b/QSThingsPlugin/en.lproj/Localizable.strings new file mode 100644 index 0000000..2ac3fa6 --- /dev/null +++ b/QSThingsPlugin/en.lproj/Localizable.strings @@ -0,0 +1 @@ +"QSThingsPluginSource" = "Things 3"; |