Skip to content

Commit 13f9998

Browse files
committed
more bulk import to own file
1 parent 9941f14 commit 13f9998

File tree

6 files changed

+170
-132
lines changed

6 files changed

+170
-132
lines changed

src/main.ts

Lines changed: 8 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder } from 'obsidian';
1+
import type { TFile } from 'obsidian';
2+
import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFolder } from 'obsidian';
23
import { requestUrl, normalizePath } from 'obsidian';
34
import type { MediaType } from 'src/utils/MediaType';
45
import { APIManager } from './api/APIManager';
@@ -14,19 +15,18 @@ import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
1415
import { SteamAPI } from './api/apis/SteamAPI';
1516
import { WikipediaAPI } from './api/apis/WikipediaAPI';
1617
import { ConfirmOverwriteModal } from './modals/ConfirmOverwriteModal';
17-
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
1818
import type { MediaTypeModel } from './models/MediaTypeModel';
1919
import { PropertyMapper } from './settings/PropertyMapper';
2020
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
2121
import type { MediaDbPluginSettings } from './settings/Settings';
2222
import { getDefaultSettings, MediaDbSettingTab } from './settings/Settings';
23+
import { BulkImportHelper } from './utils/BulkImportHelper';
2324
import { DateFormatter } from './utils/DateFormatter';
2425
import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager';
2526
import type { SearchModalOptions } from './utils/ModalHelper';
26-
import { ModalHelper, ModalResultCode } from './utils/ModalHelper';
27+
import { ModalHelper } from './utils/ModalHelper';
2728
import type { CreateNoteOptions } from './utils/Utils';
28-
import { dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils';
29-
import { BulkImportLookupMethod } from 'src/utils/BulkImportLookupMethod';
29+
import { replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils';
3030

3131
export type Metadata = Record<string, unknown>;
3232

@@ -42,6 +42,7 @@ export default class MediaDbPlugin extends Plugin {
4242
mediaTypeManager!: MediaTypeManager;
4343
modelPropertyMapper!: PropertyMapper;
4444
modalHelper!: ModalHelper;
45+
bulkImportHelper!: BulkImportHelper;
4546
dateFormatter!: DateFormatter;
4647

4748
frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---';
@@ -64,6 +65,7 @@ export default class MediaDbPlugin extends Plugin {
6465
this.mediaTypeManager = new MediaTypeManager();
6566
this.modelPropertyMapper = new PropertyMapper(this);
6667
this.modalHelper = new ModalHelper(this);
68+
this.bulkImportHelper = new BulkImportHelper(this);
6769
this.dateFormatter = new DateFormatter();
6870

6971
await this.loadSettings();
@@ -84,7 +86,7 @@ export default class MediaDbPlugin extends Plugin {
8486
menu.addItem(item => {
8587
item.setTitle('Import folder as Media DB entries')
8688
.setIcon('database')
87-
.onClick(() => this.createEntriesFromFolder(file));
89+
.onClick(() => this.bulkImportHelper.import(file));
8890
});
8991
}
9092
}),
@@ -558,116 +560,6 @@ export default class MediaDbPlugin extends Plugin {
558560
}
559561
}
560562

561-
async createEntriesFromFolder(folder: TFolder): Promise<void> {
562-
const erroredFiles: { filePath: string; error: string }[] = [];
563-
let canceled: boolean = false;
564-
565-
const { selectedAPI, lookupMethod, fieldName, appendContent } = await new Promise<{
566-
selectedAPI: string;
567-
lookupMethod: string;
568-
fieldName: string;
569-
appendContent: boolean;
570-
}>(resolve => {
571-
new MediaDbFolderImportModal(this.app, this, (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => {
572-
resolve({ selectedAPI, lookupMethod, fieldName, appendContent });
573-
}).open();
574-
});
575-
576-
for (const child of folder.children) {
577-
if (child instanceof TFile) {
578-
const file: TFile = child;
579-
if (canceled) {
580-
erroredFiles.push({ filePath: file.path, error: 'user canceled' });
581-
continue;
582-
}
583-
584-
const metadata = this.getMetadataFromFileCache(file);
585-
const lookupValue = metadata[fieldName];
586-
587-
if (!lookupValue || typeof lookupValue !== 'string') {
588-
erroredFiles.push({ filePath: file.path, error: `metadata field '${fieldName}' not found, empty, or not a string` });
589-
continue;
590-
} else if (lookupMethod === BulkImportLookupMethod.ID) {
591-
try {
592-
const model = await this.apiManager.queryDetailedInfoById(lookupValue, selectedAPI);
593-
if (model) {
594-
await this.createMediaDbNotes([model], appendContent ? file : undefined);
595-
} else {
596-
erroredFiles.push({ filePath: file.path, error: `Failed to query API with id: ${lookupValue}` });
597-
}
598-
} catch (e) {
599-
erroredFiles.push({ filePath: file.path, error: `${e}` });
600-
continue;
601-
}
602-
} else if (lookupMethod === BulkImportLookupMethod.TITLE) {
603-
let results: MediaTypeModel[] = [];
604-
try {
605-
results = await this.apiManager.query(lookupValue, [selectedAPI]);
606-
} catch (e) {
607-
erroredFiles.push({ filePath: file.path, error: `${e}` });
608-
continue;
609-
}
610-
if (!results || results.length === 0) {
611-
erroredFiles.push({ filePath: file.path, error: `no search results` });
612-
continue;
613-
}
614-
615-
const { selectModalResult, selectModal } = await this.modalHelper.createSelectModal({
616-
elements: results,
617-
skipButton: true,
618-
modalTitle: `Results for '${lookupValue}'`,
619-
});
620-
621-
if (selectModalResult.code === ModalResultCode.ERROR) {
622-
erroredFiles.push({ filePath: file.path, error: selectModalResult.error.message });
623-
selectModal.close();
624-
continue;
625-
}
626-
627-
if (selectModalResult.code === ModalResultCode.CLOSE) {
628-
erroredFiles.push({ filePath: file.path, error: 'user canceled' });
629-
selectModal.close();
630-
canceled = true;
631-
continue;
632-
}
633-
634-
if (selectModalResult.code === ModalResultCode.SKIP) {
635-
erroredFiles.push({ filePath: file.path, error: 'user skipped' });
636-
selectModal.close();
637-
continue;
638-
}
639-
640-
if (selectModalResult.data.selected.length === 0) {
641-
erroredFiles.push({ filePath: file.path, error: `no search results selected` });
642-
continue;
643-
}
644-
645-
const detailedResults = await this.queryDetails(selectModalResult.data.selected);
646-
await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
647-
648-
selectModal.close();
649-
} else {
650-
erroredFiles.push({ filePath: file.path, error: `invalid lookup type` });
651-
continue;
652-
}
653-
}
654-
}
655-
656-
if (erroredFiles.length > 0) {
657-
await this.createErroredFilesReport(erroredFiles);
658-
}
659-
}
660-
661-
async createErroredFilesReport(erroredFiles: { filePath: string; error: string }[]): Promise<void> {
662-
const title = `MDB - bulk import error report ${dateTimeToString(new Date())}`;
663-
const filePath = `${title}.md`;
664-
665-
const table = [['file', 'error']].concat(erroredFiles.map(x => [x.filePath, x.error]));
666-
667-
const fileContent = `# ${title}\n\n${markdownTable(table)}`;
668-
await this.app.vault.create(filePath, fileContent);
669-
}
670-
671563
async loadSettings(): Promise<void> {
672564
// console.log(DEFAULT_SETTINGS);
673565
const diskSettings: MediaDbPluginSettings = (await this.loadData()) as MediaDbPluginSettings;

src/modals/MediaDbFolderImportModal.ts renamed to src/modals/MediaDbBulkImportModal.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import type { App, ButtonComponent } from 'obsidian';
1+
import type { ButtonComponent } from 'obsidian';
22
import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
3-
import type MediaDbPlugin from '../main';
43
import type { APIModel } from 'src/api/APIModel';
5-
import { BulkImportLookupMethod } from 'src/utils/BulkImportLookupMethod';
4+
import { BulkImportLookupMethod } from 'src/utils/BulkImportHelper';
5+
import type MediaDbPlugin from '../main';
66

7-
export class MediaDbFolderImportModal extends Modal {
7+
export class MediaDbBulkImportModal extends Modal {
88
plugin: MediaDbPlugin;
9-
onSubmit: (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => void;
9+
onSubmit: (selectedAPI: string, lookupMethod: BulkImportLookupMethod, fieldName: string, appendContent: boolean) => void;
1010
selectedApi: string;
1111
searchBtn?: ButtonComponent;
12-
lookupMethod: string;
12+
lookupMethod: BulkImportLookupMethod;
1313
fieldName: string;
1414
appendContent: boolean;
1515

16-
constructor(app: App, plugin: MediaDbPlugin, onSubmit: (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => void) {
17-
super(app);
16+
constructor(plugin: MediaDbPlugin, onSubmit: (selectedAPI: string, lookupMethod: BulkImportLookupMethod, fieldName: string, appendContent: boolean) => void) {
17+
super(plugin.app);
1818
this.plugin = plugin;
1919
this.onSubmit = onSubmit;
2020
this.selectedApi = plugin.apiManager.apis[0].apiName;
@@ -71,7 +71,7 @@ export class MediaDbFolderImportModal extends Modal {
7171
contentEl,
7272
'Lookup media by',
7373
(value: string) => {
74-
this.lookupMethod = value;
74+
this.lookupMethod = value as BulkImportLookupMethod;
7575
},
7676
[
7777
{ value: BulkImportLookupMethod.TITLE, display: 'Title' },

src/utils/BulkImportHelper.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import type { TFolder } from 'obsidian';
2+
import { TFile } from 'obsidian';
3+
import type MediaDbPlugin from 'src/main';
4+
import { MediaDbBulkImportModal as MediaDbBulkImportModal } from 'src/modals/MediaDbBulkImportModal';
5+
import type { MediaTypeModel } from 'src/models/MediaTypeModel';
6+
import { ModalResultCode } from './ModalHelper';
7+
import { dateTimeToString, markdownTable } from './Utils';
8+
9+
export enum BulkImportLookupMethod {
10+
ID = 'id',
11+
TITLE = 'title',
12+
}
13+
14+
interface BulkImportError {
15+
filePath: string;
16+
error: string;
17+
canceled?: boolean;
18+
}
19+
20+
export class BulkImportHelper {
21+
readonly plugin: MediaDbPlugin;
22+
23+
constructor(plugin: MediaDbPlugin) {
24+
this.plugin = plugin;
25+
}
26+
27+
async import(folder: TFolder): Promise<void> {
28+
const erroredFiles: BulkImportError[] = [];
29+
let canceled: boolean = false;
30+
31+
const { selectedAPI, lookupMethod, fieldName, appendContent } = await new Promise<{
32+
selectedAPI: string;
33+
lookupMethod: BulkImportLookupMethod;
34+
fieldName: string;
35+
appendContent: boolean;
36+
}>(resolve => {
37+
new MediaDbBulkImportModal(this.plugin, (selectedAPI: string, lookupMethod: BulkImportLookupMethod, fieldName: string, appendContent: boolean) => {
38+
resolve({ selectedAPI, lookupMethod, fieldName, appendContent });
39+
}).open();
40+
});
41+
42+
for (const child of folder.children) {
43+
if (!(child instanceof TFile)) {
44+
continue;
45+
}
46+
47+
const file: TFile = child;
48+
if (canceled) {
49+
erroredFiles.push({ filePath: file.path, error: 'user canceled' });
50+
continue;
51+
}
52+
53+
const metadata = this.plugin.getMetadataFromFileCache(file);
54+
const lookupValue = metadata[fieldName];
55+
56+
if (!lookupValue || typeof lookupValue !== 'string') {
57+
erroredFiles.push({ filePath: file.path, error: `metadata field '${fieldName}' not found, empty, or not a string` });
58+
continue;
59+
} else if (lookupMethod === BulkImportLookupMethod.ID) {
60+
const error = await this.importById(file, lookupValue, selectedAPI, appendContent);
61+
if (error) {
62+
erroredFiles.push(error);
63+
}
64+
} else if (lookupMethod === BulkImportLookupMethod.TITLE) {
65+
const error = await this.importByTitle(file, lookupValue, selectedAPI, appendContent);
66+
if (error) {
67+
if (error.canceled) {
68+
canceled = true;
69+
}
70+
erroredFiles.push(error);
71+
}
72+
} else {
73+
erroredFiles.push({ filePath: file.path, error: `invalid lookup type` });
74+
continue;
75+
}
76+
}
77+
78+
if (erroredFiles.length > 0) {
79+
await this.createErroredFilesReport(erroredFiles);
80+
}
81+
}
82+
83+
private async importById(file: TFile, lookupValue: string, selectedAPI: string, appendContent: boolean): Promise<BulkImportError | undefined> {
84+
try {
85+
const model = await this.plugin.apiManager.queryDetailedInfoById(lookupValue, selectedAPI);
86+
if (model) {
87+
await this.plugin.createMediaDbNotes([model], appendContent ? file : undefined);
88+
return undefined;
89+
} else {
90+
return { filePath: file.path, error: `Failed to query API with id: ${lookupValue}` };
91+
}
92+
} catch (e) {
93+
return { filePath: file.path, error: `${e}` };
94+
}
95+
}
96+
97+
private async importByTitle(file: TFile, lookupValue: string, selectedAPI: string, appendContent: boolean): Promise<BulkImportError | undefined> {
98+
let results: MediaTypeModel[] = [];
99+
try {
100+
results = await this.plugin.apiManager.query(lookupValue, [selectedAPI]);
101+
} catch (e) {
102+
return { filePath: file.path, error: `${e}` };
103+
}
104+
if (!results || results.length === 0) {
105+
return { filePath: file.path, error: `no search results` };
106+
}
107+
108+
const { selectModalResult, selectModal } = await this.plugin.modalHelper.createSelectModal({
109+
elements: results,
110+
skipButton: true,
111+
modalTitle: `Results for '${lookupValue}'`,
112+
});
113+
114+
if (selectModalResult.code === ModalResultCode.ERROR) {
115+
selectModal.close();
116+
return { filePath: file.path, error: selectModalResult.error.message };
117+
}
118+
119+
if (selectModalResult.code === ModalResultCode.CLOSE) {
120+
selectModal.close();
121+
return { filePath: file.path, error: 'user canceled', canceled: true };
122+
}
123+
124+
if (selectModalResult.code === ModalResultCode.SKIP) {
125+
selectModal.close();
126+
return { filePath: file.path, error: 'user skipped' };
127+
}
128+
129+
if (selectModalResult.data.selected.length === 0) {
130+
selectModal.close();
131+
return { filePath: file.path, error: `no search results selected` };
132+
}
133+
134+
const detailedResults = await this.plugin.queryDetails(selectModalResult.data.selected);
135+
await this.plugin.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
136+
137+
selectModal.close();
138+
return undefined;
139+
}
140+
141+
private async createErroredFilesReport(erroredFiles: BulkImportError[]): Promise<void> {
142+
const title = `MDB - bulk import error report ${dateTimeToString(new Date())}`;
143+
const filePath = `${title}.md`;
144+
145+
const table = [['file', 'error']].concat(erroredFiles.map(x => [x.filePath, x.error]));
146+
147+
const fileContent = `# ${title}\n\n${markdownTable(table)}`;
148+
await this.plugin.app.vault.create(filePath, fileContent);
149+
}
150+
}

src/utils/BulkImportLookupMethod.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/utils/IllegalFilenameCharactersList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Illegal characters in the form `[illegal_character, replacement][]`
22
export const ILLEGAL_FILENAME_CHARACTERS = [
3-
['\/', '-'],
3+
['/', '-'],
44
['\\', '-'],
55
['<', ''],
66
['>', ''],

src/utils/MediaTypeManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { MusicReleaseModel } from '../models/MusicReleaseModel';
1010
import { SeriesModel } from '../models/SeriesModel';
1111
import { WikiModel } from '../models/WikiModel';
1212
import type { MediaDbPluginSettings } from '../settings/Settings';
13+
import { ILLEGAL_FILENAME_CHARACTERS } from './IllegalFilenameCharactersList';
1314
import { MediaType } from './MediaType';
1415
import { replaceTags } from './Utils';
15-
import { ILLEGAL_FILENAME_CHARACTERS } from './IllegalFilenameCharactersList';
1616

1717
export const MEDIA_TYPES: MediaType[] = [
1818
MediaType.Movie,
@@ -72,7 +72,7 @@ export class MediaTypeManager {
7272

7373
getFileName(mediaTypeModel: MediaTypeModel): string {
7474
// Ignore undefined tags since some search APIs do not return all properties in the model and produce clean file names even if errors occur
75-
let fileName = replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType())!, mediaTypeModel, true);
75+
const fileName = replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType())!, mediaTypeModel, true);
7676
return this.cleanFileName(fileName);
7777
}
7878

0 commit comments

Comments
 (0)