fetch frequency limit

This commit is contained in:
刘浩远 2020-06-17 16:44:45 +08:00
parent 66d4d8ac45
commit 5cc25dea02
7 changed files with 76 additions and 17 deletions

View File

@ -1,7 +1,7 @@
import * as React from "react"
import intl = require("react-intl-universal")
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react"
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown } from "@fluentui/react"
import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
import { urlTest } from "../../scripts/utils"
import DangerButton from "../utils/danger-button"
@ -11,6 +11,7 @@ type SourcesTabProps = {
addSource: (url: string) => void
updateSourceName: (source: RSSSource, name: string) => void
updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => void
updateFetchFrequency: (source: RSSSource, frequency: number) => void
deleteSource: (source: RSSSource) => void
importOPML: () => void
exportOPML: () => void
@ -74,6 +75,24 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
}
]
fetchFrequencyOptions = (): IDropdownOption[] => [
{ key: "0", text: intl.get("sources.unlimited") },
{ key: "15", text: intl.get("time.m", { m: 15 }) },
{ key: "30", text: intl.get("time.m", { m: 30 }) },
{ key: "60", text: intl.get("time.h", { h: 1 }) },
{ key: "120", text: intl.get("time.h", { h: 2 }) },
{ key: "180", text: intl.get("time.h", { h: 3 }) },
{ key: "360", text: intl.get("time.h", { h: 6 }) },
{ key: "720", text: intl.get("time.h", { h: 12 }) },
{ key: "1440", text: intl.get("time.d", { d: 1 }) }
]
onFetchFrequencyChange = (_, option: IDropdownOption) => {
let frequency = parseInt(option.key as string)
this.props.updateFetchFrequency(this.state.selectedSource, frequency)
this.setState({selectedSource: {...this.state.selectedSource, fetchFrequency: frequency} as RSSSource})
}
sourceOpenTargetChoices = (): IChoiceGroupOption[] => [
{ key: String(SourceOpenTarget.Local), text: intl.get("sources.rssText") },
{ key: String(SourceOpenTarget.Webpage), text: intl.get("sources.loadWebpage") },
@ -157,6 +176,16 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
text={intl.get("sources.editName")} />
</Stack.Item>
</Stack>
<Label>{intl.get("sources.fetchFrequency")}</Label>
<Stack>
<Stack.Item>
<Dropdown
options={this.fetchFrequencyOptions()}
selectedKey={this.state.selectedSource.fetchFrequency ? String(this.state.selectedSource.fetchFrequency) : "0"}
onChange={this.onFetchFrequencyChange}
style={{width: 200}} />
</Stack.Item>
</Stack>
<ChoiceGroup
label={intl.get("sources.openTarget")}
options={this.sourceOpenTargetChoices()}

View File

@ -26,6 +26,9 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => {
dispatch(updateSource({ ...source, openTarget: target } as RSSSource))
},
updateFetchFrequency: (source: RSSSource, frequency: number) => {
dispatch(updateSource({ ...source, fetchFrequency: frequency } as RSSSource))
},
deleteSource: (source: RSSSource) => dispatch(deleteSource(source)),
importOPML: () => {
remote.dialog.showOpenDialog(

View File

@ -9,10 +9,17 @@ if (!locked) {
}
let mainWindow: BrowserWindow
let store = new Store()
let restarting = false
performUpdate(store)
nativeTheme.themeSource = store.get("theme", "system")
let store: Store
let restarting: boolean
function init() {
restarting = false
store = new Store()
performUpdate(store)
nativeTheme.themeSource = store.get("theme", "system")
}
init()
function createWindow() {
let mainWindowState = windowStateKeeper({
@ -61,9 +68,7 @@ app.on("second-instance", () => {
app.on("window-all-closed", function () {
mainWindow = null
if (restarting) {
restarting = false
store = new Store()
nativeTheme.themeSource = store.get("theme", "system")
init()
createWindow()
} else if (process.platform !== "darwin") {
app.quit()

View File

@ -15,6 +15,11 @@
"confirmMarkAll": "Do you really want to mark all articles on this page as read?",
"confirm": "Confirm",
"cancel": "Cancel",
"time": {
"m": "{m, plural, =1 {# minute} other {# minutes}}",
"h": "{h, plural, =1 {# hour} other {# hours}}",
"d": "{d, plural, =1 {# day} other {# days}}"
},
"log": {
"empty": "No notifications",
"fetchFailure": "Failed to load source \"{name}\".",
@ -82,6 +87,8 @@
"opmlFile": "OPML File",
"name": "Source name",
"editName": "Edit name",
"fetchFrequency": "Fetch frequency limit",
"unlimited": "Unlimited",
"openTarget": "Default open target for articles",
"delete": "Delete source",
"add": "Add source",

View File

@ -15,6 +15,11 @@
"confirmMarkAll": "确认将本页所有文章标为已读?",
"confirm": "确认",
"cancel": "取消",
"time": {
"m": "{m}分钟",
"h": "{h}小时",
"d": "{d}天"
},
"log": {
"empty": "无消息",
"fetchFailure": "无法加载订阅源“{name}”",
@ -82,6 +87,8 @@
"opmlFile": "OPML文件",
"name": "订阅源名称",
"editName": "修改名称",
"fetchFrequency": "抓取频率限制",
"unlimited": "无限制",
"openTarget": "订阅源文章打开方式",
"delete": "删除订阅源",
"add": "添加订阅源",

View File

@ -147,8 +147,12 @@ export function fetchItems(): AppThunk<Promise<void>> {
return (dispatch, getState) => {
let promises = new Array<Promise<RSSItem[]>>()
if (!getState().app.fetchingItems) {
for (let source of <RSSSource[]>Object.values(getState().sources)) {
let promise = RSSSource.fetchItems(source, rssParser, db.idb)
let timenow = new Date().getTime()
let sources = <RSSSource[]>Object.values(getState().sources).filter(s =>
(s.lastFetched.getTime() + (s.fetchFrequency || 0) * 60000) <= timenow
)
for (let source of sources) {
let promise = RSSSource.fetchItems(source, rssParser)
promise.finally(() => dispatch(fetchItemsIntermediate()))
promises.push(promise)
}

View File

@ -18,11 +18,14 @@ export class RSSSource {
name: string
openTarget: SourceOpenTarget
unreadCount: number
lastFetched: Date
fetchFrequency?: number // in minutes
constructor(url: string, name: string = null) {
this.url = url
this.name = name
this.openTarget = SourceOpenTarget.Local
this.lastFetched = new Date()
}
async fetchMetaData(parser: Parser) {
@ -46,10 +49,10 @@ export class RSSSource {
}
}
private static checkItem(source: RSSSource, item: Parser.Item, db: Nedb<RSSItem>): Promise<RSSItem> {
private static checkItem(source: RSSSource, item: Parser.Item): Promise<RSSItem> {
return new Promise<RSSItem>((resolve, reject) => {
let i = new RSSItem(item, source)
db.findOne({
db.idb.findOne({
source: i.source,
title: i.title,
date: i.date
@ -66,11 +69,11 @@ export class RSSSource {
})
}
static checkItems(source: RSSSource, items: Parser.Item[], db: Nedb<RSSItem>): Promise<RSSItem[]> {
static checkItems(source: RSSSource, items: Parser.Item[]): Promise<RSSItem[]> {
return new Promise<RSSItem[]>((resolve, reject) => {
let p = new Array<Promise<RSSItem>>()
for (let item of items) {
p.push(this.checkItem(source, item, db))
p.push(this.checkItem(source, item))
}
Promise.all(p).then(values => {
resolve(values.filter(v => v != null))
@ -78,9 +81,10 @@ export class RSSSource {
})
}
static async fetchItems(source: RSSSource, parser: Parser, db: Nedb<RSSItem>) {
static async fetchItems(source: RSSSource, parser: Parser) {
let feed = await parser.parseURL(source.url)
return await this.checkItems(source, feed.items, db)
db.sdb.update({ sid: source.sid }, { $set: { lastFetched: new Date() } })
return await this.checkItems(source, feed.items)
}
}
@ -244,7 +248,7 @@ export function addSource(url: string, name: string = null, batch = false): AppT
.then(inserted => {
inserted.unreadCount = feed.items.length
dispatch(addSourceSuccess(inserted, batch))
return RSSSource.checkItems(inserted, feed.items, db.idb)
return RSSSource.checkItems(inserted, feed.items)
.then(items => insertItems(items))
.then(() => {
SourceGroup.save(getState().groups)