From 4f688e36a52a4faf8c1bf714dc3d12f5cf6e09c6 Mon Sep 17 00:00:00 2001 From: relikd Date: Tue, 14 Aug 2018 15:11:56 +0200 Subject: [PATCH] Storing of feed entries and fetched property bugfix --- baRSS.xcodeproj/project.pbxproj | 46 ++++++----- baRSS/AppDelegate.h | 2 +- baRSS/AppDelegate.m | 3 + .../DBv1.xcdatamodel/contents | 5 +- baRSS/NewsController.h | 1 - baRSS/NewsController.m | 30 -------- baRSS/Preferences/Feeds Tab/ModalFeedEdit.h | 2 +- baRSS/Preferences/Feeds Tab/ModalFeedEdit.m | 57 +++++++++----- baRSS/Preferences/Feeds Tab/SettingsFeeds.h | 1 + baRSS/Preferences/Feeds Tab/SettingsFeeds.m | 8 ++ baRSS/StoreCoordinator.h | 31 ++++++++ baRSS/StoreCoordinator.m | 77 +++++++++++++++++++ 12 files changed, 186 insertions(+), 77 deletions(-) create mode 100644 baRSS/StoreCoordinator.h create mode 100644 baRSS/StoreCoordinator.m diff --git a/baRSS.xcodeproj/project.pbxproj b/baRSS.xcodeproj/project.pbxproj index 21ae6af..39a26ab 100644 --- a/baRSS.xcodeproj/project.pbxproj +++ b/baRSS.xcodeproj/project.pbxproj @@ -28,43 +28,46 @@ 54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; }; 54E88321211B509D00064188 /* ModalFeedEdit.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54E8831F211B509D00064188 /* ModalFeedEdit.xib */; }; 54F39C2E210BE1F700AEE730 /* DBv1.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28221061B3B0020715F /* DBv1.xcdatamodeld */; }; + 54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 1968E7919BAA36F042FCB717 /* PyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyHandler.h; sourceTree = ""; }; 1968EF7567E06D2A5BB3481A /* PyHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PyHandler.m; sourceTree = ""; }; - 54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = ""; }; - 54209E932117325100F3B5EF /* DrawImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DrawImage.m; sourceTree = ""; }; - 544B01182114B41200386E5C /* ModalSheet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalSheet.h; sourceTree = ""; }; - 544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = ""; }; - 544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = ""; }; - 544B011C2114EE9100386E5C /* AppHook.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppHook.m; sourceTree = ""; }; + 54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = ""; }; + 54209E932117325100F3B5EF /* DrawImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawImage.m; sourceTree = ""; }; + 544B01182114B41200386E5C /* ModalSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalSheet.h; sourceTree = ""; }; + 544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = ""; }; + 544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = ""; }; + 544B011C2114EE9100386E5C /* AppHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHook.m; sourceTree = ""; }; 544FBD4421064AEB008A260C /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = System/Library/Frameworks/Python.framework; sourceTree = SDKROOT; }; 544FBD4621064B2F008A260C /* getFeed.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = getFeed.py; sourceTree = ""; usesTabs = 0; }; 544FBD4821064DF0008A260C /* feedparser521.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = feedparser521.py; sourceTree = ""; usesTabs = 0; }; - 546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsFeeds.h; sourceTree = ""; }; - 546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsFeeds.m; sourceTree = ""; }; + 546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsFeeds.h; sourceTree = ""; }; + 546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsFeeds.m; sourceTree = ""; }; 546FC43E21188C78007CC3A3 /* SettingsFeeds.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsFeeds.xib; sourceTree = ""; }; - 546FC44021189975007CC3A3 /* SettingsGeneral.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsGeneral.h; sourceTree = ""; }; - 546FC44121189975007CC3A3 /* SettingsGeneral.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneral.m; sourceTree = ""; }; + 546FC44021189975007CC3A3 /* SettingsGeneral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsGeneral.h; sourceTree = ""; }; + 546FC44121189975007CC3A3 /* SettingsGeneral.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneral.m; sourceTree = ""; }; 546FC44221189975007CC3A3 /* SettingsGeneral.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsGeneral.xib; sourceTree = ""; }; 546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = ""; }; 54ACC27C21061B3B0020715F /* baRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = baRSS.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 54ACC27F21061B3B0020715F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 54ACC28021061B3B0020715F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 54ACC27F21061B3B0020715F /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 54ACC28021061B3B0020715F /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = ""; }; 54ACC28521061B3C0020715F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54ACC28821061B3C0020715F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Main.xib; sourceTree = ""; }; 54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 54ACC29321061E270020715F /* NewsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewsController.h; sourceTree = ""; }; - 54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = ""; }; - 54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = ""; }; - 54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = ""; }; - 54E8831D211B509D00064188 /* ModalFeedEdit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalFeedEdit.h; sourceTree = ""; }; - 54E8831E211B509D00064188 /* ModalFeedEdit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEdit.m; sourceTree = ""; }; + 54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 54ACC29321061E270020715F /* NewsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewsController.h; sourceTree = ""; }; + 54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = ""; }; + 54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = ""; }; + 54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = ""; }; + 54E8831D211B509D00064188 /* ModalFeedEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalFeedEdit.h; sourceTree = ""; }; + 54E8831E211B509D00064188 /* ModalFeedEdit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEdit.m; sourceTree = ""; }; 54E8831F211B509D00064188 /* ModalFeedEdit.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ModalFeedEdit.xib; sourceTree = ""; }; - 54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Print.h"; sourceTree = ""; }; + 54EC3E1D211D03C100E314F4 /* FeedConfig+Print.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FeedConfig+Print.h"; sourceTree = ""; }; + 54FE73CE21220DEC003EAC65 /* StoreCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoreCoordinator.h; sourceTree = ""; }; + 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoreCoordinator.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -148,6 +151,8 @@ 544B011C2114EE9100386E5C /* AppHook.m */, 54ACC27F21061B3B0020715F /* AppDelegate.h */, 54ACC28021061B3B0020715F /* AppDelegate.m */, + 54FE73CE21220DEC003EAC65 /* StoreCoordinator.h */, + 54FE73CF21220DEC003EAC65 /* StoreCoordinator.m */, 54ACC29321061E270020715F /* NewsController.h */, 54ACC29421061E270020715F /* NewsController.m */, 54209E922117325100F3B5EF /* DrawImage.h */, @@ -271,6 +276,7 @@ 54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */, 1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */, 54209E942117325100F3B5EF /* DrawImage.m in Sources */, + 54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/baRSS/AppDelegate.h b/baRSS/AppDelegate.h index c1ffa2b..7c97dc3 100644 --- a/baRSS/AppDelegate.h +++ b/baRSS/AppDelegate.h @@ -25,7 +25,7 @@ @interface AppDelegate : NSObject @property (readonly, strong) NSPersistentContainer *persistentContainer; - +- (IBAction)saveAction:(id)sender; - (void)preferencesClosed; @end diff --git a/baRSS/AppDelegate.m b/baRSS/AppDelegate.m index 9d2212e..2d6bacc 100644 --- a/baRSS/AppDelegate.m +++ b/baRSS/AppDelegate.m @@ -24,6 +24,7 @@ #import "PyHandler.h" #import "DrawImage.h" #import "Preferences.h" +#import "StoreCoordinator.h" @interface AppDelegate () @property (weak) IBOutlet NSMenu *statusMenu; @@ -42,6 +43,7 @@ self.statusItem.image.template = YES; [PyHandler prepare]; printf("up and running\n"); +// [StoreCoordinator deleteUnreferencedFeeds]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { @@ -75,6 +77,7 @@ } }]; NSUndoManager *um = [[NSUndoManager alloc] init]; + um.groupsByEvent = NO; um.levelsOfUndo = 30; _persistentContainer.viewContext.undoManager = um; } diff --git a/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents b/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents index 137cd9b..662afaf 100644 --- a/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents +++ b/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents @@ -12,9 +12,6 @@ - - - @@ -42,7 +39,7 @@ - + diff --git a/baRSS/NewsController.h b/baRSS/NewsController.h index 0a84c0f..ce315a6 100644 --- a/baRSS/NewsController.h +++ b/baRSS/NewsController.h @@ -23,6 +23,5 @@ #import @interface NewsController : NSObject - + (void)downloadFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block; @end diff --git a/baRSS/NewsController.m b/baRSS/NewsController.m index 44fcd97..1ca879c 100644 --- a/baRSS/NewsController.m +++ b/baRSS/NewsController.m @@ -22,7 +22,6 @@ #import "NewsController.h" #import "PyHandler.h" -#import "DBv1+CoreDataModel.h" @interface NewsController () @end @@ -35,35 +34,6 @@ - (IBAction)updateAllFeeds:(NSMenuItem *)sender { NSLog(@"update all"); - NSDictionary * obj = [PyHandler getFeed:@"https://feeds.feedburner.com/simpledesktops" withEtag:nil andModified:nil]; - NSLog(@"obj = %@", obj); - // TODO: check status code - /* - Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:self.managedObjectContext]; - a.title = obj[@"feed"][@"title"]; - a.subtitle = obj[@"feed"][@"subtitle"]; - a.author = obj[@"feed"][@"author"]; - a.link = obj[@"feed"][@"link"]; - a.published = obj[@"feed"][@"published"]; - a.icon = obj[@"feed"][@"icon"]; - a.etag = obj[@"header"][@"etag"]; - a.date = obj[@"header"][@"date"]; - a.modified = obj[@"header"][@"modified"]; - for (NSDictionary *entry in obj[@"entries"]) { - FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:self.managedObjectContext]; - b.title = entry[@"title"]; - b.subtitle = entry[@"subtitle"]; - b.author = entry[@"author"]; - b.link = entry[@"link"]; - b.published = entry[@"published"]; - b.summary = entry[@"summary"]; - for (NSString *tag in entry[@"tags"]) { - FeedTag *c = [[FeedTag alloc] initWithEntity:FeedTag.entity insertIntoManagedObjectContext:self.managedObjectContext]; - c.name = tag; - [b addTagsObject:c]; - } - [a addItemsObject:b]; - }*/ } - (IBAction)openAllUnread:(NSMenuItem *)sender { diff --git a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.h b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.h index 74076df..c25f69b 100644 --- a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.h +++ b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.h @@ -23,7 +23,7 @@ #import @protocol ModalFeedConfigEdit -- (void)updateRepresentedObject; // must call [item.managedObjectContext refreshAllObjects] +- (void)updateRepresentedObject; // must call [item.managedObjectContext refreshObject:item mergeChanges:YES]; @end diff --git a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m index 9d698db..17fdfde 100644 --- a/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m +++ b/baRSS/Preferences/Feeds Tab/ModalFeedEdit.m @@ -22,6 +22,7 @@ #import "ModalFeedEdit.h" #import "NewsController.h" +#import "StoreCoordinator.h" #import "FeedConfig+CoreDataProperties.h" @interface ModalFeedEdit() @@ -38,8 +39,9 @@ @property (strong) NSError *feedError; @property (strong) NSDictionary *feedResult; -@property (assign) BOOL shouldEvaluate; -@property (assign) BOOL lateEvaluation; +@property (assign) BOOL shouldSaveObject; +@property (assign) BOOL objectNeedsSaving; +@property (assign) BOOL objectIsModified; @end @implementation ModalFeedEdit @@ -48,7 +50,9 @@ [super viewDidLoad]; self.previousURL = @""; self.refreshNum.intValue = 30; - self.shouldEvaluate = NO; + self.shouldSaveObject = NO; + self.objectNeedsSaving = NO; + self.objectIsModified = NO; FeedConfig *fc = [self feedConfigOrNil]; if (fc) { @@ -65,21 +69,30 @@ } - (void)dealloc { - FeedConfig *item = [self feedConfigOrNil]; - if (self.shouldEvaluate && self.lateEvaluation && item) { - if (!item.name || [item.name isEqualToString:@""]) { - [self setTitleFromFeed]; - if (![item.name isEqualToString: self.name.stringValue]) // only if result isnt empty as well - item.name = self.name.stringValue; - [item.managedObjectContext refreshAllObjects]; + if (self.shouldSaveObject) { + if (self.objectNeedsSaving) + [self updateRepresentedObject]; + NSUndoManager *um = [self feedConfigOrNil].managedObjectContext.undoManager; + [um endUndoGrouping]; + if (!self.objectIsModified) { + [um disableUndoRegistration]; + [um undoNestedGroup]; + [um enableUndoRegistration]; + } else { + [StoreCoordinator save]; } - } } - (void)updateRepresentedObject { FeedConfig *item = [self feedConfigOrNil]; if (item) { + if (!self.shouldSaveObject) { // first call to this method + [item.managedObjectContext.undoManager beginUndoGrouping]; + } + self.shouldSaveObject = YES; + self.objectNeedsSaving = NO; // after this method it is saved + // if's to prevent unnecessary undo groups if nothing has changed if (![item.name isEqualToString: self.name.stringValue]) item.name = self.name.stringValue; @@ -90,11 +103,17 @@ if (item.refreshUnit != self.refreshUnit.indexOfSelectedItem) item.refreshUnit = (int16_t)self.refreshUnit.indexOfSelectedItem; - self.shouldEvaluate = YES; - self.lateEvaluation = NO; - // TODO: append feed result - NSLog(@"here i want to set it"); - [item.managedObjectContext refreshAllObjects]; + + if (self.feedResult) { + Feed *rss = [StoreCoordinator createFeedFromDictionary:self.feedResult]; + if (item.feed) + [item.managedObjectContext deleteObject:(NSManagedObject*)item.feed]; + item.feed = rss; + } + if ([item.managedObjectContext hasChanges]) { + self.objectIsModified = YES; + [item.managedObjectContext refreshObject:item mergeChanges:YES]; + } } } @@ -118,17 +137,15 @@ if (obj.object == self.url && [self urlHasChanged]) { self.previousURL = self.url.stringValue; self.feedResult = nil; - NSLog(@"setting result to nil"); self.feedError = nil; [self.spinnerURL startAnimation:nil]; [self.spinnerName startAnimation:nil]; [NewsController downloadFeed:self.previousURL withBlock:^(NSDictionary *result, NSError *error) { self.feedResult = result; - NSLog(@"got results back"); self.feedError = error; // warning indicator .hidden is bound to feedError // TODO: play error sound? dispatch_async(dispatch_get_main_queue(), ^{ - self.lateEvaluation = YES; // stays YES if this block runs after updateRepresentedObject: + self.objectNeedsSaving = YES; // stays YES if this block runs after updateRepresentedObject: [self setTitleFromFeed]; [self.spinnerURL stopAnimation:nil]; [self.spinnerName stopAnimation:nil]; @@ -186,7 +203,7 @@ NSString *name = ((NSTextField*)self.view).stringValue; if (![item.name isEqualToString: name]) { item.name = name; - [item.managedObjectContext refreshAllObjects]; + [item.managedObjectContext refreshObject:item mergeChanges:YES]; } } } diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.h b/baRSS/Preferences/Feeds Tab/SettingsFeeds.h index f4b06b9..b69691a 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.h +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.h @@ -22,6 +22,7 @@ #import +/** Manages the NSOutlineView and Feed creation and editing */ @interface SettingsFeeds : NSViewController @end diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m index 7a3ccd1..887b387 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m @@ -101,12 +101,14 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; [self.view.window beginSheet:[ModalSheet modalWithView:self.modalController.view] completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSModalResponseOK) { + [self.undoManager beginUndoGrouping]; if (!existingItem) { // create new item FeedConfig *item = [self insertSortedItemAtSelection]; item.type = (group ? 0 : 1); self.modalController.representedObject = item; } [self.modalController updateRepresentedObject]; + [self.undoManager endUndoGrouping]; } self.modalController = nil; }]; @@ -328,6 +330,12 @@ static NSString *dragNodeType = @"baRSS-feed-drag"; NSLog(@"%@", str); } +/** + Go through all children recursively and prepend the string with spaces as nesting + @param obj Root Node or parent Node + @param str An initialized @c NSMutableString to append to + @param prefix Should be @c @@"" for the first call + */ - (void)traverseChildren:(NSTreeNode*)obj appendString:(NSMutableString*)str prefix:(NSString*)prefix { [str appendFormat:@"%@%@\n", prefix, [obj.representedObject readableDescription]]; prefix = [prefix stringByAppendingString:@" "]; diff --git a/baRSS/StoreCoordinator.h b/baRSS/StoreCoordinator.h new file mode 100644 index 0000000..8dbbbfd --- /dev/null +++ b/baRSS/StoreCoordinator.h @@ -0,0 +1,31 @@ +// +// The MIT License (MIT) +// Copyright (c) 2018 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import + +@class Feed; + +@interface StoreCoordinator : NSObject ++ (void)save; ++ (void)deleteUnreferencedFeeds; ++ (Feed*)createFeedFromDictionary:(NSDictionary*)obj; +@end diff --git a/baRSS/StoreCoordinator.m b/baRSS/StoreCoordinator.m new file mode 100644 index 0000000..d6946fe --- /dev/null +++ b/baRSS/StoreCoordinator.m @@ -0,0 +1,77 @@ +// +// The MIT License (MIT) +// Copyright (c) 2018 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import "StoreCoordinator.h" +#import "AppDelegate.h" +#import "DBv1+CoreDataModel.h" + +@implementation StoreCoordinator + ++ (NSManagedObjectContext*)getContext { + return [(AppDelegate*)[NSApp delegate] persistentContainer].viewContext; +} + ++ (void)save { + [(AppDelegate*)[NSApp delegate] saveAction:nil]; +} + ++ (void)deleteUnreferencedFeeds { + NSManagedObjectContext *moc = [self getContext]; + NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:Feed.entity.name]; + fr.predicate = [NSPredicate predicateWithFormat:@"config = NULL"]; + NSBatchDeleteRequest *bdr = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fr]; + NSError *err; + [moc executeRequest:bdr error:&err]; + if (err) NSLog(@"%@", err); +} + ++ (Feed*)createFeedFromDictionary:(NSDictionary*)obj { + NSManagedObjectContext *moc = [self getContext]; + Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:moc]; + a.title = obj[@"feed"][@"title"]; + a.subtitle = obj[@"feed"][@"subtitle"]; + a.author = obj[@"feed"][@"author"]; + a.link = obj[@"feed"][@"link"]; + a.published = obj[@"feed"][@"published"]; + a.icon = obj[@"feed"][@"icon"]; + a.etag = obj[@"header"][@"etag"]; + a.date = obj[@"header"][@"date"]; + a.modified = obj[@"header"][@"modified"]; + for (NSDictionary *entry in obj[@"entries"]) { + FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:moc]; + b.title = entry[@"title"]; + b.subtitle = entry[@"subtitle"]; + b.author = entry[@"author"]; + b.link = entry[@"link"]; + b.published = entry[@"published"]; + b.summary = entry[@"summary"]; + for (NSString *tag in entry[@"tags"]) { + FeedTag *c = [[FeedTag alloc] initWithEntity:FeedTag.entity insertIntoManagedObjectContext:moc]; + c.name = tag; + [b addTagsObject:c]; + } + [a addItemsObject:b]; + } + return a; +} + +@end