| 
									
										
										
										
											2017-11-03 00:09:34 +00:00
										 |  |  | const { time } = require('lib/time-utils'); | 
					
						
							| 
									
										
										
										
											2017-12-14 18:12:14 +00:00
										 |  |  | const BaseItem = require('lib/models/BaseItem.js'); | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | const Alarm = require('lib/models/Alarm'); | 
					
						
							| 
									
										
										
										
											2017-12-14 18:12:14 +00:00
										 |  |  | const Folder = require('lib/models/Folder.js'); | 
					
						
							|  |  |  | const Note = require('lib/models/Note.js'); | 
					
						
							| 
									
										
										
										
											2017-11-03 00:09:34 +00:00
										 |  |  | const { _ } = require('lib/locale.js'); | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ReportService { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-21 18:48:50 +00:00
										 |  |  | 	csvEscapeCell(cell) { | 
					
						
							|  |  |  | 		cell = this.csvValueToString(cell); | 
					
						
							|  |  |  | 		let output = cell.replace(/"/, '""'); | 
					
						
							|  |  |  | 		if (this.csvCellRequiresQuotes(cell, ',')) { | 
					
						
							|  |  |  | 			return '"' + output + '"'; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return output; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	csvCellRequiresQuotes(cell, delimiter) { | 
					
						
							|  |  |  | 		if (cell.indexOf('\n') >= 0) return true; | 
					
						
							|  |  |  | 		if (cell.indexOf('"') >= 0) return true; | 
					
						
							|  |  |  | 		if (cell.indexOf(delimiter) >= 0) return true; | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	csvValueToString(v) { | 
					
						
							|  |  |  | 		if (v === undefined || v === null) return ''; | 
					
						
							|  |  |  | 		return v.toString(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	csvCreateLine(row) { | 
					
						
							|  |  |  | 		for (let i = 0; i < row.length; i++) { | 
					
						
							|  |  |  | 			row[i] = this.csvEscapeCell(row[i]); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return row.join(','); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	csvCreate(rows) { | 
					
						
							|  |  |  | 		let output = []; | 
					
						
							|  |  |  | 		for (let i = 0; i < rows.length; i++) { | 
					
						
							|  |  |  | 			output.push(this.csvCreateLine(rows[i])); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return output.join('\n'); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async basicItemList(option = null) { | 
					
						
							|  |  |  | 		if (!option) option = {}; | 
					
						
							|  |  |  | 		if (!option.format) option.format = 'array'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const itemTypes = BaseItem.syncItemTypes(); | 
					
						
							|  |  |  | 		let output = []; | 
					
						
							|  |  |  | 		output.push(['type', 'id', 'updated_time', 'sync_time', 'is_conflict']); | 
					
						
							|  |  |  | 		for (let i = 0; i < itemTypes.length; i++) { | 
					
						
							|  |  |  | 			const itemType = itemTypes[i]; | 
					
						
							|  |  |  | 			const ItemClass = BaseItem.getClassByItemType(itemType); | 
					
						
							|  |  |  | 			const items = await ItemClass.modelSelectAll('SELECT items.id, items.updated_time, sync_items.sync_time FROM ' + ItemClass.tableName() + ' items JOIN sync_items ON sync_items.item_id = items.id'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (let j = 0; j < items.length; j++) { | 
					
						
							|  |  |  | 				const item = items[j]; | 
					
						
							|  |  |  | 				let row = [itemType, item.id, item.updated_time, item.sync_time]; | 
					
						
							|  |  |  | 				row.push(('is_conflict' in item) ? item.is_conflict : ''); | 
					
						
							|  |  |  | 				output.push(row); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return option.format === 'csv' ? this.csvCreate(output) : output; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-16 17:06:05 +01:00
										 |  |  | 	async syncStatus(syncTarget) { | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		let output = { | 
					
						
							|  |  |  | 			items: {}, | 
					
						
							|  |  |  | 			total: {}, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		let itemCount = 0; | 
					
						
							|  |  |  | 		let syncedCount = 0; | 
					
						
							|  |  |  | 		for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) { | 
					
						
							|  |  |  | 			let d = BaseItem.syncItemDefinitions_[i]; | 
					
						
							|  |  |  | 			let ItemClass = BaseItem.getClass(d.className); | 
					
						
							|  |  |  | 			let o = { | 
					
						
							|  |  |  | 				total: await ItemClass.count(), | 
					
						
							| 
									
										
										
										
											2017-07-16 17:06:05 +01:00
										 |  |  | 				synced: await ItemClass.syncedCount(syncTarget), | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 			output.items[d.className] = o; | 
					
						
							|  |  |  | 			itemCount += o.total; | 
					
						
							|  |  |  | 			syncedCount += o.synced; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-15 23:47:11 +01:00
										 |  |  | 		let conflictedCount = await Note.conflictedCount(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		output.total = { | 
					
						
							| 
									
										
										
										
											2017-07-15 23:47:11 +01:00
										 |  |  | 			total: itemCount - conflictedCount, | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 			synced: syncedCount, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		output.toDelete = { | 
					
						
							| 
									
										
										
										
											2017-07-19 20:15:55 +01:00
										 |  |  | 			total: await BaseItem.deletedItemCount(syncTarget), | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-15 16:35:40 +01:00
										 |  |  | 		output.conflicted = { | 
					
						
							|  |  |  | 			total: await Note.conflictedCount(), | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		output.items['Note'].total -= output.conflicted.total; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		return output; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-16 17:06:05 +01:00
										 |  |  | 	async status(syncTarget) { | 
					
						
							|  |  |  | 		let r = await this.syncStatus(syncTarget); | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		let sections = []; | 
					
						
							| 
									
										
										
										
											2017-12-05 19:21:01 +00:00
										 |  |  | 		let section = null; | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-05 19:21:01 +00:00
										 |  |  | 		const disabledItems = await BaseItem.syncDisabledItems(syncTarget); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (disabledItems.length) { | 
					
						
							|  |  |  | 			section = { title: _('Items that cannot be synchronised'), body: [] }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (let i = 0; i < disabledItems.length; i++) { | 
					
						
							|  |  |  | 				const row = disabledItems[i]; | 
					
						
							|  |  |  | 				section.body.push(_('"%s": "%s"', row.item.title, row.syncInfo.sync_disabled_reason)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			sections.push(section); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		section = { title: _('Sync status (synced items / total items)'), body: [] }; | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		for (let n in r.items) { | 
					
						
							|  |  |  | 			if (!r.items.hasOwnProperty(n)) continue; | 
					
						
							|  |  |  | 			section.body.push(_('%s: %d/%d', n, r.items[n].synced, r.items[n].total)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-15 16:35:40 +01:00
										 |  |  | 		section.body.push(_('Total: %d/%d', r.total.synced, r.total.total)); | 
					
						
							|  |  |  | 		section.body.push(''); | 
					
						
							|  |  |  | 		section.body.push(_('Conflicted: %d', r.conflicted.total)); | 
					
						
							|  |  |  | 		section.body.push(_('To delete: %d', r.toDelete.total)); | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		sections.push(section); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | 		section = { title: _('Folders'), body: [] }; | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | 		const folders = await Folder.all({ | 
					
						
							| 
									
										
										
										
											2017-07-26 19:36:16 +01:00
										 |  |  | 			order: { by: 'title', dir: 'ASC' }, | 
					
						
							| 
									
										
										
										
											2017-07-14 18:02:45 +00:00
										 |  |  | 			caseInsensitive: true, | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		for (let i = 0; i < folders.length; i++) { | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | 			const folder = folders[i]; | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 			section.body.push(_('%s: %d notes', folders[i].title, await Folder.noteCount(folders[i].id))); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sections.push(section); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | 		const alarms = await Alarm.allDue(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-05 19:21:01 +00:00
										 |  |  | 		if (alarms.length) { | 
					
						
							|  |  |  | 			section = { title: _('Coming alarms'), body: [] }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (let i = 0; i < alarms.length; i++) { | 
					
						
							|  |  |  | 				const alarm = alarms[i]; | 
					
						
							|  |  |  | 				const note = await Note.load(alarm.note_id); | 
					
						
							|  |  |  | 				section.body.push(_('On %s: %s', time.formatMsToLocal(alarm.trigger_time), note.title)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			sections.push(section); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-11-28 20:31:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 18:09:47 +00:00
										 |  |  | 		return sections; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-03 00:13:17 +00:00
										 |  |  | module.exports = { ReportService }; |