Changed feed parsing from python lib to RSXML
- Bug fix: Changing url to malformed one will remove all entries - Bug fix: Add feed without selection resulted in a crash - Removed FeedTag from database model
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -80,7 +80,7 @@ xcuserdata/
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
|
||||
1
Cartfile.resolved
Normal file
1
Cartfile.resolved
Normal file
@@ -0,0 +1 @@
|
||||
github "relikd/RSXML" "c1b8eca0854aa4d1262dc5dfc054ec8dafb18609"
|
||||
@@ -7,14 +7,13 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1968EF7567E06D2A5BB3481A /* PyHandler.m */; };
|
||||
541A90F221257D77002680A6 /* MenuItemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 541A90F121257D77002680A6 /* MenuItemInfo.m */; };
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 54209E932117325100F3B5EF /* DrawImage.m */; };
|
||||
544B011A2114B41200386E5C /* ModalSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* ModalSheet.m */; };
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; };
|
||||
544FBD4521064AEB008A260C /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544FBD4421064AEB008A260C /* Python.framework */; };
|
||||
544FBD4721064B2F008A260C /* getFeed.py in Resources */ = {isa = PBXBuildFile; fileRef = 544FBD4621064B2F008A260C /* getFeed.py */; };
|
||||
544FBD4921064DF0008A260C /* feedparser521.py in Resources */ = {isa = PBXBuildFile; fileRef = 544FBD4821064DF0008A260C /* feedparser521.py */; };
|
||||
544DCCB9212A2B4D002DBC46 /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML.framework */; };
|
||||
544DCCBA212A2B4D002DBC46 /* RSXML.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
544DCCBE212A2B6F002DBC46 /* RSXML.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 544DCCBD212A2B6F002DBC46 /* RSXML.framework.dSYM */; };
|
||||
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */ = {isa = PBXBuildFile; fileRef = 546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */; };
|
||||
546FC43F21188C78007CC3A3 /* SettingsFeeds.xib in Resources */ = {isa = PBXBuildFile; fileRef = 546FC43E21188C78007CC3A3 /* SettingsFeeds.xib */; };
|
||||
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */ = {isa = PBXBuildFile; fileRef = 546FC44121189975007CC3A3 /* SettingsGeneral.m */; };
|
||||
@@ -32,9 +31,31 @@
|
||||
54FE73D3212316CD003EAC65 /* BarMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FE73D2212316CD003EAC65 /* BarMenu.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
544DCCBB212A2B4D002DBC46 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
544DCCBA212A2B4D002DBC46 /* RSXML.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
544DCCBC212A2B5A002DBC46 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
544DCCBE212A2B6F002DBC46 /* RSXML.framework.dSYM in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1968E7919BAA36F042FCB717 /* PyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyHandler.h; sourceTree = "<group>"; };
|
||||
1968EF7567E06D2A5BB3481A /* PyHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PyHandler.m; sourceTree = "<group>"; };
|
||||
541A90F021257D77002680A6 /* MenuItemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuItemInfo.h; sourceTree = "<group>"; };
|
||||
541A90F121257D77002680A6 /* MenuItemInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuItemInfo.m; sourceTree = "<group>"; };
|
||||
54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = "<group>"; };
|
||||
@@ -43,9 +64,8 @@
|
||||
544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = "<group>"; };
|
||||
544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = "<group>"; };
|
||||
544B011C2114EE9100386E5C /* AppHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHook.m; sourceTree = "<group>"; };
|
||||
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 = "<group>"; usesTabs = 0; };
|
||||
544FBD4821064DF0008A260C /* feedparser521.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = feedparser521.py; sourceTree = "<group>"; usesTabs = 0; };
|
||||
544DCCB8212A2B4D002DBC46 /* RSXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML.framework; path = Carthage/Build/Mac/RSXML.framework; sourceTree = "<group>"; };
|
||||
544DCCBD212A2B6F002DBC46 /* RSXML.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = RSXML.framework.dSYM; path = Carthage/Build/Mac/RSXML.framework.dSYM; sourceTree = "<group>"; };
|
||||
546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsFeeds.h; sourceTree = "<group>"; };
|
||||
546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsFeeds.m; sourceTree = "<group>"; };
|
||||
546FC43E21188C78007CC3A3 /* SettingsFeeds.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsFeeds.xib; sourceTree = "<group>"; };
|
||||
@@ -78,7 +98,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
544FBD4521064AEB008A260C /* Python.framework in Frameworks */,
|
||||
544DCCB9212A2B4D002DBC46 /* RSXML.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -99,7 +119,8 @@
|
||||
544FBD4321064AEB008A260C /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
544FBD4421064AEB008A260C /* Python.framework */,
|
||||
544DCCB8212A2B4D002DBC46 /* RSXML.framework */,
|
||||
544DCCBD212A2B6F002DBC46 /* RSXML.framework.dSYM */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -130,17 +151,6 @@
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
549369F421091E6D001AF895 /* python */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1968E7919BAA36F042FCB717 /* PyHandler.h */,
|
||||
1968EF7567E06D2A5BB3481A /* PyHandler.m */,
|
||||
544FBD4621064B2F008A260C /* getFeed.py */,
|
||||
544FBD4821064DF0008A260C /* feedparser521.py */,
|
||||
);
|
||||
path = python;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54ACC27321061B3B0020715F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -161,7 +171,6 @@
|
||||
54ACC27E21061B3B0020715F /* baRSS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
549369F421091E6D001AF895 /* python */,
|
||||
544B011B2114EE9100386E5C /* AppHook.h */,
|
||||
544B011C2114EE9100386E5C /* AppHook.m */,
|
||||
541A90EF21257D4F002680A6 /* Status Bar Menu */,
|
||||
@@ -203,6 +212,8 @@
|
||||
54ACC27821061B3B0020715F /* Sources */,
|
||||
54ACC27921061B3B0020715F /* Frameworks */,
|
||||
54ACC27A21061B3B0020715F /* Resources */,
|
||||
544DCCBB212A2B4D002DBC46 /* Embed Frameworks */,
|
||||
544DCCBC212A2B5A002DBC46 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -264,8 +275,6 @@
|
||||
54ACC28621061B3C0020715F /* Assets.xcassets in Resources */,
|
||||
546FC44421189975007CC3A3 /* SettingsGeneral.xib in Resources */,
|
||||
546FC43F21188C78007CC3A3 /* SettingsFeeds.xib in Resources */,
|
||||
544FBD4921064DF0008A260C /* feedparser521.py in Resources */,
|
||||
544FBD4721064B2F008A260C /* getFeed.py in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -287,7 +296,6 @@
|
||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */,
|
||||
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
|
||||
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */,
|
||||
1968E0AE14B8E8A90E194980 /* PyHandler.m in Sources */,
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */,
|
||||
54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */,
|
||||
541A90F221257D77002680A6 /* MenuItemInfo.m in Sources */,
|
||||
@@ -432,6 +440,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||
);
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
@@ -481,6 +490,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||
);
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "AppHook.h"
|
||||
#import "PyHandler.h"
|
||||
#import "BarMenu.h"
|
||||
|
||||
@implementation AppHook
|
||||
@@ -34,12 +33,12 @@
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
_barMenu = [BarMenu new];
|
||||
[PyHandler prepare];
|
||||
printf("up and running\n");
|
||||
// https://feeds.feedburner.com/simpledesktops
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
||||
[PyHandler shutdown];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17G65" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1">
|
||||
<entity name="Feed" representedClassName="Feed" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="author" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="etag" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="httpEtag" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="httpModified" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Binary" customClassName="NSImage" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modified" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="published" optional="YES" attributeType="Transformable" customClassName="NSArray" syncable="YES"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="config" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedConfig" inverseName="feed" inverseEntity="FeedConfig" syncable="YES"/>
|
||||
@@ -17,6 +14,7 @@
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="refreshNum" optional="YES" attributeType="Integer 32" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="refreshUnit" optional="YES" attributeType="Integer 16" usesScalarValueType="YES" customClassName="NSUInteger" syncable="YES"/>
|
||||
<attribute name="scheduled" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="type" optional="YES" attributeType="Integer 16" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
|
||||
@@ -25,24 +23,19 @@
|
||||
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedConfig" inverseName="children" inverseEntity="FeedConfig" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="FeedItem" representedClassName="FeedItem" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="abstract" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="author" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="body" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="guid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="published" optional="YES" attributeType="Transformable" customClassName="NSArray" syncable="YES"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="summary" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO" customClassName="NSArray" syncable="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="unread" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="items" inverseEntity="Feed" syncable="YES"/>
|
||||
<relationship name="tags" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="FeedTag" inverseName="feedItem" inverseEntity="FeedTag" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="FeedTag" representedClassName="FeedTag" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="feedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedItem" inverseName="tags" inverseEntity="FeedItem" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Feed" positionX="-209" positionY="-3" width="128" height="210"/>
|
||||
<element name="FeedConfig" positionX="-20" positionY="-126" width="128" height="180"/>
|
||||
<element name="Feed" positionX="-209" positionY="-3" width="128" height="165"/>
|
||||
<element name="FeedConfig" positionX="-20" positionY="-126" width="128" height="195"/>
|
||||
<element name="FeedItem" positionX="-20" positionY="81" width="128" height="180"/>
|
||||
<element name="FeedTag" positionX="187" positionY="171" width="128" height="75"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -21,7 +21,8 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <RSXML/RSXML.h>
|
||||
|
||||
@interface FeedDownload : NSObject
|
||||
+ (void)getFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block;
|
||||
+ (void)getFeed:(NSString *)url block:(void(^)(RSParsedFeed *feed, NSError* error, NSHTTPURLResponse* response))block;
|
||||
@end
|
||||
|
||||
@@ -21,19 +21,30 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "FeedDownload.h"
|
||||
#import "PyHandler.h"
|
||||
|
||||
@implementation FeedDownload
|
||||
|
||||
+ (void)getFeed:(NSString*)url withBlock:(nullable void (^)(NSDictionary* result, NSError* error))block {
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
NSDictionary *dict = [PyHandler getFeed:url withEtag:nil andModified:nil];
|
||||
NSError *err = nil;
|
||||
if (!dict || [dict[@"entries"] count] == 0 ) {
|
||||
err = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotParseResponse userInfo:nil];
|
||||
+ (void)getFeed:(NSString *)url block:(void(^)(RSParsedFeed *feed, NSError* error, NSHTTPURLResponse* response))block {
|
||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
|
||||
req.timeoutInterval = 30;
|
||||
req.cachePolicy = NSURLRequestReloadIgnoringCacheData;
|
||||
// [req setValue:@"Mon, 10 Sep 2018 10:32:19 GMT" forHTTPHeaderField:@"If-Modified-Since"];
|
||||
// [req setValue:@"wII2pETT9EGmlqyCHBFJpm25/7w" forHTTPHeaderField:@"If-None-Match"]; // ETag
|
||||
[[[NSURLSession sharedSession] dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
|
||||
// NSString* etag = [httpResponse allHeaderFields][@"Etag"];
|
||||
// if (etag.length > 5 && [[etag substringFromIndex:etag.length - 5] isEqualToString:@"-gzip"]) {
|
||||
// etag = [etag substringToIndex:etag.length - 5];
|
||||
// }
|
||||
if (error || [httpResponse statusCode] == 304) {
|
||||
block(nil, error, httpResponse);
|
||||
return;
|
||||
}
|
||||
if (block) block(dict, err);
|
||||
}];
|
||||
RSXMLData *xml = [[RSXMLData alloc] initWithData:data urlString:url];
|
||||
RSParseFeed(xml, ^(RSParsedFeed * _Nullable parsedFeed, NSError * _Nullable err) {
|
||||
block(parsedFeed, err, httpResponse);
|
||||
});
|
||||
}] resume];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -30,5 +30,10 @@
|
||||
<string>Copyright © 2018 relikd. All rights reserved.</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -36,9 +36,10 @@
|
||||
|
||||
@property (copy) NSString *previousURL;
|
||||
@property (strong) NSError *feedError;
|
||||
@property (strong) NSDictionary *feedResult;
|
||||
@property (strong) RSParsedFeed *feedResult;
|
||||
|
||||
@property (assign) BOOL shouldSaveObject;
|
||||
@property (assign) BOOL shouldDeletePrevArticles;
|
||||
@property (assign) BOOL objectNeedsSaving;
|
||||
@property (assign) BOOL objectIsModified;
|
||||
@end
|
||||
@@ -51,6 +52,7 @@
|
||||
self.previousURL = @"";
|
||||
self.refreshNum.intValue = 30;
|
||||
self.shouldSaveObject = NO;
|
||||
self.shouldDeletePrevArticles = NO;
|
||||
self.objectNeedsSaving = NO;
|
||||
self.objectIsModified = NO;
|
||||
|
||||
@@ -104,12 +106,12 @@
|
||||
if (item.refreshUnit != self.refreshUnit.indexOfSelectedItem)
|
||||
item.refreshUnit = (int16_t)self.refreshUnit.indexOfSelectedItem;
|
||||
|
||||
if (self.feedResult) {
|
||||
if (self.shouldDeletePrevArticles) {
|
||||
[item.managedObjectContext performBlockAndWait:^{
|
||||
Feed *rss = [StoreCoordinator createFeedFromDictionary:self.feedResult inContext:item.managedObjectContext];
|
||||
if (item.feed)
|
||||
[item.managedObjectContext deleteObject:(NSManagedObject*)item.feed];
|
||||
item.feed = rss;
|
||||
if (self.feedResult)
|
||||
item.feed = [StoreCoordinator createFeedFrom:self.feedResult inContext:item.managedObjectContext];
|
||||
}];
|
||||
}
|
||||
if ([item.managedObjectContext hasChanges]) {
|
||||
@@ -138,16 +140,19 @@
|
||||
|
||||
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
||||
if (obj.object == self.url && [self urlHasChanged]) {
|
||||
self.shouldDeletePrevArticles = YES;
|
||||
self.previousURL = self.url.stringValue;
|
||||
self.feedResult = nil;
|
||||
self.feedError = nil;
|
||||
[self.spinnerURL startAnimation:nil];
|
||||
[self.spinnerName startAnimation:nil];
|
||||
[FeedDownload getFeed:self.previousURL withBlock:^(NSDictionary *result, NSError *error) {
|
||||
[FeedDownload getFeed:self.previousURL block:^(RSParsedFeed *result, NSError *error, NSHTTPURLResponse* response) {
|
||||
self.feedResult = result;
|
||||
self.feedError = error; // warning indicator .hidden is bound to feedError
|
||||
// TODO: play error sound?
|
||||
// [httpResponse allHeaderFields][@"Date"]; // @"Expires", @"Last-Modified"
|
||||
// [httpResponse allHeaderFields][@"Etag"];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// TODO: play error sound?
|
||||
self.feedError = error; // warning indicator .hidden is bound to feedError
|
||||
self.objectNeedsSaving = YES; // stays YES if this block runs after updateRepresentedObject:
|
||||
[self setTitleFromFeed];
|
||||
[self.spinnerURL stopAnimation:nil];
|
||||
@@ -159,7 +164,7 @@
|
||||
|
||||
- (void)setTitleFromFeed {
|
||||
if ([self.name.stringValue isEqualToString:@""]) {
|
||||
self.name.objectValue = self.feedResult[@"feed"][@"title"];
|
||||
self.name.objectValue = self.feedResult.title;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,10 +144,12 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
|
||||
- (FeedConfig*)insertSortedItemAtSelection {
|
||||
NSIndexPath *selectedIndex = [self.dataStore selectionIndexPath];
|
||||
if (selectedIndex == NULL)
|
||||
selectedIndex = [NSIndexPath new];
|
||||
NSIndexPath *insertIndex = selectedIndex;
|
||||
|
||||
FeedConfig *selected = [[[self.dataStore arrangedObjects] descendantNodeAtIndexPath:selectedIndex] representedObject];
|
||||
NSUInteger lastIndex = selected.children.count;
|
||||
NSUInteger lastIndex = (selected ? selected.children.count : self.dataStore.arrangedObjects.childNodes.count);
|
||||
BOOL groupSelected = (selected.typ == GROUP);
|
||||
|
||||
if (!groupSelected) {
|
||||
|
||||
@@ -74,7 +74,7 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
sleep(1);
|
||||
[self performSelectorInBackground:@selector(donothing) withObject:nil];
|
||||
}
|
||||
|
||||
// TODO: remove debugging stuff
|
||||
- (void)printUnreadRecurisve:(NSMenu*)menu str:(NSString*)prefix {
|
||||
for (NSMenuItem *item in menu.itemArray) {
|
||||
MenuItemInfo *info = item.representedObject;
|
||||
@@ -103,8 +103,8 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
self.barItem.image = [[RSSIcon templateIcon:16 tint:nil] image];
|
||||
self.barItem.image.template = YES;
|
||||
}
|
||||
NSLog(@"==> %d", self.unreadCountTotal);
|
||||
[self printUnreadRecurisve:self.barItem.menu str:@""];
|
||||
// NSLog(@"==> %d", self.unreadCountTotal);
|
||||
// [self printUnreadRecurisve:self.barItem.menu str:@""];
|
||||
}
|
||||
|
||||
|
||||
@@ -123,9 +123,11 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
[self defaultHeaderForMenu:menu scope:ScopeGlobal];
|
||||
|
||||
self.unreadCountTotal = 0;
|
||||
@autoreleasepool {
|
||||
for (FeedConfig *fc in [StoreCoordinator sortedFeedConfigItems]) {
|
||||
[menu addItem:[self menuItemForFeedConfig:fc unread:&_unreadCountTotal]];
|
||||
}
|
||||
}
|
||||
[self updateBarIcon];
|
||||
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
@@ -214,7 +216,12 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle:item.title action:@selector(openFeedURL:) keyEquivalent:@""];
|
||||
mi.target = self;
|
||||
mi.representedObject = [MenuItemInfo withID:item.objectID unread:(item.unread ? 1 : 0)];
|
||||
mi.toolTip = item.subtitle;
|
||||
//mi.toolTip = item.abstract;
|
||||
// TODO: Do regex during save, not during display. Its here for testing purposes ...
|
||||
if (item.abstract.length > 0) {
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"<[^>]*>" options:kNilOptions error:nil];
|
||||
mi.toolTip = [regex stringByReplacingMatchesInString:item.abstract options:kNilOptions range:NSMakeRange(0, item.abstract.length) withTemplate:@""];
|
||||
}
|
||||
mi.enabled = (item.link.length > 0);
|
||||
mi.state = (item.unread ? NSControlStateValueOn : NSControlStateValueOff);
|
||||
mi.tag = ScopeLocal;
|
||||
@@ -278,17 +285,17 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
self.prefWindow = [[Preferences alloc] initWithWindowNibName:@"Preferences"];
|
||||
self.prefWindow.window.title = [NSString stringWithFormat:@"%@ %@", NSProcessInfo.processInfo.processName,
|
||||
NSLocalizedString(@"Preferences", nil)];
|
||||
// one time token to set reference to nil, which will release window
|
||||
NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter];
|
||||
__block id token = [center addObserverForName:NSWindowWillCloseNotification object:self.prefWindow.window queue:nil usingBlock:^(NSNotification *note) {
|
||||
self.prefWindow = nil;
|
||||
[center removeObserver:token];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferencesClosed:) name:NSWindowWillCloseNotification object:self.prefWindow.window];
|
||||
}
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[self.prefWindow showWindow:nil];
|
||||
}
|
||||
|
||||
- (void)preferencesClosed:(id)sender {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:self.prefWindow.window];
|
||||
self.prefWindow = nil;
|
||||
}
|
||||
|
||||
|
||||
- (void)pauseUpdates:(NSMenuItem*)sender {
|
||||
NSLog(@"1pause");
|
||||
@@ -441,11 +448,13 @@ typedef NS_OPTIONS(NSInteger, MenuItemTag) {
|
||||
[[self requestFeedConfigForMenuItem:sender.parentItem] descendantFeedItems:block];
|
||||
} else {
|
||||
// Sadly we can't just fetch the list of FeedItems since it is not ordered (in case open 10 at a time)
|
||||
@autoreleasepool {
|
||||
for (FeedConfig *config in [StoreCoordinator sortedFeedConfigItems]) {
|
||||
if ([config descendantFeedItems:block] == NO)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
#import "DBv1+CoreDataModel.h"
|
||||
#import "FeedConfig+Ext.h"
|
||||
|
||||
@class RSParsedFeed;
|
||||
|
||||
@interface StoreCoordinator : NSObject
|
||||
+ (void)saveContext:(NSManagedObjectContext*)context;
|
||||
+ (void)deleteUnreferencedFeeds;
|
||||
+ (NSArray<FeedConfig*>*)sortedFeedConfigItems;
|
||||
+ (id)objectWithID:(NSManagedObjectID*)objID;
|
||||
+ (Feed*)createFeedFromDictionary:(NSDictionary*)obj inContext:(NSManagedObjectContext*)context;
|
||||
+ (Feed*)createFeedFrom:(RSParsedFeed*)obj inContext:(NSManagedObjectContext*)context;
|
||||
@end
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import "StoreCoordinator.h"
|
||||
#import "AppHook.h"
|
||||
#import <RSXML/RSXML.h>
|
||||
|
||||
@implementation StoreCoordinator
|
||||
|
||||
@@ -66,30 +67,23 @@
|
||||
return [[self getContext] objectWithID:objID];
|
||||
}
|
||||
|
||||
+ (Feed*)createFeedFromDictionary:(NSDictionary*)obj inContext:(NSManagedObjectContext*)context {
|
||||
+ (Feed*)createFeedFrom:(RSParsedFeed*)obj inContext:(NSManagedObjectContext*)context {
|
||||
Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:context];
|
||||
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"]) {
|
||||
a.title = obj.title;
|
||||
a.subtitle = obj.subtitle;
|
||||
a.link = obj.link;
|
||||
for (RSParsedArticle *entry in obj.articles) {
|
||||
FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:context];
|
||||
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:context];
|
||||
c.name = tag;
|
||||
[b addTagsObject:c];
|
||||
}
|
||||
b.guid = entry.guid;
|
||||
b.title = entry.title;
|
||||
b.abstract = entry.abstract;
|
||||
b.body = entry.body;
|
||||
b.author = entry.author;
|
||||
b.link = entry.link;
|
||||
b.published = entry.datePublished;
|
||||
// TODO: remove NSLog()
|
||||
if (!entry.datePublished)
|
||||
NSLog(@"No date for feed '%@'", obj.urlString);
|
||||
[a addItemsObject:b];
|
||||
}
|
||||
return a;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
@interface PyHandler : NSObject
|
||||
+ (void)prepare; // must be called before getFeed
|
||||
+ (void)shutdown;
|
||||
+ (NSDictionary *)getFeed:(NSString *)url withEtag:(NSString *)etag andModified:(NSString *)modified;
|
||||
@end
|
||||
@@ -1,156 +0,0 @@
|
||||
//
|
||||
// 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 "PyHandler.h"
|
||||
#import <Python/Python.h>
|
||||
|
||||
static PyObject *parseFeedModule;
|
||||
|
||||
@implementation PyHandler
|
||||
|
||||
+ (PyObject*)appBundlePath {
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
CFURLRef appPath = CFBundleCopyResourcesDirectoryURL(mainBundle);
|
||||
CFURLRef absolutePath = CFURLCopyAbsoluteURL(appPath);
|
||||
CFStringRef path = CFURLCopyFileSystemPath(absolutePath, kCFURLPOSIXPathStyle);
|
||||
PyObject * pyStr = PyString_FromString(CFStringGetCStringPtr(path, CFStringGetSystemEncoding()));
|
||||
// const char *resourcePath = [[[NSBundle mainBundle] resourcePath] UTF8String];
|
||||
CFRelease(path);
|
||||
CFRelease(absolutePath);
|
||||
CFRelease(appPath);
|
||||
return pyStr;
|
||||
}
|
||||
|
||||
+ (void)prepare {
|
||||
Py_Initialize();
|
||||
PyObject *sys = PyImport_Import(PyString_FromString("sys"));
|
||||
PyObject *sys_path_append = PyObject_GetAttrString(PyObject_GetAttrString(sys, "path"), "append");
|
||||
PyObject *resourcePath = PyTuple_New(1);
|
||||
PyTuple_SetItem(resourcePath, 0, [self appBundlePath]);
|
||||
PyObject_CallObject(sys_path_append, resourcePath);
|
||||
|
||||
// import MyModule # this is in my project folder
|
||||
PyObject *myModule = PyImport_Import(PyString_FromString("getFeed"));
|
||||
parseFeedModule = PyObject_GetAttrString(myModule, "parse");
|
||||
}
|
||||
|
||||
+ (void)shutdown {
|
||||
PyObject_Free(parseFeedModule);
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
+ (char*)run:(PyObject*)args {
|
||||
if (parseFeedModule && PyCallable_Check(parseFeedModule)) {
|
||||
PyObject *result = PyObject_CallObject(parseFeedModule, args);
|
||||
if (result != NULL && PyObject_TypeCheck(result, &PyString_Type))
|
||||
return PyString_AsString(result);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+ (char *)run:(const char *)url withEtag:(const char *)etag andDateString:(const char *)date {
|
||||
if (!Py_IsInitialized())
|
||||
return NULL;
|
||||
return [self run:Py_BuildValue("(z z z)", url, etag, date)];
|
||||
}
|
||||
|
||||
+ (char *)run:(const char *)url withEtag:(const char *)etag andDateArray:(int *)d {
|
||||
if (!Py_IsInitialized())
|
||||
return NULL;
|
||||
if (d == NULL || abs(d[8]) > 1) { // d[8] == tm_isdst (between -1 and 1). Array size must be 9
|
||||
return [self run:Py_BuildValue("(z z z)", url, etag, NULL)];
|
||||
}
|
||||
return [self run:Py_BuildValue("(z z [iiiiiiiii])", url, etag,
|
||||
d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8])];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)getFeed:(NSString *)url withEtag:(NSString *)etag andModified:(NSString *)modified {
|
||||
const char* u = NULL;
|
||||
const char* e = NULL;
|
||||
const char* m = NULL;
|
||||
if (url && url.length > 0)
|
||||
u = [url UTF8String];
|
||||
if (etag && etag.length > 0)
|
||||
e = [etag UTF8String];
|
||||
if (modified && modified.length > 0)
|
||||
m = [modified UTF8String];
|
||||
|
||||
char *data = [self run:u withEtag:e andDateString:m];
|
||||
printf("JSON result:\n%s\n\n", data);
|
||||
if (data == NULL) return nil;
|
||||
|
||||
NSError *error = nil;
|
||||
id object = [NSJSONSerialization JSONObjectWithData:
|
||||
[[NSString stringWithUTF8String:data] dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:0 error:&error];
|
||||
|
||||
if (error || !object || ![object isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
// @see: https://docs.python.org/3/c-api/index.html
|
||||
/* PyObject *ObjcToPyObject(id object)
|
||||
{
|
||||
if (object == nil) {
|
||||
// This technically doesn't need to be an extra case,
|
||||
// but you may want to differentiate it for error checking
|
||||
return NULL;
|
||||
} else if ([object isKindOfClass:[NSString class]]) {
|
||||
return PyString_FromString([object UTF8String]);
|
||||
} else if ([object isKindOfClass:[NSNumber class]]) {
|
||||
// You could probably do some extra checking here if you need to
|
||||
// with the -objCType method.
|
||||
return PyLong_FromLong([object longValue]);
|
||||
} else if ([object isKindOfClass:[NSArray class]]) {
|
||||
// You may want to differentiate between NSArray (analagous to tuples)
|
||||
// and NSMutableArray (analagous to lists) here.
|
||||
Py_ssize_t i, len = [object count];
|
||||
PyObject *list = PyList_New(len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
PyObject *item = ObjcToPyObject([object objectAtIndex:i]);
|
||||
NSCAssert(item != NULL, @"Can't add NULL item to Python List");
|
||||
// Note that PyList_SetItem() "steals" the reference to the passed item.
|
||||
// (i.e., you do not need to release it)
|
||||
PyList_SetItem(list, i, item);
|
||||
}
|
||||
return list;
|
||||
} else if ([object isKindOfClass:[NSDictionary class]]) {
|
||||
PyObject *dict = PyDict_New();
|
||||
for (id key in object) {
|
||||
PyObject *pyKey = ObjcToPyObject(key);
|
||||
NSCAssert(pyKey != NULL, @"Can't add NULL key to Python Dictionary");
|
||||
PyObject *pyItem = ObjcToPyObject([object objectForKey:key]);
|
||||
NSCAssert(pyItem != NULL, @"Can't add NULL item to Python Dictionary");
|
||||
PyDict_SetItem(dict, pyKey, pyItem);
|
||||
Py_DECREF(pyKey);
|
||||
Py_DECREF(pyItem);
|
||||
}
|
||||
return dict;
|
||||
} else {
|
||||
NSLog(@"ObjcToPyObject() could not convert Obj-C object to PyObject.");
|
||||
return NULL;
|
||||
}
|
||||
}*/
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = """
|
||||
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 feedparser521 as fp
|
||||
import json
|
||||
import time
|
||||
|
||||
COPY_ENTRY_TAGS = False
|
||||
COPY_ENTRY_SUMMARY = False
|
||||
|
||||
|
||||
def valueFormatter(key, obj):
|
||||
if isinstance(obj, time.struct_time):
|
||||
return list(obj)
|
||||
if key == "etag":
|
||||
# stupid server convention to append but not consider changed etag
|
||||
# some servers append '-gzip' if gzip header is sent
|
||||
return obj.replace("-gzip", "")
|
||||
return obj
|
||||
|
||||
|
||||
def copyIfExists(source, source_path, target, target_path):
|
||||
src = source
|
||||
trgt = target
|
||||
try:
|
||||
srcPTH = source_path.split("/")
|
||||
trgtPTH = target_path.split("/")
|
||||
for x in srcPTH[:-1]:
|
||||
src = src[x]
|
||||
for x in trgtPTH[:-1]:
|
||||
trgt = trgt[x]
|
||||
|
||||
key = srcPTH[-1]
|
||||
trgt[trgtPTH[-1]] = valueFormatter(key, src[key])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def prepareResult(obj):
|
||||
r = {"header": dict(), "feed": dict(), "entries": list()}
|
||||
try:
|
||||
if obj.debug_message.startswith("The feed has not changed since"):
|
||||
obj.status = 304
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
r["header"]["status"] = obj.status
|
||||
if obj.status == 304 or len(obj.entries) == 0:
|
||||
return r
|
||||
except Exception:
|
||||
return r
|
||||
|
||||
copyIfExists(obj, "etag", r, "header/etag")
|
||||
copyIfExists(obj, "modified", r, "header/modified")
|
||||
copyIfExists(obj, "headers/date", r, "header/date")
|
||||
copyIfExists(obj, "feed/title", r, "feed/title")
|
||||
copyIfExists(obj, "feed/subtitle", r, "feed/subtitle")
|
||||
copyIfExists(obj, "feed/author", r, "feed/author")
|
||||
copyIfExists(obj, "feed/link", r, "feed/link")
|
||||
copyIfExists(obj, "feed/image/href", r, "feed/icon")
|
||||
copyIfExists(obj, "feed/published_parsed", r, "feed/published")
|
||||
|
||||
for entry in obj.entries:
|
||||
e = dict()
|
||||
copyIfExists(entry, "title", e, "title")
|
||||
copyIfExists(entry, "subtitle", e, "subtitle")
|
||||
copyIfExists(entry, "author", e, "author")
|
||||
copyIfExists(entry, "link", e, "link")
|
||||
copyIfExists(entry, "published_parsed", e, "published")
|
||||
if COPY_ENTRY_SUMMARY:
|
||||
copyIfExists(entry, "summary", e, "summary")
|
||||
if COPY_ENTRY_TAGS:
|
||||
try:
|
||||
e["tags"] = list()
|
||||
for tag in entry.tags:
|
||||
e["tags"].append(tag.term)
|
||||
except Exception:
|
||||
pass
|
||||
r["entries"].append(e)
|
||||
return r
|
||||
|
||||
|
||||
def parse(url, etag=None, modified=None):
|
||||
if isinstance(modified, list):
|
||||
modified = time.struct_time(modified)
|
||||
d = fp.parse(url, etag=etag, modified=modified)
|
||||
return json.dumps(prepareResult(d), separators=(',', ':'))
|
||||
Reference in New Issue
Block a user