mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Electron: Getting notifications to work
This commit is contained in:
parent
7df6541902
commit
6e23fead59
@ -355,7 +355,15 @@ class Application extends BaseApplication {
|
||||
setInterval(() => { runAutoUpdateCheck() }, 2 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
reg.scheduleSync();
|
||||
setTimeout(() => {
|
||||
AlarmService.garbageCollect();
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
reg.scheduleSync().then(() => {
|
||||
// Wait for the first sync before updating the notifications, since synchronisation
|
||||
// might change the notifications.
|
||||
AlarmService.updateAllNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class Bridge {
|
||||
this.autoUpdateLogger_ = new Logger();
|
||||
this.autoUpdateLogger_.addTarget('file', { path: logFilePath });
|
||||
this.autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG);
|
||||
this.autoUpdateLogger_.info('checkForUpdatesAndNotify: Intializing...');
|
||||
this.autoUpdateLogger_.info('checkForUpdatesAndNotify: Initializing...');
|
||||
this.autoUpdater_ = require("electron-updater").autoUpdater;
|
||||
this.autoUpdater_.logger = this.autoUpdateLogger_;
|
||||
}
|
||||
|
@ -348,7 +348,7 @@ class BaseApplication {
|
||||
this.dbLogger_.setLevel(initArgs.logLevel);
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
this.dbLogger_.setLevel(Logger.LEVEL_DEBUG);
|
||||
this.dbLogger_.setLevel(Logger.LEVEL_WARN);
|
||||
}
|
||||
|
||||
this.logger_.info('Profile directory: ' + profileDir);
|
||||
|
@ -331,14 +331,14 @@ class BaseModel {
|
||||
}
|
||||
|
||||
static delete(id, options = null) {
|
||||
options = this.modOptions(options);
|
||||
if (!id) throw new Error('Cannot delete object without an ID');
|
||||
options = this.modOptions(options);
|
||||
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
static batchDelete(ids, options = null) {
|
||||
if (!ids.length) return;
|
||||
options = this.modOptions(options);
|
||||
if (!ids.length) throw new Error('Cannot delete object without an ID');
|
||||
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id IN ("' + ids.join('","') + '")');
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,14 @@ class Alarm extends BaseModel {
|
||||
return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]);
|
||||
}
|
||||
|
||||
static async garbageCollect() {
|
||||
// Delete alarms that have already been triggered
|
||||
await this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]);
|
||||
static async deleteExpiredAlarms() {
|
||||
return this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]);
|
||||
}
|
||||
|
||||
// Delete alarms that correspond to non-existent notes
|
||||
static async alarmIdsWithoutNotes() {
|
||||
// https://stackoverflow.com/a/4967229/561309
|
||||
await this.db().exec('DELETE FROM alarms WHERE id IN (SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL)');
|
||||
|
||||
// TODO: Check for duplicate alarms for a note
|
||||
// const rows = await this.db().exec('SELECT count(*) as note_count, note_id from alarms group by note_id having note_count >= 2');
|
||||
const alarms = await this.db().selectAll('SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL');
|
||||
return alarms.map((a) => { return a.id });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -422,7 +422,7 @@ class Note extends BaseItem {
|
||||
}
|
||||
|
||||
static dueNotes() {
|
||||
return this.modelSelectAll('SELECT id, title, body, todo_due FROM notes WHERE is_conflict = 0 AND is_todo = 1 AND todo_completed = 0 AND todo_due > ?', [time.unixMs()]);
|
||||
return this.modelSelectAll('SELECT id, title, body, is_todo, todo_due, todo_completed, is_conflict FROM notes WHERE is_conflict = 0 AND is_todo = 1 AND todo_completed = 0 AND todo_due > ?', [time.unixMs()]);
|
||||
}
|
||||
|
||||
static needAlarm(note) {
|
||||
|
@ -37,6 +37,11 @@ reg.syncTarget = (syncTargetId = null) => {
|
||||
reg.scheduleSync = async (delay = null) => {
|
||||
if (delay === null) delay = 1000 * 3;
|
||||
|
||||
let promiseResolve = null;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
promiseResolve = resolve;
|
||||
});
|
||||
|
||||
if (reg.scheduleSyncId_) {
|
||||
clearTimeout(reg.scheduleSyncId_);
|
||||
reg.scheduleSyncId_ = null;
|
||||
@ -57,6 +62,7 @@ reg.scheduleSync = async (delay = null) => {
|
||||
|
||||
if (!reg.syncTarget(syncTargetId).isAuthenticated()) {
|
||||
reg.logger().info('Synchroniser is missing credentials - manual sync required to authenticate.');
|
||||
promiseResolve();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -73,6 +79,7 @@ reg.scheduleSync = async (delay = null) => {
|
||||
if (error.code == 'alreadyStarted') {
|
||||
reg.logger().info(error.message);
|
||||
} else {
|
||||
promiseResolve();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -82,6 +89,8 @@ reg.scheduleSync = async (delay = null) => {
|
||||
}
|
||||
|
||||
reg.setupRecurrentSync();
|
||||
|
||||
promiseResolve();
|
||||
};
|
||||
|
||||
if (delay === 0) {
|
||||
@ -89,6 +98,8 @@ reg.scheduleSync = async (delay = null) => {
|
||||
} else {
|
||||
reg.scheduleSyncId_ = setTimeout(timeoutCallback, delay);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
reg.setupRecurrentSync = () => {
|
||||
|
@ -20,13 +20,52 @@ class AlarmService {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
static async updateNoteNotification(noteId, isDeleted = false) {
|
||||
const note = await Note.load(noteId);
|
||||
static async garbageCollect() {
|
||||
this.logger().info('Garbage collecting alarms...');
|
||||
|
||||
// Delete alarms that have already been triggered
|
||||
await Alarm.deleteExpiredAlarms();
|
||||
|
||||
// Delete alarms that correspond to non-existent notes
|
||||
const alarmIds = await Alarm.alarmIdsWithoutNotes();
|
||||
for (let i = 0; i < alarmIds.length; i++) {
|
||||
this.logger().info('Clearing notification for non-existing note. Alarm ' + alarmIds[i]);
|
||||
await this.driver().clearNotification(alarmIds[i]);
|
||||
}
|
||||
await Alarm.batchDelete(alarmIds);
|
||||
}
|
||||
|
||||
// When passing a note, make sure it has all the required properties
|
||||
// (better to pass a complete note or else just the ID)
|
||||
static async updateNoteNotification(noteOrId, isDeleted = false) {
|
||||
let note = null;
|
||||
let noteId = null;
|
||||
|
||||
if (typeof noteOrId === 'object') {
|
||||
note = noteOrId;
|
||||
noteId = note.id;
|
||||
} else {
|
||||
note = await Note.load(noteOrId);
|
||||
noteId = note ? note.id : null;
|
||||
}
|
||||
|
||||
if (!note && !isDeleted) return;
|
||||
|
||||
const driver = this.driver();
|
||||
|
||||
let alarm = await Alarm.byNoteId(note.id);
|
||||
let clearAlarm = false;
|
||||
|
||||
const makeNotificationFromAlarm = (alarm) => {
|
||||
return {
|
||||
id: alarm.id,
|
||||
date: new Date(note.todo_due),
|
||||
title: note.title,
|
||||
}
|
||||
}
|
||||
|
||||
console.info('NOTE', note, Note.needAlarm(note));
|
||||
|
||||
if (isDeleted ||
|
||||
!Note.needAlarm(note) ||
|
||||
(alarm && alarm.trigger_time !== note.todo_due))
|
||||
@ -34,11 +73,24 @@ class AlarmService {
|
||||
clearAlarm = !!alarm;
|
||||
}
|
||||
|
||||
if (!clearAlarm && alarm) return; // Alarm already exists and set at the right time
|
||||
if (!clearAlarm && alarm) { // Alarm already exists and set at the right time
|
||||
|
||||
// For persistent notifications (those that stay active after the app has been closed, like on mobile), if we have
|
||||
// an alarm object we can be sure that the notification has already been set, so there's nothing to do.
|
||||
// For non-persistent notifications however we need to check that the notification has been set because, for example,
|
||||
// if the app has just started the notifications need to be set again. so we do this below.
|
||||
if (!driver.hasPersistentNotifications() && !driver.notificationIsSet(alarm.id)) {
|
||||
const notification = makeNotificationFromAlarm(alarm);
|
||||
this.logger().info('Scheduling (non-persistent) notification for note ' + note.id, notification);
|
||||
driver.scheduleNotification(notification);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearAlarm) {
|
||||
this.logger().info('Clearing notification for note ' + noteId);
|
||||
await this.driver().clearNotification(alarm.id);
|
||||
await driver.clearNotification(alarm.id);
|
||||
await Alarm.delete(alarm.id);
|
||||
}
|
||||
|
||||
@ -52,20 +104,28 @@ class AlarmService {
|
||||
// Reload alarm to get its ID
|
||||
alarm = await Alarm.byNoteId(note.id);
|
||||
|
||||
const notification = {
|
||||
id: alarm.id,
|
||||
date: new Date(note.todo_due),
|
||||
title: note.title,
|
||||
};
|
||||
const notification = makeNotificationFromAlarm(alarm);
|
||||
|
||||
if (note.body) notification.body = note.body;
|
||||
|
||||
this.logger().info('Scheduling notification for note ' + note.id, notification);
|
||||
await this.driver().scheduleNotification(notification);
|
||||
await driver.scheduleNotification(notification);
|
||||
}
|
||||
|
||||
static async updateAllNotifications() {
|
||||
this.logger().info('Updading all notifications...');
|
||||
|
||||
await this.garbageCollect();
|
||||
|
||||
const dueNotes = await Note.dueNotes();
|
||||
for (let i = 0; i < dueNotes.length; i++) {
|
||||
await this.updateNoteNotification(dueNotes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: inner notifications (when app is active)
|
||||
// TODO: locale-dependent format
|
||||
// TODO: status to view active notifications
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,14 @@ const PushNotification = require('react-native-push-notification');
|
||||
|
||||
class AlarmServiceDriver {
|
||||
|
||||
hasPersistentNotifications() {
|
||||
return true;
|
||||
}
|
||||
|
||||
notificationIsSet(alarmId) {
|
||||
throw new Error('Available only for non-persistent alarms');
|
||||
}
|
||||
|
||||
async clearNotification(id) {
|
||||
PushNotification.cancelLocalNotifications({ id: id });
|
||||
}
|
||||
|
@ -1,21 +1,32 @@
|
||||
class AlarmServiceDriverNode {
|
||||
|
||||
constructor() {
|
||||
this.notifications_ = {};
|
||||
}
|
||||
|
||||
hasPersistentNotifications() {
|
||||
return false;
|
||||
}
|
||||
|
||||
notificationIsSet(id) {
|
||||
return id in this.notifications_;
|
||||
}
|
||||
|
||||
async clearNotification(id) {
|
||||
console.info('AlarmServiceDriverNode::clearNotification', id);
|
||||
if (!this.notificationIsSet(id)) return;
|
||||
clearTimeout(this.notifications_[id].timeoutId);
|
||||
delete this.notifications_[id];
|
||||
}
|
||||
|
||||
async scheduleNotification(notification) {
|
||||
console.info('AlarmServiceDriverNode::scheduleNotification', notification);
|
||||
const now = Date.now();
|
||||
const interval = notification.date.getTime() - now;
|
||||
if (interval < 0) return;
|
||||
|
||||
// const androidNotification = {
|
||||
// id: notification.id,
|
||||
// message: notification.title.substr(0, 100), // No idea what the limits are for title and body but set something reasonable anyway
|
||||
// date: notification.date,
|
||||
// };
|
||||
|
||||
// if ('body' in notification) androidNotification.body = notification.body.substr(0, 512);
|
||||
|
||||
// PushNotification.localNotificationSchedule(androidNotification);
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.info('NOTIFICATION: ', notification);
|
||||
this.clearNotification(notification.id);
|
||||
}, interval);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user