fetch frequency limit
This commit is contained in:
parent
66d4d8ac45
commit
5cc25dea02
|
@ -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()}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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": "添加订阅源",
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue