diff --git a/src/bridges/settings.ts b/src/bridges/settings.ts index c20c4597..db63fd5f 100644 --- a/src/bridges/settings.ts +++ b/src/bridges/settings.ts @@ -89,6 +89,13 @@ const settingsBridge = { ipcRenderer.invoke("set-fetch-interval", interval) }, + getRefreshOnStart: (): boolean => { + return ipcRenderer.sendSync("get-refresh-on-start") + }, + setRefreshOnStart: (flag: boolean) => { + ipcRenderer.invoke("set-refresh-on-start", flag) + }, + getSearchEngine: (): SearchEngines => { return ipcRenderer.sendSync("get-search-engine") }, diff --git a/src/components/nav.tsx b/src/components/nav.tsx index dcb4c8f7..ee5dad89 100644 --- a/src/components/nav.tsx +++ b/src/components/nav.tsx @@ -13,6 +13,7 @@ type NavProps = { search: () => void markAllRead: () => void fetch: () => void + stopFetch: () => void logs: () => void views: () => void settings: () => void @@ -155,10 +156,18 @@ class Nav extends React.Component { {this.props.state.title}
- + className="btn" + onClick={ + this.canFetch() ? this.fetch : this.props.stopFetch + } + title={ + this.canFetch() + ? intl.get("nav.refresh") + : intl.get("nav.stop") + }> + void + stopFetch: () => void } class Settings extends React.Component { @@ -68,6 +69,12 @@ class Settings extends React.Component { label={intl.get("settings.fetching")} tabIndex={0} /> +
+ +
)} diff --git a/src/components/settings/app.tsx b/src/components/settings/app.tsx index 5a018e3d..3316e97f 100644 --- a/src/components/settings/app.tsx +++ b/src/components/settings/app.tsx @@ -40,6 +40,7 @@ type AppTabState = { itemSize: string cacheSize: string deleteIndex: string + refreshOnStart: boolean } class AppTab extends React.Component { @@ -52,6 +53,7 @@ class AppTab extends React.Component { itemSize: null, cacheSize: null, deleteIndex: null, + refreshOnStart: window.settings.getRefreshOnStart(), } this.getItemSize() this.getCacheSize() @@ -156,6 +158,11 @@ class AppTab extends React.Component { }) } + toggleRefreshOnStart = (_: any, checked: boolean) => { + window.settings.setRefreshOnStart(checked) + this.setState({ refreshOnStart: checked }) + } + handleInputChange = event => { const name: string = event.target.name // @ts-ignore @@ -196,6 +203,18 @@ class AppTab extends React.Component { selectedKey={this.state.themeSettings} /> + + + + + + + + + diff --git a/src/containers/nav-container.tsx b/src/containers/nav-container.tsx index 662bea50..fdbebd98 100644 --- a/src/containers/nav-container.tsx +++ b/src/containers/nav-container.tsx @@ -2,7 +2,7 @@ import intl from "react-intl-universal" import { connect } from "react-redux" import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" -import { fetchItems, markAllRead } from "../scripts/models/item" +import { fetchItems, markAllRead, stopFetchItems } from "../scripts/models/item" import { toggleMenu, toggleLogMenu, @@ -25,9 +25,9 @@ const mapStateToProps = createSelector( itemShown: itemShown, }) ) - const mapDispatchToProps = dispatch => ({ fetch: () => dispatch(fetchItems()), + stopFetch: () => dispatch(stopFetchItems()), menu: () => dispatch(toggleMenu()), logs: () => dispatch(toggleLogMenu()), views: () => dispatch(openViewMenu()), diff --git a/src/containers/settings-container.tsx b/src/containers/settings-container.tsx index 248c71b0..02c173ba 100644 --- a/src/containers/settings-container.tsx +++ b/src/containers/settings-container.tsx @@ -2,6 +2,7 @@ import { connect } from "react-redux" import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" import { exitSettings } from "../scripts/models/app" +import { stopFetchItems } from "../scripts/models/item" import Settings from "../components/settings" const getApp = (state: RootState) => state.app @@ -19,6 +20,7 @@ const mapStateToProps = createSelector([getApp], app => ({ const mapDispatchToProps = dispatch => { return { close: () => dispatch(exitSettings()), + stopFetch: () => dispatch(stopFetchItems()), } } diff --git a/src/main/settings.ts b/src/main/settings.ts index e6e6a029..6ffedc8d 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -146,6 +146,14 @@ ipcMain.handle("set-fetch-interval", (_, interval: number) => { store.set(FETCH_INTEVAL_STORE_KEY, interval) }) +const REFRESH_ON_START_STORE_KEY = "refreshOnStart" +ipcMain.on("get-refresh-on-start", event => { + event.returnValue = store.get(REFRESH_ON_START_STORE_KEY, true) +}) +ipcMain.handle("set-refresh-on-start", (_, flag: boolean) => { + store.set(REFRESH_ON_START_STORE_KEY, flag) +}) + const SEARCH_ENGINE_STORE_KEY = "searchEngine" ipcMain.on("get-search-engine", event => { event.returnValue = store.get(SEARCH_ENGINE_STORE_KEY, SearchEngines.Google) diff --git a/src/schema-types.ts b/src/schema-types.ts index d295f9a0..ea86c8c2 100644 --- a/src/schema-types.ts +++ b/src/schema-types.ts @@ -98,4 +98,5 @@ export type SchemaTypes = { filterType: number listViewConfigs: ViewConfigs useNeDB: boolean + refreshOnStart: boolean } diff --git a/src/scripts/i18n/en-US.json b/src/scripts/i18n/en-US.json index fad7eacd..1e4f9cff 100644 --- a/src/scripts/i18n/en-US.json +++ b/src/scripts/i18n/en-US.json @@ -39,6 +39,7 @@ "nav": { "menu": "Menu", "refresh": "Refresh", + "stop": "Stop refresh", "markAllRead": "Mark all as read", "notifications": "Notifications", "view": "View", @@ -237,6 +238,7 @@ "setPac": "Set PAC", "pacHint": "For Socks proxies, it is recommended for PAC to return \"SOCKS5\" for proxy-side DNS. Turning off proxy requires restart.", "fetchInterval": "Automatic fetch interval", + "refreshOnStart": "Refresh feeds on app start", "never": "Never" } -} +} \ No newline at end of file diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index ab17becf..7ec9c415 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -404,7 +404,11 @@ export function initApp(): AppThunk { .then(() => dispatch(initFeeds())) .then(async () => { dispatch(selectAllArticles()) - await dispatch(fetchItems()) + if (window.settings.getRefreshOnStart()) { + await dispatch(fetchItems()) + } else { + dispatch(setupAutoFetch()) + } }) .then(() => { dispatch(updateFavicon()) @@ -555,17 +559,17 @@ export function appReducer( action.items.length == 0 ? state.logMenu : { - ...state.logMenu, - logs: [ - ...state.logMenu.logs, - new AppLog( - AppLogType.Info, - intl.get("log.fetchSuccess", { - count: action.items.length, - }) - ), - ], - }, + ...state.logMenu, + logs: [ + ...state.logMenu.logs, + new AppLog( + AppLogType.Info, + intl.get("log.fetchSuccess", { + count: action.items.length, + }) + ), + ], + }, } case ActionStatus.Intermediate: return { diff --git a/src/scripts/models/item.ts b/src/scripts/models/item.ts index aaa95ff5..7dcd0b96 100644 --- a/src/scripts/models/item.ts +++ b/src/scripts/models/item.ts @@ -205,6 +205,14 @@ export async function insertItems(items: RSSItem[]): Promise { .exec()) as RSSItem[] } +let cancelFetch: () => void = null + +export function stopFetchItems(): AppThunk { + return () => { + if (cancelFetch) cancelFetch() + } +} + export function fetchItems( background = false, sids: number[] = null @@ -225,32 +233,50 @@ export function fetchItems( let sources = sids === null ? Object.values(sourcesState).filter(s => { - let last = s.lastFetched ? s.lastFetched.getTime() : 0 - return ( - !s.serviceRef && - (last > timenow || - last + (s.fetchFrequency || 0) * 60000 <= - timenow) - ) - }) + let last = s.lastFetched ? s.lastFetched.getTime() : 0 + return ( + !s.serviceRef && + (last > timenow || + last + (s.fetchFrequency || 0) * 60000 <= + timenow) + ) + }) : sids - .map(sid => sourcesState[sid]) - .filter(s => !s.serviceRef) - for (let source of sources) { + .map(sid => sourcesState[sid]) + .filter(s => !s.serviceRef) + + let results: { status: 'fulfilled' | 'rejected', value?: RSSItem[], reason?: any }[] = new Array(sources.length) + + for (let i = 0; i < sources.length; i++) { + let source = sources[i] let promise = RSSSource.fetchItems(source) - promise.then(() => + promise.then((items) => { + results[i] = { status: 'fulfilled', value: items } dispatch( updateSource({ ...source, lastFetched: new Date() }) ) - ) - promise.finally(() => dispatch(fetchItemsIntermediate())) + }).catch(err => { + results[i] = { status: 'rejected', reason: err } + }).finally(() => dispatch(fetchItemsIntermediate())) promises.push(promise) } dispatch(fetchItemsRequest(promises.length)) - const results = await Promise.allSettled(promises) + + const cancelPromise = new Promise(resolve => { + cancelFetch = resolve + }) + + await Promise.race([ + Promise.allSettled(promises), + cancelPromise + ]) + + cancelFetch = null + return await new Promise((resolve, reject) => { let items = new Array() results.map((r, i) => { + if (!r) return // Cancelled/Pending if (r.status === "fulfilled") items.push(...r.value) else { console.log(r.reason)