Skip to content

Commit

Permalink
refactor(spawn): replace exec with spawn. Add error handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
bandantonio committed Apr 16, 2024
1 parent e20c752 commit d663505
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 2,084 deletions.
2 changes: 1 addition & 1 deletion esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ if (prod) {
process.exit(0);
} else {
await context.watch();
}
}
138 changes: 61 additions & 77 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import * as child_process from 'child_process';


import dayjs from 'dayjs';
import * as Handlebars from 'handlebars';
import { normalizePath, Notice, Plugin } from 'obsidian';
import dayjs from 'dayjs';
import * as path from 'path';
import { promisify } from 'util';
import path from 'path';
import { DEFAULT_SETTINGS, IBookHighlightsSettingTab } from './src/settings';
import { IBookHighlightsPluginSearchModal } from './src/search';
import { dbRequest, dbAnnotationsRequest } from 'src/db/index';
import {
BOOKS_DB_NAME,
BOOKS_LIBRARY_NAME,
BOOKS_LIBRARY_COLUMNS,
HIGHLIGHTS_DB_NAME,
HIGHLIGHTS_LIBRARY_NAME,
HIGHLIGHTS_LIBRARY_COLUMNS
} from './src/db/constants';
import {
CombinedHighlight,
ICombinedBooksAndHighlights,
IBook,
IBookAnnotation,
IBookHighlightsPluginSettings
Expand All @@ -18,38 +27,46 @@ export default class IBookHighlightsPlugin extends Plugin {

async onload() {
const settings = await this.loadSettings();

if (settings.importOnStart) {
await this.importAndSaveHighlights();
await this.aggregateAndSaveHighlights();
}

this.addRibbonIcon('book-open', this.manifest.name, async () => {
await this.importAndSaveHighlights().then(() => {
await this.aggregateAndSaveHighlights().then(() => {
new Notice('Apple Books highlights imported successfully');
}).catch(() => {
}).catch((error) => {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
console.error(`[${this.manifest.name}]: ${error}`);

});
});

this.addSettingTab(new IBookHighlightsSettingTab(this.app, this));

this.addCommand({
id: 'import-all-highlights',
name: 'Import all',
callback: async () => {
try {
await this.importAndSaveHighlights();
await this.aggregateAndSaveHighlights();
} catch (error) {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
console.error(`[${this.manifest.name}]: ${error}`);
}
},
});

this.addCommand({
id: 'import-single-highlights',
name: 'From a specific book...',
callback: () => {
new IBookHighlightsPluginSearchModal(this.app, this).open();
callback: async () => {
try {
new IBookHighlightsPluginSearchModal(this.app, this).open();
} catch (error) {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
console.error(`[${this.manifest.name}]: ${error}`);
}
},
});
}
Expand All @@ -59,7 +76,7 @@ export default class IBookHighlightsPlugin extends Plugin {

async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());

return this.settings;
}

Expand All @@ -68,69 +85,36 @@ export default class IBookHighlightsPlugin extends Plugin {
}

async getBooks(): Promise<IBook[]> {
try {
const IBOOK_LIBRARY = '~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite';
const booksSql = `
SELECT ZASSETID, ZTITLE, ZAUTHOR, ZGENRE, ZLANGUAGE, ZLASTOPENDATE, ZCOVERURL
FROM ZBKLIBRARYASSET
WHERE ZPURCHASEDATE IS NOT NULL`;

const command = `echo "${booksSql}" | sqlite3 ${IBOOK_LIBRARY} -json`;
const exec = promisify(child_process.exec);
// Issue #11 - Temporary set maxBuffer to 100MB
// TODO: Need a more efficient solution to handle large data
const { stdout } = await exec(command, { maxBuffer: 100 * 1024 * 1024 });

if (!stdout) {
throw('No books found. Looks like your Apple Books library is empty.');
}

return JSON.parse(stdout);
} catch (error) {
console.warn(`[${this.manifest.name}]:`, error);
return [];
}
const bookDetails = await dbRequest(
BOOKS_DB_NAME,
`SELECT ${BOOKS_LIBRARY_COLUMNS.join(', ')} FROM ${BOOKS_LIBRARY_NAME} WHERE ZPURCHASEDATE IS NOT NULL`
);

return bookDetails;
}

async getAnnotations(): Promise<IBookAnnotation[]> {
try {
const IBOOK_ANNOTATION_DB = '~/Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite';
const annotationsSql = `
SELECT ZANNOTATIONASSETID, ZFUTUREPROOFING5, ZANNOTATIONREPRESENTATIVETEXT, ZANNOTATIONSELECTEDTEXT, ZANNOTATIONNOTE, ZANNOTATIONCREATIONDATE, ZANNOTATIONMODIFICATIONDATE, ZANNOTATIONSTYLE
FROM ZAEANNOTATION
WHERE ZANNOTATIONSELECTEDTEXT IS NOT NULL
AND ZANNOTATIONDELETED IS 0`;

const command = `echo "${annotationsSql}" | sqlite3 ${IBOOK_ANNOTATION_DB} -json`;
const exec = promisify(child_process.exec);
// Issue #11 - Temporary set maxBuffer to 100MB
// TODO: Need a more efficient solution to handle large data
const { stdout } = await exec(command, { maxBuffer: 100 * 1024 * 1024 });

if (stdout.length === 0) {
throw('No highlights found. Make sure you made some highlights in your Apple Books.');
}

return JSON.parse(stdout);
} catch (error) {
console.warn(`[${this.manifest.name}]:`, error);
return [];
}

const annotationDetails = await dbAnnotationsRequest(
HIGHLIGHTS_DB_NAME,
`SELECT ${HIGHLIGHTS_LIBRARY_COLUMNS.join(', ')} FROM ${HIGHLIGHTS_LIBRARY_NAME} WHERE ZANNOTATIONSELECTEDTEXT IS NOT NULL AND ZANNOTATIONDELETED IS 0`
);

return annotationDetails;
}
async importHighlights(): Promise<CombinedHighlight[]> {

async aggregateBookAndHighlightDetails(): Promise<ICombinedBooksAndHighlights[]> {
const books = await this.getBooks();
const annotations = await this.getAnnotations();

const resultingHighlights: CombinedHighlight[] = books.reduce((highlights: CombinedHighlight[], book: IBook) => {
const resultingHighlights: ICombinedBooksAndHighlights[] = books.reduce((highlights: ICombinedBooksAndHighlights[], book: IBook) => {
const bookRelatedAnnotations: IBookAnnotation[] = annotations.filter(annotation => annotation.ZANNOTATIONASSETID === book.ZASSETID);

if (bookRelatedAnnotations.length > 0) {
// Obsidian forbids adding certain characters to the title of a note, so they must be replaced with a dash (-)
// | # ^ [] \ / :
//eslint-disable-next-line
// eslint-disable-next-line
const normalizedBookTitle = book.ZTITLE.replace(/[\|\#\^\[\]\\\/\:]+/g, ' -');

highlights.push({
bookTitle: normalizedBookTitle,
bookId: book.ZASSETID,
Expand All @@ -155,21 +139,21 @@ export default class IBookHighlightsPlugin extends Plugin {

return highlights;
}, []);

return resultingHighlights;
}
async importAndSaveHighlights(): Promise<void> {
const highlights = await this.importHighlights();

async aggregateAndSaveHighlights(): Promise<void> {
const highlights = await this.aggregateBookAndHighlightDetails();

if (highlights.length === 0) {
throw ('No highlights found. Make sure you made some highlights in your Apple Books.');
}

await this.saveHighlightsToVault(highlights);
}
async saveHighlightsToVault(highlights: CombinedHighlight[]) {

async saveHighlightsToVault(highlights: ICombinedBooksAndHighlights[]) {
const highlightsFolderPath = this.app.vault.getAbstractFileByPath(this.settings.highlightsFolder);
const isBackupEnabled = this.settings.backup;

Expand All @@ -191,18 +175,18 @@ export default class IBookHighlightsPlugin extends Plugin {

await this.app.vault.createFolder(this.settings.highlightsFolder);

highlights.forEach(async (highlight: CombinedHighlight) => {
highlights.forEach(async (highlight: ICombinedBooksAndHighlights) => {
// TODO: Consider moving to a separate file if there are several helpers to be added
Handlebars.registerHelper('eq', (a, b) => {
if (a == b) {
return this;
}
});

Handlebars.registerHelper('dateFormat', (date, format) => {
return dayjs('2001-01-01').add(date, 's').format(format);
});

const template = Handlebars.compile(this.settings.template);
const renderedTemplate = template(highlight);

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "apple-books-import-highlights",
"name": "Apple Books - Import Highlights",
"version": "1.2.3",
"version": "1.3.0",
"minAppVersion": "0.15.0",
"description": "Import your Apple Books highlights and notes to Obsidian.",
"author": "bandantonio",
Expand Down
Loading

0 comments on commit d663505

Please sign in to comment.