From e180a086c7381a32cebcdbc8b8e6963266d2e2d5 Mon Sep 17 00:00:00 2001 From: Luke Arms Date: Tue, 22 Feb 2022 01:14:41 +1100 Subject: [PATCH] Allow the next break or microbreak to be scheduled from the CLI (#1092) Allow the next break or microbreak to be scheduled from the CLI Co-authored-by: Jan Hovancik --- CHANGELOG.md | 3 +- app/breaksPlanner.js | 10 +++---- app/main.js | 63 ++++++++++++++++++++++++++++------------- app/process.js | 1 - app/utils/commands.js | 26 ++++++++++++++--- test/commands.js | 66 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 31 deletions(-) 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 diff --git a/app/breaksPlanner.js b/app/breaksPlanner.js index 7e98af0c2..57b2ba90c 100644 --- a/app/breaksPlanner.js +++ b/app/breaksPlanner.js @@ -168,22 +168,22 @@ class BreaksPlanner extends EventEmitter { this.emit('updateToolTip') } - skipToMicrobreak () { + skipToMicrobreak (delay = 100) { 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 () { + skipToBreak (delay = 100) { this.scheduler.cancel() const shouldBreak = this.settings.get('break') const shouldMicrobreak = this.settings.get('microbreak') @@ -191,7 +191,7 @@ 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') } diff --git a/app/main.js b/app/main.js index 01bc4c943..fb8c10450 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 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) skipToMicrobreak() + if (!cmd.options.noskip || delay) skipToMicrobreak(delay) break + } - case 'long': + case 'long': { log.info('Stretchly: skip to Long Break (requested by second instance)') + 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) skipToBreak() + if (!cmd.options.noskip || delay) skipToBreak(delay) break + } case 'resume': log.info('Stretchly: resume Breaks (requested by second instance)') @@ -119,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) { - log.error('Stretchly: error when parsing duration to ms because of unvalid value') + if (duration === -1) { + log.error('Stretchly: error when parsing duration to ms because of invalid value') return } - pauseBreaks(ms) + pauseBreaks(duration) break } } @@ -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) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } if (breakWins) { breakWins = breakComplete(false, breakWins) } - breakPlanner.skipToMicrobreak() - log.info('Stretchly: skipping to Mini Break') + if (delay) { + breakPlanner.skipToMicrobreak(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) { if (microbreakWins) { microbreakWins = breakComplete(false, microbreakWins) } if (breakWins) { breakWins = breakComplete(false, breakWins) } - breakPlanner.skipToBreak() - log.info('Stretchly: skipping to Long Break') + if (delay) { + breakPlanner.skipToBreak(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/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/app/utils/commands.js b/app/utils/commands.js index 386def3e4..5d06acb6f 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] } } @@ -85,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 @@ -145,7 +155,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 +174,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 +196,14 @@ class Command { } } + waitToMs () { + if (!this.options.wait) { + return 0 + } + + return parseDuration(this.options.wait) + } + checkInMain () { if (!this.command) { return false 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) + }) })