jevstrudel.git / website / src / repl / idbutils.mjs
1import { registerSampleSource } from '@strudel/webaudio';
2import { isAudioFile } from './files.mjs';
3import { logger } from '@strudel/core';

utilites for writing and reading to the indexdb

7export const userSamplesDBConfig = {
8  dbName: 'samples',
9  table: 'usersamples',
10  columns: ['blob', 'title'],
11  version: 1,
12};

deletes all of the databases, useful for debugging

15function clearAllIDB() {
16  window.indexedDB
17    .databases()
18    .then((r) => {
19      for (var i = 0; i < r.length; i++) clearIDB(r[i].name);
20    })
21    .then(() => {
22      alert('All data cleared.');
23    });
24}
26export function clearIDB(dbName) {
27  return window.indexedDB.deleteDatabase(dbName);
28}

queries the DB, and registers the sounds so they can be played

31export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => {}) {
32  openDB(config, (objectStore) => {
33    const query = objectStore.getAll();
34    query.onerror = (e) => {
35      logger('User Samples failed to load ', 'error');
36      onComplete();
37      console.error(e?.target?.error);
38    };
39
40    query.onsuccess = (event) => {
41      const soundFiles = event.target.result;
42      if (!soundFiles?.length) {
43        return;
44      }
45      const sounds = new Map();
46
47      Promise.all(
48        [...soundFiles]
49          .sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
50          .map((soundFile, i) => {
51            const title = soundFile.title;
52            if (!isAudioFile(title)) {
53              return;
54            }
55            const splitRelativePath = soundFile.id.split('/');
56            let parentDirectory =
57              //fallback to file name before period and seperator if no parent directory
58              splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
59            const blob = soundFile.blob;
60
61            return blobToDataUrl(blob).then((soundPath) => {
62              const titlePathMap = sounds.get(parentDirectory) ?? new Map();
63
64              titlePathMap.set(title, soundPath);
65
66              sounds.set(parentDirectory, titlePathMap);
67              return;
68            });
69          }),
70      )
71        .then(() => {
72          sounds.forEach((titlePathMap, key) => {
73            const value = Array.from(titlePathMap.keys())
74              .sort((a, b) => {
75                return a.localeCompare(b);
76              })
77              .map((title) => titlePathMap.get(title));
78
79            registerSampleSource(key, value, { prebake: false });
80          });
81
82          logger('imported sounds registered!', 'success');
83          onComplete();
84        })
85        .catch((error) => {
86          logger('Something went wrong while registering saved samples from the index db', 'error');
87          console.error(error);
88        });
89    };
90  });
91}
93async function blobToDataUrl(blob) {
94  return new Promise((resolve) => {
95    resolve(URL.createObjectURL(blob));
96  });
97}

open db and initialize it if necessary

100function openDB(config, onOpened) {
101  const { dbName, version, table, columns } = config;
102  if (typeof window === 'undefined') {
103    return;
104  }
105  if (!('indexedDB' in window)) {
106    console.log('IndexedDB is not supported.');
107    return;
108  }
109  const dbOpen = indexedDB.open(dbName, version);
110
111  dbOpen.onupgradeneeded = (_event) => {
112    const db = dbOpen.result;
113    const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false });
114    columns.forEach((c) => {
115      objectStore.createIndex(c, c, { unique: false });
116    });
117  };
118  dbOpen.onerror = (err) => {
119    logger('Something went wrong while trying to open the the client DB', 'error');
120    console.error(`indexedDB error: ${err.errorCode}`);
121  };
122
123  dbOpen.onsuccess = () => {
124    const db = dbOpen.result;
125    // lock store for writing
126    const writeTransaction = db.transaction([table], 'readwrite');
127    // get object store
128    const objectStore = writeTransaction.objectStore(table);
129    onOpened(objectStore, db);
130  };
131  return dbOpen;
132}
134async function processFilesForIDB(files) {
135  return Promise.all(
136    Array.from(files)
137      .map((s) => {
138        const title = s.name;
139
140        if (!isAudioFile(title)) {
141          return;
142        }
143        //create obscured url to file system that can be fetched
144        const sUrl = URL.createObjectURL(s);
145        //fetch the sound and turn it into a buffer array
146        return fetch(sUrl).then((res) => {
147          return res.blob().then((blob) => {
148            const path = s.webkitRelativePath;
149            let id = path?.length ? path : title;
150            if (id == null || title == null || blob == null) {
151              return;
152            }
153            return {
154              title,
155              blob,
156              id,
157            };
158          });
159        });
160      })
161      .filter(Boolean),
162  ).catch((error) => {
163    logger('Something went wrong while processing uploaded files', 'error');
164    console.error(error);
165  });
166}
167
168export async function uploadSamplesToDB(config, files) {
169  logger('procesing user samples...');
170  await processFilesForIDB(files).then((files) => {
171    logger('user samples processed... opening db');
172    const onOpened = (objectStore, _db) => {
173      logger('index db opened... writing files to db');
174      files.forEach((file) => {
175        if (file == null) {
176          return;
177        }
178        objectStore.put(file);
179      });
180      logger('user samples written successfully');
181    };
182    openDB(config, onOpened);
183  });
184}