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 * as React from "react"
import intl = require("react-intl-universal") import intl = require("react-intl-universal")
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList, 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 { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
import { urlTest } from "../../scripts/utils" import { urlTest } from "../../scripts/utils"
import DangerButton from "../utils/danger-button" import DangerButton from "../utils/danger-button"
@ -11,6 +11,7 @@ type SourcesTabProps = {
addSource: (url: string) => void addSource: (url: string) => void
updateSourceName: (source: RSSSource, name: string) => void updateSourceName: (source: RSSSource, name: string) => void
updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => void updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => void
updateFetchFrequency: (source: RSSSource, frequency: number) => void
deleteSource: (source: RSSSource) => void deleteSource: (source: RSSSource) => void
importOPML: () => void importOPML: () => void
exportOPML: () => 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[] => [ sourceOpenTargetChoices = (): IChoiceGroupOption[] => [
{ key: String(SourceOpenTarget.Local), text: intl.get("sources.rssText") }, { key: String(SourceOpenTarget.Local), text: intl.get("sources.rssText") },
{ key: String(SourceOpenTarget.Webpage), text: intl.get("sources.loadWebpage") }, { 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")} /> text={intl.get("sources.editName")} />
</Stack.Item> </Stack.Item>
</Stack> </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 <ChoiceGroup
label={intl.get("sources.openTarget")} label={intl.get("sources.openTarget")}
options={this.sourceOpenTargetChoices()} options={this.sourceOpenTargetChoices()}

View File

@ -26,6 +26,9 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => { updateSourceOpenTarget: (source: RSSSource, target: SourceOpenTarget) => {
dispatch(updateSource({ ...source, openTarget: target } as RSSSource)) 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)), deleteSource: (source: RSSSource) => dispatch(deleteSource(source)),
importOPML: () => { importOPML: () => {
remote.dialog.showOpenDialog( remote.dialog.showOpenDialog(

View File

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

View File

@ -15,6 +15,11 @@
"confirmMarkAll": "Do you really want to mark all articles on this page as read?", "confirmMarkAll": "Do you really want to mark all articles on this page as read?",
"confirm": "Confirm", "confirm": "Confirm",
"cancel": "Cancel", "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": { "log": {
"empty": "No notifications", "empty": "No notifications",
"fetchFailure": "Failed to load source \"{name}\".", "fetchFailure": "Failed to load source \"{name}\".",
@ -82,6 +87,8 @@
"opmlFile": "OPML File", "opmlFile": "OPML File",
"name": "Source name", "name": "Source name",
"editName": "Edit name", "editName": "Edit name",
"fetchFrequency": "Fetch frequency limit",
"unlimited": "Unlimited",
"openTarget": "Default open target for articles", "openTarget": "Default open target for articles",
"delete": "Delete source", "delete": "Delete source",
"add": "Add source", "add": "Add source",

View File

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

View File

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

View File

@ -18,11 +18,14 @@ export class RSSSource {
name: string name: string
openTarget: SourceOpenTarget openTarget: SourceOpenTarget
unreadCount: number unreadCount: number
lastFetched: Date
fetchFrequency?: number // in minutes
constructor(url: string, name: string = null) { constructor(url: string, name: string = null) {
this.url = url this.url = url
this.name = name this.name = name
this.openTarget = SourceOpenTarget.Local this.openTarget = SourceOpenTarget.Local
this.lastFetched = new Date()
} }
async fetchMetaData(parser: Parser) { 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) => { return new Promise<RSSItem>((resolve, reject) => {
let i = new RSSItem(item, source) let i = new RSSItem(item, source)
db.findOne({ db.idb.findOne({
source: i.source, source: i.source,
title: i.title, title: i.title,
date: i.date 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) => { return new Promise<RSSItem[]>((resolve, reject) => {
let p = new Array<Promise<RSSItem>>() let p = new Array<Promise<RSSItem>>()
for (let item of items) { for (let item of items) {
p.push(this.checkItem(source, item, db)) p.push(this.checkItem(source, item))
} }
Promise.all(p).then(values => { Promise.all(p).then(values => {
resolve(values.filter(v => v != null)) 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) 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 => { .then(inserted => {
inserted.unreadCount = feed.items.length inserted.unreadCount = feed.items.length
dispatch(addSourceSuccess(inserted, batch)) dispatch(addSourceSuccess(inserted, batch))
return RSSSource.checkItems(inserted, feed.items, db.idb) return RSSSource.checkItems(inserted, feed.items)
.then(items => insertItems(items)) .then(items => insertItems(items))
.then(() => { .then(() => {
SourceGroup.save(getState().groups) SourceGroup.save(getState().groups)