Refactoring refresh interval handling
This commit is contained in:
14
README.md
14
README.md
@@ -26,12 +26,24 @@ All basic functionality is there. What's missing?
|
||||
|
||||
- Authenticated feeds
|
||||
- Online sync with other services
|
||||
- Automatic feed detection (e.g., YouTube)
|
||||
- Text / UI localisation
|
||||
- App icon & UI icons
|
||||
|
||||
All in all, the software is in a usable state. The remaining features will be added in the coming weeks.
|
||||
|
||||
|
||||
Hidden options
|
||||
--------------
|
||||
|
||||
1) When holding down the option key, the menu will show an item to open only a few unread items at a time. This number can be changed with the following Terminal command (default: 10):
|
||||
|
||||
defaults write de.relikd.baRSS openFewLinksLimit -int 10
|
||||
|
||||
2) In preferences you can choose to show 'Short article names'. This will limit the number of displayed characters to 60 (default). With this Terminal command you can customize this number:
|
||||
|
||||
defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
|
||||
|
||||
|
||||
ToDo
|
||||
----
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
|
||||
54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; };
|
||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.m */; };
|
||||
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54BB048821FD2AB500C303A5 /* NSDate+Ext.m */; };
|
||||
54CC04382162532A00A48795 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CC04372162532A00A48795 /* main.m */; };
|
||||
54CC043E2162566900A48795 /* baRSS-Helper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 54CC042C2162532800A48795 /* baRSS-Helper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; };
|
||||
@@ -115,6 +116,8 @@
|
||||
54ACC29421061E270020715F /* FeedDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedDownload.m; sourceTree = "<group>"; };
|
||||
54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; };
|
||||
54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = "<group>"; };
|
||||
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Ext.h"; sourceTree = "<group>"; };
|
||||
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Ext.m"; sourceTree = "<group>"; };
|
||||
54CC042C2162532800A48795 /* baRSS-Helper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "baRSS-Helper.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54CC04362162532A00A48795 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
54CC04372162532A00A48795 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
@@ -183,6 +186,8 @@
|
||||
54209E932117325100F3B5EF /* DrawImage.m */,
|
||||
544936F921F1E66100DEE9AA /* Statistics.h */,
|
||||
544936FA21F1E66100DEE9AA /* Statistics.m */,
|
||||
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */,
|
||||
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */,
|
||||
);
|
||||
path = Helper;
|
||||
sourceTree = "<group>";
|
||||
@@ -411,6 +416,7 @@
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */,
|
||||
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */,
|
||||
54ACC29521061E270020715F /* FeedDownload.m in Sources */,
|
||||
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */,
|
||||
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */,
|
||||
544936FB21F1E66100DEE9AA /* Statistics.m in Sources */,
|
||||
540F704521B6C16C0022E69D /* FeedMeta+Ext.m in Sources */,
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#import "FeedArticle+CoreDataClass.h"
|
||||
#import "StoreCoordinator.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <RSXML/RSXML.h>
|
||||
|
||||
@implementation Feed (Ext)
|
||||
@@ -46,7 +45,7 @@
|
||||
NSInteger lastIndex = [StoreCoordinator numberRootItemsInContext:moc];
|
||||
FeedGroup *fg = [FeedGroup newGroup:FEED inContext:moc];
|
||||
[fg setParent:nil andSortIndex:(int32_t)lastIndex];
|
||||
[fg.feed.meta setRefresh:30 unit:RefreshUnitMinutes];
|
||||
[fg.feed.meta setRefreshAndSchedule:kDefaultFeedRefreshInterval];
|
||||
return fg.feed;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,4 +44,5 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||
- (BOOL)iterateSorted:(BOOL)ordered overDescendantFeeds:(void(^)(Feed *feed, BOOL* cancel))block;
|
||||
// Printing
|
||||
- (NSString*)readableDescription;
|
||||
- (nonnull NSString*)refreshString;
|
||||
@end
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "Feed+Ext.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "NSDate+Ext.h"
|
||||
|
||||
@implementation FeedGroup (Ext)
|
||||
|
||||
@@ -118,8 +117,19 @@
|
||||
case SEPARATOR: return @"-------------";
|
||||
case GROUP: return [NSString stringWithFormat:@"%@", self.name];
|
||||
case FEED:
|
||||
return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.feed.meta.url, self.refreshStr];
|
||||
return [NSString stringWithFormat:@"%@ (%@) - %@", self.name, self.feed.meta.url, [self refreshString]];
|
||||
}
|
||||
}
|
||||
|
||||
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
|
||||
- (nonnull NSString*)refreshString {
|
||||
if (self.type == FEED) {
|
||||
int32_t refresh = self.feed.meta.refresh;
|
||||
if (refresh <= 0)
|
||||
return @"∞"; // ∞ ƒ Ø
|
||||
return [NSDate stringForInterval:refresh rounded:NO];
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,22 +22,14 @@
|
||||
|
||||
#import "FeedMeta+CoreDataClass.h"
|
||||
|
||||
/// Easy memorable @c int16_t enum for refresh unit index
|
||||
typedef NS_ENUM(int16_t, RefreshUnitType) {
|
||||
RefreshUnitSeconds = 0, RefreshUnitMinutes = 1, RefreshUnitHours = 2, RefreshUnitDays = 3, RefreshUnitWeeks = 4
|
||||
};
|
||||
|
||||
static const int32_t kDefaultFeedRefreshInterval = 30 * 60;
|
||||
|
||||
@interface FeedMeta (Ext)
|
||||
@property (readonly) BOOL refreshIntervalDisabled; // self.refreshNum <= 0
|
||||
@property (readonly) int32_t refreshInterval; // self.refreshNum * RefreshUnitValue
|
||||
|
||||
// HTTP response
|
||||
- (void)setErrorAndPostponeSchedule;
|
||||
- (void)setSucessfulWithResponse:(NSHTTPURLResponse*)response;
|
||||
// Setter
|
||||
- (void)setUrlIfChanged:(NSString*)url;
|
||||
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
||||
- (BOOL)setRefresh:(int32_t)refresh unit:(RefreshUnitType)unit;
|
||||
- (BOOL)setRefreshAndUnitFromInterval:(int32_t)interval;
|
||||
- (BOOL)setRefreshAndSchedule:(int32_t)refresh;
|
||||
@end
|
||||
|
||||
@@ -24,30 +24,8 @@
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
|
||||
/// smhdw: [1, 60, 3600, 86400, 604800]
|
||||
static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhdw
|
||||
|
||||
@implementation FeedMeta (Ext)
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
/// Check whether update interval is disabled by user (refresh interval is 0).
|
||||
- (BOOL)refreshIntervalDisabled {
|
||||
return (self.refreshNum <= 0);
|
||||
}
|
||||
|
||||
/// @return Time interval respecting the selected unit. E.g., returns @c 180 for @c '3m'
|
||||
- (int32_t)refreshInterval {
|
||||
return self.refreshNum * RefreshUnitValues[self.refreshUnit % 5];
|
||||
}
|
||||
|
||||
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
|
||||
- (NSString*)readableRefreshString {
|
||||
if (self.refreshIntervalDisabled)
|
||||
return @"∞"; // ∞ ƒ Ø
|
||||
return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]];
|
||||
}
|
||||
|
||||
#pragma mark - HTTP response
|
||||
|
||||
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
||||
@@ -68,7 +46,7 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
||||
self.errorCount = 0; // reset counter
|
||||
NSDictionary *header = [response allHeaderFields];
|
||||
[self setEtag:header[@"Etag"] modified:header[@"Date"]]; // @"Expires", @"Last-Modified"
|
||||
[self scheduleNow:[self refreshInterval]];
|
||||
[self scheduleNow:self.refresh];
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
@@ -85,44 +63,22 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
||||
}
|
||||
|
||||
/**
|
||||
Set @c refresh and @c unit from popup button selection. Only values that differ will be updated.
|
||||
Also, calculate and set new @c scheduled date and update FeedGroup @c refreshStr (if changed).
|
||||
Set @c refresh and calculate new @c scheduled date.
|
||||
|
||||
@return @c YES if refresh interval has changed
|
||||
*/
|
||||
- (BOOL)setRefresh:(int32_t)refresh unit:(RefreshUnitType)unit {
|
||||
BOOL intervalChanged = (self.refreshNum != refresh || self.refreshUnit != unit);
|
||||
if (self.refreshNum != refresh) self.refreshNum = refresh;
|
||||
if (self.refreshUnit != unit) self.refreshUnit = unit;
|
||||
|
||||
if (intervalChanged) {
|
||||
[self scheduleNow:[self refreshInterval]];
|
||||
NSString *str = [self readableRefreshString];
|
||||
if (![self.feed.group.refreshStr isEqualToString:str])
|
||||
self.feed.group.refreshStr = str;
|
||||
- (BOOL)setRefreshAndSchedule:(int32_t)refresh {
|
||||
if (self.refresh != refresh) {
|
||||
self.refresh = refresh;
|
||||
[self scheduleNow:self.refresh];
|
||||
return YES;
|
||||
}
|
||||
return intervalChanged;
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
Set properties @c refreshNum and @c refreshUnit to highest possible (integer-dividable-)unit.
|
||||
Only values that differ will be updated.
|
||||
Also, calculate and set new @c scheduled date and update FeedGroup @c refreshStr (if changed).
|
||||
|
||||
@return @c YES if refresh interval has changed
|
||||
*/
|
||||
- (BOOL)setRefreshAndUnitFromInterval:(int32_t)interval {
|
||||
for (RefreshUnitType i = 4; i >= 0; i--) { // start with weeks
|
||||
if (interval % RefreshUnitValues[i] == 0) { // find first unit that is dividable
|
||||
return [self setRefresh:abs(interval) / RefreshUnitValues[i] unit:i];
|
||||
}
|
||||
}
|
||||
return NO; // since loop didn't return, no value was changed
|
||||
}
|
||||
|
||||
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
|
||||
/// Set next scheduled feed update or @c nil if @c refresh @c <= @c 0.
|
||||
- (void)scheduleNow:(NSTimeInterval)future {
|
||||
if (self.refreshIntervalDisabled) { // update deactivated; manually update with force update all
|
||||
if (self.refresh <= 0) { // update deactivated; manually update with force update all
|
||||
if (self.scheduled != nil) // already nil? Avoid unnecessary core data edits
|
||||
self.scheduled = nil;
|
||||
} else {
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
// TODO: Add support for media player? image feed?
|
||||
// <enclosure url="https://url.mp3" length="63274022" type="audio/mpeg" />
|
||||
// TODO: Disable 'update all' menu item during update?
|
||||
// TODO: List of hidden preferences for readme
|
||||
// TODO: Do we need to search for favicon in places other than '../favicon.ico'?
|
||||
|
||||
|
||||
/**
|
||||
@c notification.object is @c NSNumber of type @c NSUInteger.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.18" systemVersion="17G4015" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.18" systemVersion="17G5019" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1">
|
||||
<entity name="Feed" representedClassName="Feed" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="indexPath" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
@@ -25,7 +25,6 @@
|
||||
</entity>
|
||||
<entity name="FeedGroup" representedClassName="FeedGroup" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="refreshStr" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="type" optional="YES" attributeType="Integer 16" defaultValueString="-1" usesScalarValueType="YES" syncable="YES"/>
|
||||
<relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedGroup" inverseName="parent" inverseEntity="FeedGroup" syncable="YES"/>
|
||||
@@ -40,8 +39,7 @@
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="etag" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modified" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="refreshNum" optional="YES" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="refreshUnit" optional="YES" attributeType="Integer 16" defaultValueString="-1" usesScalarValueType="YES" customClassName="NSUInteger" syncable="YES"/>
|
||||
<attribute name="refresh" optional="YES" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="scheduled" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="meta" inverseEntity="Feed" syncable="YES"/>
|
||||
@@ -49,8 +47,8 @@
|
||||
<elements>
|
||||
<element name="Feed" positionX="-278.84765625" positionY="-112.953125" width="128" height="180"/>
|
||||
<element name="FeedArticle" positionX="-96.77734375" positionY="-113.83984375" width="128" height="195"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="150"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="135"/>
|
||||
<element name="FeedIcon" positionX="-202.79296875" positionY="137.71875" width="128" height="75"/>
|
||||
<element name="FeedMeta" positionX="-348.02734375" positionY="136.89453125" width="128" height="165"/>
|
||||
<element name="FeedMeta" positionX="-348.02734375" positionY="136.89453125" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
24
baRSS/Helper/NSDate+Ext.h
Normal file
24
baRSS/Helper/NSDate+Ext.h
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
typedef int32_t Interval;
|
||||
typedef NS_ENUM(int32_t, TimeUnitType) {
|
||||
TimeUnitSeconds = 1,
|
||||
TimeUnitMinutes = 60,
|
||||
TimeUnitHours = 60 * 60,
|
||||
TimeUnitDays = 24 * 60 * 60,
|
||||
TimeUnitWeeks = 7 * 24 * 60 * 60,
|
||||
TimeUnitYears = 365 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
@interface NSDate (Ext)
|
||||
+ (nonnull NSString*)stringForInterval:(Interval)intv rounded:(BOOL)flag;
|
||||
@end
|
||||
|
||||
|
||||
@interface NSDate (RefreshControlsUI)
|
||||
+ (Interval)intervalForPopup:(NSPopUpButton*)unit andField:(NSTextField*)value;
|
||||
+ (void)setInterval:(Interval)intv forPopup:(NSPopUpButton*)popup andField:(NSTextField*)field;
|
||||
+ (void)populateUnitsMenu:(NSPopUpButton*)popup selected:(TimeUnitType)unit;
|
||||
@end
|
||||
101
baRSS/Helper/NSDate+Ext.m
Normal file
101
baRSS/Helper/NSDate+Ext.m
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
#import "NSDate+Ext.h"
|
||||
|
||||
static const char _shortnames[] = {'y','w','d','h','m','s'};
|
||||
static const char *_names[] = {"Years", "Weeks", "Days", "Hours", "Minutes", "Seconds"};
|
||||
static const TimeUnitType _values[] = {
|
||||
TimeUnitYears,
|
||||
TimeUnitWeeks,
|
||||
TimeUnitDays,
|
||||
TimeUnitHours,
|
||||
TimeUnitMinutes,
|
||||
TimeUnitSeconds,
|
||||
};
|
||||
|
||||
|
||||
@implementation NSDate (Ext)
|
||||
|
||||
+ (nonnull NSString*)stringForInterval:(Interval)intv rounded:(BOOL)flag {
|
||||
if (flag) {
|
||||
unsigned short i = [self floatUnitIndexForInterval:abs(intv)];
|
||||
return [NSString stringWithFormat:@"%1.1f%c", intv / (float)_values[i], _shortnames[i]];
|
||||
}
|
||||
unsigned short i = [self exactUnitIndexForInterval:abs(intv)];
|
||||
return [NSString stringWithFormat:@"%d%c", intv / _values[i], _shortnames[i]];
|
||||
}
|
||||
|
||||
/// @return Highest non-zero unit ( @c flag=YES ). Or highest integer-dividable unit ( @c flag=NO ).
|
||||
+ (TimeUnitType)unitForInterval:(Interval)intv rounded:(BOOL)flag {
|
||||
if (flag) {
|
||||
return _values[[self floatUnitIndexForInterval:abs(intv)]];
|
||||
}
|
||||
return _values[[self exactUnitIndexForInterval:abs(intv)]];
|
||||
}
|
||||
|
||||
/// @return Highest unit type that allows integer division. E.g., '61 minutes'.
|
||||
+ (unsigned short)exactUnitIndexForInterval:(Interval)intv {
|
||||
for (unsigned short i = 0; i < 5; i++)
|
||||
if (intv % _values[i] == 0) return i;
|
||||
return 5; // seconds
|
||||
}
|
||||
|
||||
/// @return Highest non-zero unit type. Can be used with fractions e.g., '1.1 hours'.
|
||||
+ (unsigned short)floatUnitIndexForInterval:(Interval)intv {
|
||||
for (unsigned short i = 0; i < 5; i++)
|
||||
if (intv > _values[i]) return i;
|
||||
return 5; // seconds
|
||||
}
|
||||
/* NOT USED
|
||||
/// Convert any unit to the next smaller one. Unit does not have to be exact.
|
||||
+ (TimeUnitType)smallerUnit:(TimeUnitType)unit {
|
||||
if (unit <= TimeUnitHours) return TimeUnitSeconds;
|
||||
if (unit <= TimeUnitDays) return TimeUnitMinutes; // > hours
|
||||
if (unit <= TimeUnitWeeks) return TimeUnitHours; // > days
|
||||
if (unit <= TimeUnitYears) return TimeUnitDays; // > weeks
|
||||
return TimeUnitWeeks; // > years
|
||||
}
|
||||
|
||||
/// @return Formatted string from @c timeIntervalSinceNow.
|
||||
- (nonnull NSString*)intervalStringWithDecimal:(BOOL)flag {
|
||||
return [NSDate stringForInterval:(Interval)[self timeIntervalSinceNow] rounded:flag];
|
||||
}
|
||||
|
||||
/// @return Highest non-zero unit ( @c flag=YES ). Or highest integer-dividable unit ( @c flag=NO ).
|
||||
- (TimeUnitType)unitWithDecimal:(BOOL)flag {
|
||||
Interval absIntv = abs((Interval)[self timeIntervalSinceNow]);
|
||||
if (flag) {
|
||||
return _values[ [NSDate floatUnitIndexForInterval:absIntv] ];
|
||||
}
|
||||
return _values[ [NSDate exactUnitIndexForInterval:absIntv] ];
|
||||
}
|
||||
*/
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSDate (RefreshControlsUI)
|
||||
|
||||
/// @return Interval by multiplying the text field value with the currently selected popup unit.
|
||||
+ (Interval)intervalForPopup:(NSPopUpButton*)unit andField:(NSTextField*)value {
|
||||
return value.intValue * (Interval)unit.selectedTag;
|
||||
}
|
||||
|
||||
/// Configure both @c NSControl elements based on the provided interval @c intv.
|
||||
+ (void)setInterval:(Interval)intv forPopup:(NSPopUpButton*)popup andField:(NSTextField*)field {
|
||||
TimeUnitType unit = [self unitForInterval:intv rounded:NO];
|
||||
[popup selectItemWithTag:unit];
|
||||
field.intValue = (int)(intv / unit);
|
||||
}
|
||||
|
||||
/// Insert all @c TimeUnitType items into popup button. Save unit value into @c tag attribute.
|
||||
+ (void)populateUnitsMenu:(NSPopUpButton*)popup selected:(TimeUnitType)unit {
|
||||
[popup removeAllItems];
|
||||
for (NSUInteger i = 0; i < 6; i++) {
|
||||
[popup addItemWithTitle:[NSString stringWithUTF8String:_names[i]]];
|
||||
NSMenuItem *item = popup.lastItem;
|
||||
[item setKeyEquivalent:[[NSString stringWithFormat:@"%c", _shortnames[i]] uppercaseString]];
|
||||
item.tag = _values[i];
|
||||
}
|
||||
[popup selectItemWithTag:unit];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -27,6 +27,8 @@
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "Statistics.h"
|
||||
#import "NSDate+Ext.h"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
|
||||
@@ -89,6 +91,7 @@
|
||||
[super viewDidLoad];
|
||||
self.previousURL = @"";
|
||||
self.refreshNum.intValue = 30;
|
||||
[NSDate populateUnitsMenu:self.refreshUnit selected:TimeUnitMinutes];
|
||||
self.warningIndicator.image = nil;
|
||||
[self.warningIndicator.cell setHighlightsBy:NSNoCellMask];
|
||||
[self populateTextFields:self.feedGroup];
|
||||
@@ -102,12 +105,8 @@
|
||||
self.name.objectValue = fg.name;
|
||||
self.url.objectValue = fg.feed.meta.url;
|
||||
self.previousURL = self.url.stringValue;
|
||||
self.refreshNum.intValue = fg.feed.meta.refreshNum;
|
||||
NSInteger unit = (NSInteger)fg.feed.meta.refreshUnit;
|
||||
if (unit < 0 || unit > self.refreshUnit.numberOfItems - 1)
|
||||
unit = self.refreshUnit.numberOfItems - 1;
|
||||
[self.refreshUnit selectItemAtIndex:unit];
|
||||
self.warningIndicator.image = [fg.feed iconImage16];
|
||||
[NSDate setInterval:fg.feed.meta.refresh forPopup:self.refreshUnit andField:self.refreshNum];
|
||||
[self statsForCoreDataObject];
|
||||
}
|
||||
|
||||
@@ -122,7 +121,8 @@
|
||||
[self.feedGroup setNameIfChanged:self.name.stringValue];
|
||||
FeedMeta *meta = feed.meta;
|
||||
[meta setUrlIfChanged:self.previousURL];
|
||||
[meta setRefresh:self.refreshNum.intValue unit:(int16_t)self.refreshUnit.indexOfSelectedItem]; // updateTimer will be scheduled once preferences is closed
|
||||
[meta setRefreshAndSchedule:[NSDate intervalForPopup:self.refreshUnit andField:self.refreshNum]];
|
||||
// updateTimer will be scheduled once preferences is closed
|
||||
if (self.didDownloadFeed) {
|
||||
[meta setEtag:self.httpEtag modified:self.httpDate];
|
||||
[feed updateWithRSS:self.feedResult postUnreadCountChange:YES];
|
||||
|
||||
@@ -86,24 +86,12 @@
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TUi-VS-ge4">
|
||||
<rect key="frame" x="198" y="-3" width="125" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Minutes" bezelStyle="rounded" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" altersStateOfSelectedItem="NO" selectedItem="CsM-KR-zzs" id="O0p-Tc-KQ1">
|
||||
<popUpButtonCell key="cell" type="push" title="-- list --" bezelStyle="rounded" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" altersStateOfSelectedItem="NO" selectedItem="lQ1-ai-wYn" id="O0p-Tc-KQ1">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" showsStateColumn="NO" autoenablesItems="NO" id="7hX-7Y-rtT">
|
||||
<items>
|
||||
<menuItem title="Seconds" keyEquivalent="s" id="VD1-1h-Hdh">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Minutes" state="on" keyEquivalent="m" id="CsM-KR-zzs">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Hours" keyEquivalent="h" id="Nqd-L9-4V8">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Days" keyEquivalent="d" id="5c2-Mb-3aw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Weeks" keyEquivalent="w" id="mJE-8n-iKF">
|
||||
<menuItem title="-- list --" id="lQ1-ai-wYn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
|
||||
@@ -170,9 +170,9 @@
|
||||
meta.url = [item attributeForKey:OPMLXMLURLKey];
|
||||
id refresh = [item attributeForKey:@"refreshInterval"]; // baRSS specific
|
||||
if (refresh) {
|
||||
[meta setRefreshAndUnitFromInterval:(int32_t)[refresh integerValue]];
|
||||
[meta setRefreshAndSchedule:(int32_t)[refresh integerValue]];
|
||||
} else {
|
||||
[meta setRefresh:30 unit:RefreshUnitMinutes];
|
||||
[meta setRefreshAndSchedule:kDefaultFeedRefreshInterval]; // TODO: set -1, then auto
|
||||
}
|
||||
}
|
||||
[list addObject:newFeed.feed];
|
||||
@@ -232,7 +232,7 @@
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:OPMLHMTLURLKey stringValue:item.feed.link]];
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:OPMLXMLURLKey stringValue:item.feed.meta.url]];
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:OPMLTypeKey stringValue:@"rss"]];
|
||||
NSString *intervalStr = [NSString stringWithFormat:@"%d", item.feed.meta.refreshInterval];
|
||||
NSString *intervalStr = [NSString stringWithFormat:@"%d", item.feed.meta.refresh];
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"refreshInterval" stringValue:intervalStr]]; // baRSS specific
|
||||
// TODO: option to export unread state?
|
||||
}
|
||||
|
||||
@@ -408,8 +408,9 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
||||
|
||||
if (isRefreshColumn) {
|
||||
cellView.textField.objectValue = fg.refreshStr;
|
||||
cellView.textField.textColor = (fg.refreshStr.length > 1 ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]);
|
||||
NSString *str = [fg refreshString];
|
||||
cellView.textField.stringValue = str;
|
||||
cellView.textField.textColor = (str.length > 1 ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]);
|
||||
} else if (isSeperator) {
|
||||
return cellView; // refresh cell already skipped with the above if condition
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user