diff --git a/src/actions/service.js b/src/actions/service.js index 68c62d594..762a24ec3 100644 --- a/src/actions/service.js +++ b/src/actions/service.js @@ -91,4 +91,10 @@ export default { serviceId: PropTypes.string.isRequired, }, openDevToolsForActiveService: {}, + hibernate: { + serviceId: PropTypes.string.isRequired, + }, + awake: { + serviceId: PropTypes.string.isRequired, + }, }; diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index 3b09518c5..600a2f1fc 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js @@ -143,11 +143,13 @@ export default @observer class ServiceView extends Component { {service.recipe.id === CUSTOM_WEBSITE_ID && ( )} - + {!service.isHibernating && ( + + )} )} diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 5cde0db8e..1f51430fe 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -77,6 +77,10 @@ const messages = defineMessages({ id: 'settings.service.form.isMutedInfo', defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', }, + isHibernationEnabledInfo: { + id: 'settings.service.form.isHibernatedEnabledInfo', + defaultMessage: '!!!When enabled, a service will be shut down after a period of time to save system resources.', + }, headlineNotifications: { id: 'settings.service.form.headlineNotifications', defaultMessage: '!!!Notifications', @@ -333,6 +337,10 @@ export default @observer class EditServiceForm extends Component { )} + +

+ {intl.formatMessage(messages.isHibernationEnabledInfo)} +

diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index e4ff03bb3..889ecca6b 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js @@ -33,6 +33,10 @@ const messages = defineMessages({ id: 'settings.service.form.enableService', defaultMessage: '!!!Enable service', }, + enableHibernation: { + id: 'settings.service.form.enableHibernation', + defaultMessage: '!!!Enable hibernation', + }, enableNotification: { id: 'settings.service.form.enableNotification', defaultMessage: '!!!Enable Notifications', @@ -114,8 +118,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex const { stores, + router, } = this.props; + const { action } = router.params; + let defaultSpellcheckerLanguage = SPELLCHECKER_LOCALES[stores.settings.app.spellcheckerLanguage]; if (stores.settings.app.spellcheckerLanguage === 'automatic') { @@ -140,6 +147,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex value: service.isEnabled, default: true, }, + isHibernationEnabled: { + label: intl.formatMessage(messages.enableHibernation), + value: action !== 'edit' ? recipe.autoHibernate : service.isHibernationEnabled, + default: true, + }, isNotificationEnabled: { label: intl.formatMessage(messages.enableNotification), value: service.isNotificationEnabled, diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 4cf444b2c..5584b2732 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -298,6 +298,7 @@ "settings.service.form.enableAudio": "Enable audio", "settings.service.form.enableBadge": "Show unread message badges", "settings.service.form.enableDarkMode": "Enable Dark Mode", + "settings.service.form.enableHibernation": "Enable hibernation", "settings.service.form.enableNotification": "Enable notifications", "settings.service.form.enableService": "Enable service", "settings.service.form.headlineBadges": "Unread message badges", @@ -308,6 +309,7 @@ "settings.service.form.iconUpload": "Drop your image, or click here", "settings.service.form.indirectMessageInfo": "You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", "settings.service.form.indirectMessages": "Show message badge for all new messages", + "settings.service.form.isHibernatedEnabledInfo": "When enabled, a service will be shut down after a period of time to save system resources.", "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", "settings.service.form.name": "Name", "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings", diff --git a/src/i18n/messages/src/components/settings/services/EditServiceForm.json b/src/i18n/messages/src/components/settings/services/EditServiceForm.json index e66db807d..e82d27b64 100644 --- a/src/i18n/messages/src/components/settings/services/EditServiceForm.json +++ b/src/i18n/messages/src/components/settings/services/EditServiceForm.json @@ -181,16 +181,29 @@ "column": 3 } }, + { + "id": "settings.service.form.isHibernatedEnabledInfo", + "defaultMessage": "!!!When enabled, a service will be shut down after a period of time to save system resources.", + "file": "src/components/settings/services/EditServiceForm.js", + "start": { + "line": 80, + "column": 28 + }, + "end": { + "line": 83, + "column": 3 + } + }, { "id": "settings.service.form.headlineNotifications", "defaultMessage": "!!!Notifications", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 80, + "line": 84, "column": 25 }, "end": { - "line": 83, + "line": 87, "column": 3 } }, @@ -199,11 +212,11 @@ "defaultMessage": "!!!Unread message badges", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 84, + "line": 88, "column": 18 }, "end": { - "line": 87, + "line": 91, "column": 3 } }, @@ -212,11 +225,11 @@ "defaultMessage": "!!!General", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 88, + "line": 92, "column": 19 }, "end": { - "line": 91, + "line": 95, "column": 3 } }, @@ -225,11 +238,11 @@ "defaultMessage": "!!!Delete", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 92, + "line": 96, "column": 14 }, "end": { - "line": 95, + "line": 99, "column": 3 } }, @@ -238,11 +251,11 @@ "defaultMessage": "!!!Drop your image, or click here", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 96, + "line": 100, "column": 14 }, "end": { - "line": 99, + "line": 103, "column": 3 } }, @@ -251,11 +264,11 @@ "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 100, + "line": 104, "column": 17 }, "end": { - "line": 103, + "line": 107, "column": 3 } }, @@ -264,11 +277,11 @@ "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 104, + "line": 108, "column": 20 }, "end": { - "line": 107, + "line": 111, "column": 3 } }, @@ -277,11 +290,11 @@ "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", "file": "src/components/settings/services/EditServiceForm.js", "start": { - "line": 108, + "line": 112, "column": 13 }, "end": { - "line": 111, + "line": 115, "column": 3 } } diff --git a/src/i18n/messages/src/containers/settings/EditServiceScreen.json b/src/i18n/messages/src/containers/settings/EditServiceScreen.json index 42ca42125..2d2a8a8b8 100644 --- a/src/i18n/messages/src/containers/settings/EditServiceScreen.json +++ b/src/i18n/messages/src/containers/settings/EditServiceScreen.json @@ -25,16 +25,29 @@ "column": 3 } }, + { + "id": "settings.service.form.enableHibernation", + "defaultMessage": "!!!Enable hibernation", + "file": "src/containers/settings/EditServiceScreen.js", + "start": { + "line": 36, + "column": 21 + }, + "end": { + "line": 39, + "column": 3 + } + }, { "id": "settings.service.form.enableNotification", "defaultMessage": "!!!Enable Notifications", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 36, + "line": 40, "column": 22 }, "end": { - "line": 39, + "line": 43, "column": 3 } }, @@ -43,11 +56,11 @@ "defaultMessage": "!!!Show unread message badges", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 40, + "line": 44, "column": 15 }, "end": { - "line": 43, + "line": 47, "column": 3 } }, @@ -56,11 +69,11 @@ "defaultMessage": "!!!Enable audio", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 44, + "line": 48, "column": 15 }, "end": { - "line": 47, + "line": 51, "column": 3 } }, @@ -69,11 +82,11 @@ "defaultMessage": "!!!Team", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 48, + "line": 52, "column": 8 }, "end": { - "line": 51, + "line": 55, "column": 3 } }, @@ -82,11 +95,11 @@ "defaultMessage": "!!!Custom server", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 52, + "line": 56, "column": 13 }, "end": { - "line": 55, + "line": 59, "column": 3 } }, @@ -95,11 +108,11 @@ "defaultMessage": "!!!Show message badge for all new messages", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 56, + "line": 60, "column": 20 }, "end": { - "line": 59, + "line": 63, "column": 3 } }, @@ -108,11 +121,11 @@ "defaultMessage": "!!!Custom icon", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 60, + "line": 64, "column": 8 }, "end": { - "line": 63, + "line": 67, "column": 3 } }, @@ -121,11 +134,11 @@ "defaultMessage": "!!!Enable Dark Mode", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 64, + "line": 68, "column": 18 }, "end": { - "line": 67, + "line": 71, "column": 3 } }, @@ -134,11 +147,11 @@ "defaultMessage": "!!!Use Proxy", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 68, + "line": 72, "column": 15 }, "end": { - "line": 71, + "line": 75, "column": 3 } }, @@ -147,11 +160,11 @@ "defaultMessage": "!!!Proxy Host/IP", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 72, + "line": 76, "column": 13 }, "end": { - "line": 75, + "line": 79, "column": 3 } }, @@ -160,11 +173,11 @@ "defaultMessage": "!!!Port", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 76, + "line": 80, "column": 13 }, "end": { - "line": 79, + "line": 83, "column": 3 } }, @@ -173,11 +186,11 @@ "defaultMessage": "!!!User", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 80, + "line": 84, "column": 13 }, "end": { - "line": 83, + "line": 87, "column": 3 } }, @@ -186,11 +199,11 @@ "defaultMessage": "!!!Password", "file": "src/containers/settings/EditServiceScreen.js", "start": { - "line": 84, + "line": 88, "column": 17 }, "end": { - "line": 87, + "line": 91, "column": 3 } } diff --git a/src/models/Recipe.js b/src/models/Recipe.js index 00c0f699f..bfec1de6d 100644 --- a/src/models/Recipe.js +++ b/src/models/Recipe.js @@ -38,6 +38,8 @@ export default class Recipe { disablewebsecurity = false; + autoHibernate = false; + constructor(data) { if (!data) { throw Error('Recipe config not valid'); @@ -78,6 +80,8 @@ export default class Recipe { this.disablewebsecurity = data.config.disablewebsecurity || this.disablewebsecurity; + this.autoHibernate = data.config.autoHibernate || this.autoHibernate; + this.message = data.config.message || this.message; } diff --git a/src/models/Service.js b/src/models/Service.js index e45c39564..3829c29cd 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -1,6 +1,7 @@ import { computed, observable, autorun } from 'mobx'; import path from 'path'; import normalizeUrl from 'normalize-url'; +import moment from 'moment'; const debug = require('debug')('Franz:Service'); @@ -70,6 +71,12 @@ export default class Service { @observable restrictionType = null; + @observable isHibernationEnabled = false; + + @observable isHibernating = false; + + @observable lastUsed = Date.now(); // timestamp + constructor(data, recipe) { if (!data) { console.error('Service config not valid'); @@ -113,6 +120,8 @@ export default class Service { this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage; + this.isHibernationEnabled = data.isHibernationEnabled !== undefined ? data.isHibernationEnabled : this.isHibernationEnabled; + this.recipe = recipe; autorun(() => { diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 65e68e4d7..38aaa881d 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -7,6 +7,7 @@ import { import { debounce, remove } from 'lodash'; import ms from 'ms'; import { remote } from 'electron'; +import moment from 'moment'; import Store from './lib/Store'; import Request from './lib/Request'; @@ -72,6 +73,8 @@ export default class ServicesStore extends Store { this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); + this.actions.service.hibernate.listen(this._hibernate.bind(this)); + this.actions.service.awake.listen(this._awake.bind(this)); this.registerReactions([ this._focusServiceReaction.bind(this), @@ -100,6 +103,42 @@ export default class ServicesStore extends Store { ); } + initialize() { + super.initialize(); + + // Check services to become hibernated + this._hibernationTick(); + } + + teardown() { + super.teardown(); + + // Stop checking services for hibernation + this._hibernationTick.cancel(); + } + + /** + * Сheck for services to become hibernated. + */ + _hibernationTick = debounce(() => { + this._hibernateServices(); + this._hibernationTick(); + debug('Hibernation tick'); + }, ms('1m')); // every 1 min + + /** + * Defines which services should be hibernated. + */ + _hibernateServices() { + this.all.forEach((service) => { + if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) { + // If service is stale for 5 min, hibernate it. + this._hibernate({ serviceId: service.id }); + } + }); + } + + // Computed props @computed get all() { if (this.stores.user.isLoggedIn) { const services = this.allServicesRequest.execute().result; @@ -300,6 +339,8 @@ export default class ServicesStore extends Store { this.all[index].isActive = false; }); service.isActive = true; + this._awake({ serviceId: service.id }); + service.lastUsed = Date.now(); statsEvent('activate-service', service.recipe.id); @@ -598,6 +639,24 @@ export default class ServicesStore extends Store { } } + @action _hibernate({ serviceId }) { + const service = this.one(serviceId); + if (service.isActive || !service.isHibernationEnabled) { + debug('Skipping service hibernation'); + return; + } + + debug(`Hibernate ${service.name}`); + + service.isHibernating = true; + } + + @action _awake({ serviceId }) { + const service = this.one(serviceId); + service.isHibernating = false; + service.liveFrom = Date.now(); + } + // Reactions _focusServiceReaction() { const service = this.active;