From 89ea73f4e7ad747b09c5531d4fabb62786c2b688 Mon Sep 17 00:00:00 2001 From: Luke Arms Date: Mon, 24 Jan 2022 18:49:49 +1100 Subject: [PATCH 1/4] Allow the next break or microbreak to be scheduled from the CLI `Command`: - Add new `--wait` option to commands `mini` and `long` - Add `waitToMs` method to utilise the existing interval parser, `parseDuration` `BreaksPlanner`: - Split `skipToMicrobreak` to add `scheduleMicrobreak` - Same for `skipToBreak`, `scheduleBreak` - Fix typos and inconsistencies in logged output --- app/breaksPlanner.js | 18 ++++++++++---- app/main.js | 57 ++++++++++++++++++++++++++++++------------- app/utils/commands.js | 22 ++++++++++++++--- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/app/breaksPlanner.js b/app/breaksPlanner.js index 7e98af0c2..c041e3a6d 100644 --- a/app/breaksPlanner.js +++ b/app/breaksPlanner.js @@ -168,22 +168,26 @@ class BreaksPlanner extends EventEmitter { this.emit('updateToolTip') } - skipToMicrobreak () { + scheduleMicrobreak (delay) { this.scheduler.cancel() const shouldBreak = this.settings.get('break') const shouldMicrobreak = this.settings.get('microbreak') - const breakInterval = this.settings.get('breakInterval') + 1 if (shouldBreak && shouldMicrobreak) { + const breakInterval = this.settings.get('breakInterval') + 1 if (this.breakNumber % breakInterval === 0) { this.breakNumber = 1 } } - this.scheduler = new Scheduler(() => this.emit('startMicrobreak'), 100, 'startMicrobreak') + this.scheduler = new Scheduler(() => this.emit('startMicrobreak'), delay, 'startMicrobreak') this.scheduler.plan() this.emit('updateToolTip') } - skipToBreak () { + skipToMicrobreak () { + this.scheduleMicrobreak(100) + } + + scheduleBreak (delay) { this.scheduler.cancel() const shouldBreak = this.settings.get('break') const shouldMicrobreak = this.settings.get('microbreak') @@ -191,11 +195,15 @@ class BreaksPlanner extends EventEmitter { const breakInterval = this.settings.get('breakInterval') + 1 this.breakNumber = breakInterval } - this.scheduler = new Scheduler(() => this.emit('startBreak'), 100, 'startBreak') + this.scheduler = new Scheduler(() => this.emit('startBreak'), delay, 'startBreak') this.scheduler.plan() this.emit('updateToolTip') } + skipToBreak () { + this.scheduleBreak(100) + } + clear () { this.scheduler.cancel() this.breakNumber = 0 diff --git a/app/main.js b/app/main.js index 01bc4c943..09208441e 100644 --- a/app/main.js +++ b/app/main.js @@ -85,26 +85,39 @@ if (!gotTheLock) { } if (!cmd.checkInMain()) { - log.info(`Stretchly: command '${cmd.command}' executed in second-instance, dropped in main instance`) + log.info(`Stretchly: command '${cmd.command}' executed in second instance, dropped in main instance`) return } + switch (cmd.command) { case 'reset': - log.info('Stretchly: reseting breaks (requested by second instance)') + log.info('Stretchly: resetting breaks (requested by second instance)') resetBreaks() break - case 'mini': + case 'mini': { log.info('Stretchly: skip to Mini Break (requested by second instance)') + const ms = cmd.waitToMs() + if (ms === -1) { + log.error('Stretchly: error parsing wait interval to ms because of invalid value') + return + } if (cmd.options.title) nextIdea = [cmd.options.title] - if (!cmd.options.noskip) skipToMicrobreak() + if (!cmd.options.noskip || ms) skipToMicrobreak(ms) break + } - case 'long': + case 'long': { log.info('Stretchly: skip to Long Break (requested by second instance)') + const ms = cmd.waitToMs() + if (ms === -1) { + log.error('Stretchly: error parsing wait interval to ms because of invalid value') + return + } nextIdea = [cmd.options.title ? cmd.options.title : null, cmd.options.text ? cmd.options.text : null] - if (!cmd.options.noskip) skipToBreak() + if (!cmd.options.noskip || ms) skipToBreak(ms) break + } case 'resume': log.info('Stretchly: resume Breaks (requested by second instance)') @@ -122,7 +135,7 @@ if (!gotTheLock) { const ms = cmd.durationToMs(settings) // -1 indicates an invalid value if (ms === -1) { - log.error('Stretchly: error when parsing duration to ms because of unvalid value') + log.error('Stretchly: error when parsing duration to ms because of invalid value') return } pauseBreaks(ms) @@ -223,7 +236,7 @@ function initialize (isAppStart = true) { if (!resumeBreaksShortcut) { log.warn('Stretchly: resumeBreaksShortcut registration failed') } else { - log.info(`Stretchly: resumeBreaksShortcut registration succesful (${settings.get('resumeBreaksShortcut')})`) + log.info(`Stretchly: resumeBreaksShortcut registration successful (${settings.get('resumeBreaksShortcut')})`) } } if (settings.get('pauseBreaksShortcut') !== '') { @@ -234,7 +247,7 @@ function initialize (isAppStart = true) { if (!pauseBreaksShortcut) { log.warn('Stretchly: pauseBreaksShortcut registration failed') } else { - log.info(`Stretchly: pauseBreaksShortcut registration succesful (${settings.get('pauseBreaksShortcut')})`) + log.info(`Stretchly: pauseBreaksShortcut registration successful (${settings.get('pauseBreaksShortcut')})`) } } } @@ -942,27 +955,37 @@ function postponeBreak (shouldPlaySound = false) { updateTray() } -function skipToMicrobreak () { +function skipToMicrobreak (delay = 0) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } if (breakWins) { breakWins = breakComplete(false, breakWins) } - breakPlanner.skipToMicrobreak() - log.info('Stretchly: skipping to Mini Break') + if (delay) { + breakPlanner.scheduleMicrobreak(delay) + log.info(`Stretchly: skipping to Mini Break in ${delay}ms`) + } else { + breakPlanner.skipToMicrobreak() + log.info('Stretchly: skipping to Mini Break') + } updateTray() } -function skipToBreak () { +function skipToBreak (delay = 0) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } if (breakWins) { breakWins = breakComplete(false, breakWins) } - breakPlanner.skipToBreak() - log.info('Stretchly: skipping to Long Break') + if (delay) { + breakPlanner.scheduleBreak(delay) + log.info(`Stretchly: skipping to Long Break in ${delay}ms`) + } else { + breakPlanner.skipToBreak() + log.info('Stretchly: skipping to Long Break') + } updateTray() } @@ -974,7 +997,7 @@ function resetBreaks () { breakWins = breakComplete(false, breakWins) } breakPlanner.reset() - log.info('Stretchly: reseting breaks') + log.info('Stretchly: resetting breaks') updateTray() } @@ -1004,7 +1027,7 @@ function pauseBreaks (milliseconds) { finishBreak(false) } breakPlanner.pause(milliseconds) - log.info(`Stretchly: pausing breaks for ${milliseconds}`) + log.info(`Stretchly: pausing breaks for ${milliseconds}ms`) updateTray() } diff --git a/app/utils/commands.js b/app/utils/commands.js index 386def3e4..1ae7bb51c 100644 --- a/app/utils/commands.js +++ b/app/utils/commands.js @@ -20,6 +20,12 @@ const allOptions = { description: 'Do not skip directly to this break (Long or Mini)', withValue: false }, + wait: { + long: '--wait', + short: '-w', + description: 'Specify an interval to wait before skipping to this break (Long or Mini) [HHhMMm|HHh|MMm|MM]', + withValue: true + }, duration: { long: '--duration', short: '-d', @@ -50,11 +56,11 @@ const allCommands = { }, mini: { description: 'Skip to the Mini Break, customize it', - options: [allOptions.title, allOptions.noskip] + options: [allOptions.title, allOptions.noskip, allOptions.wait] }, long: { description: 'Skip to the Long Break, customize it', - options: [allOptions.text, allOptions.title, allOptions.noskip] + options: [allOptions.text, allOptions.title, allOptions.noskip, allOptions.wait] } } @@ -145,7 +151,7 @@ class Command { }) if (!valid) { - log.error(`Stretchly ${this.isFirstInstance ? '' : '2'}: options '${name}' is not valid for command '${this.command}'`) + log.error(`Stretchly${this.isFirstInstance ? '' : ' 2'}: option '${name}' is not valid for command '${this.command}'`) } } @@ -164,7 +170,7 @@ class Command { default: if (this.hasSupportedCommand) { - log.info(`Stretchly ${this.isFirstInstance ? '' : '2'}: forwarding command '${this.command}' to the main instance`) + log.info(`Stretchly${this.isFirstInstance ? '' : ' 2'}: forwarding command '${this.command}' to the main instance`) } } } @@ -186,6 +192,14 @@ class Command { } } + waitToMs () { + if (!this.options.wait) { + return 0 + } + + return parseDuration(this.options.wait) + } + checkInMain () { if (!this.command) { return false From a00636daea87122c0b85d9d54e390441b6cddda6 Mon Sep 17 00:00:00 2001 From: Luke Arms Date: Wed, 2 Feb 2022 13:26:46 +1100 Subject: [PATCH 2/4] Review break scheduling code - breaksPlanner.js: remove separate `scheduleMicrobreak` and `scheduleBreak` functions - main.js: - Give `ms` variables more descriptive names - Remove default `delay` from `skipToMicrobreak` and `skipToBreak` - commands.js: add an example --- app/breaksPlanner.js | 12 ++---------- app/main.js | 26 +++++++++++++------------- app/utils/commands.js | 4 ++++ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/app/breaksPlanner.js b/app/breaksPlanner.js index c041e3a6d..57b2ba90c 100644 --- a/app/breaksPlanner.js +++ b/app/breaksPlanner.js @@ -168,7 +168,7 @@ class BreaksPlanner extends EventEmitter { this.emit('updateToolTip') } - scheduleMicrobreak (delay) { + skipToMicrobreak (delay = 100) { this.scheduler.cancel() const shouldBreak = this.settings.get('break') const shouldMicrobreak = this.settings.get('microbreak') @@ -183,11 +183,7 @@ class BreaksPlanner extends EventEmitter { this.emit('updateToolTip') } - skipToMicrobreak () { - this.scheduleMicrobreak(100) - } - - scheduleBreak (delay) { + skipToBreak (delay = 100) { this.scheduler.cancel() const shouldBreak = this.settings.get('break') const shouldMicrobreak = this.settings.get('microbreak') @@ -200,10 +196,6 @@ class BreaksPlanner extends EventEmitter { this.emit('updateToolTip') } - skipToBreak () { - this.scheduleBreak(100) - } - clear () { this.scheduler.cancel() this.breakNumber = 0 diff --git a/app/main.js b/app/main.js index 09208441e..fb8c10450 100644 --- a/app/main.js +++ b/app/main.js @@ -97,25 +97,25 @@ if (!gotTheLock) { case 'mini': { log.info('Stretchly: skip to Mini Break (requested by second instance)') - const ms = cmd.waitToMs() - if (ms === -1) { + const delay = cmd.waitToMs() + if (delay === -1) { log.error('Stretchly: error parsing wait interval to ms because of invalid value') return } if (cmd.options.title) nextIdea = [cmd.options.title] - if (!cmd.options.noskip || ms) skipToMicrobreak(ms) + if (!cmd.options.noskip || delay) skipToMicrobreak(delay) break } case 'long': { log.info('Stretchly: skip to Long Break (requested by second instance)') - const ms = cmd.waitToMs() - if (ms === -1) { + const delay = cmd.waitToMs() + if (delay === -1) { log.error('Stretchly: error parsing wait interval to ms because of invalid value') return } nextIdea = [cmd.options.title ? cmd.options.title : null, cmd.options.text ? cmd.options.text : null] - if (!cmd.options.noskip || ms) skipToBreak(ms) + if (!cmd.options.noskip || delay) skipToBreak(delay) break } @@ -132,13 +132,13 @@ if (!gotTheLock) { case 'pause': { log.info('Stretchly: pause Breaks (requested by second instance)') - const ms = cmd.durationToMs(settings) + const duration = cmd.durationToMs(settings) // -1 indicates an invalid value - if (ms === -1) { + if (duration === -1) { log.error('Stretchly: error when parsing duration to ms because of invalid value') return } - pauseBreaks(ms) + pauseBreaks(duration) break } } @@ -955,7 +955,7 @@ function postponeBreak (shouldPlaySound = false) { updateTray() } -function skipToMicrobreak (delay = 0) { +function skipToMicrobreak (delay) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } @@ -963,7 +963,7 @@ function skipToMicrobreak (delay = 0) { breakWins = breakComplete(false, breakWins) } if (delay) { - breakPlanner.scheduleMicrobreak(delay) + breakPlanner.skipToMicrobreak(delay) log.info(`Stretchly: skipping to Mini Break in ${delay}ms`) } else { breakPlanner.skipToMicrobreak() @@ -972,7 +972,7 @@ function skipToMicrobreak (delay = 0) { updateTray() } -function skipToBreak (delay = 0) { +function skipToBreak (delay) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } @@ -980,7 +980,7 @@ function skipToBreak (delay = 0) { breakWins = breakComplete(false, breakWins) } if (delay) { - breakPlanner.scheduleBreak(delay) + breakPlanner.skipToBreak(delay) log.info(`Stretchly: skipping to Long Break in ${delay}ms`) } else { breakPlanner.skipToBreak() diff --git a/app/utils/commands.js b/app/utils/commands.js index 1ae7bb51c..5d06acb6f 100644 --- a/app/utils/commands.js +++ b/app/utils/commands.js @@ -91,6 +91,10 @@ const allExamples = [{ { cmd: 'stretchly long -T "Stretch up!" -t "Go stretch!"', description: 'Start a long break, with the title "Stretch up!" and text "Go stretch!"' +}, +{ + cmd: 'stretchly long -w 20m -T "Stretch up!"', + description: 'Wait 20 minutes, then start a long break with the title set to "Stretch up!"' }] // Parse cmd line, check if valid and put variables in a dedicated object From f412970bee3cf19bd919f52bb39608d9054dd225 Mon Sep 17 00:00:00 2001 From: Jan Hovancik Date: Mon, 21 Feb 2022 14:55:55 +0100 Subject: [PATCH 3/4] Update readme --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8bd2e51f..392cc94c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - advanced option for app exclusion check interval -- support Apple silicon +- build for Apple silicon +- ability to schedule break from command line ## [1.9.0] - 2021-12-24 ### Changed From cf9342afb28e0a2ee7a493f0d8f1de125add1f6b Mon Sep 17 00:00:00 2001 From: Jan Hovancik Date: Mon, 21 Feb 2022 15:09:43 +0100 Subject: [PATCH 4/4] Adds tests --- app/process.js | 1 - test/commands.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/app/process.js b/app/process.js index f3b114a4a..fc5d01ca8 100644 --- a/app/process.js +++ b/app/process.js @@ -8,7 +8,6 @@ const log = require('electron-log') const path = require('path') log.transports.file.resolvePath = () => path.join(remote.app.getPath('userData'), 'logs/main.log') - window.onload = (e) => { ipcRenderer.on('playSound', (event, file, volume) => { const audio = new Audio(`audio/${file}.wav`) diff --git a/test/commands.js b/test/commands.js index c3ce7fba9..32974d80d 100644 --- a/test/commands.js +++ b/test/commands.js @@ -116,4 +116,70 @@ describe('commands', () => { const cmd = new Command(input, '1.2.3') cmd.durationToMs(null).should.be.equal(-1) }) + + it('parses a number scheduler as the number of minutes to break', () => { + const input = ['long', '-w', '60'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(60 * 60 * 1000) + }) + + it('parses a scheduler argument with hours and minutes', () => { + const input = ['long', '-w', '4h39m'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(4 * 60 * 60 * 1000 + 39 * 60 * 1000) + }) + + it('parses a scheduler argument with hours and minutes in the upper case', () => { + const input = ['long', '-w', '4H39M'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(4 * 60 * 60 * 1000 + 39 * 60 * 1000) + }) + + it('parses a scheduler argument with just the minutes', () => { + const input = ['long', '-w', '60m'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(60 * 60 * 1000) + }) + + it('parses a scheduler argument with just the hours', () => { + const input = ['long', '-w', '184h'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(184 * 60 * 60 * 1000) + }) + + it('returns -1 if there\'s extra text in the scheduler argument', () => { + new Command(['long', '-w', 'foo4h39mbar'], '1.2.3').waitToMs(null).should.be.equal(-1) + new Command(['long', '-w', 'foo4h39m'], '1.2.3').waitToMs(null).should.be.equal(-1) + new Command(['long', '-w', '4h39mbar'], '1.2.3').waitToMs(null).should.be.equal(-1) + }) + + it('returns -1 if a number scheduler argument is zero', () => { + const input = ['long', '-w', '0'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(-1) + }) + + it('returns -1 if the scheduler argument with the hours and minutes evaluates to zero', () => { + const input = ['long', '-w', '0h0m'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(-1) + }) + + it('returns -1 if the scheduler argument with just the minutes evaluates to zero', () => { + const input = ['long', '-w', '0m'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(-1) + }) + + it('returns -1 if the scheduler argument with just the hours evaluates to zero', () => { + const input = ['long', '-w', '0h'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(-1) + }) + + it('should return -1 if the scheduler argument is not in a known format', () => { + const input = ['long', '-w', '10i20k'] + const cmd = new Command(input, '1.2.3') + cmd.waitToMs(null).should.be.equal(-1) + }) })