manual method
Import stores from specified inputFiles
Setting collisionHandler
allows for custom behaviour in the event that
a store with the same name already exists. If it returns true
, the store
will be overwritten, otherwise (and by default) the import will fail.
Note that there are no checks to confirm that the listed inputFiles
are
valid Isar databases. Attempting to import other files or corrupted
databases may cause the app to crash.
Also see withGUI for a prebuilt solution to allow the user to select files to import.
Returns a Map of the input filename to its corresponding ImportResult.
Implementation
Map<String, Future<ImportResult>> manual(
List<File> inputFiles, {
FutureOr<bool> Function(String filename, String storeName)?
collisionHandler,
}) =>
Map.fromEntries(
inputFiles.map(
(f) => MapEntry(
p.basename(f.path),
() async {
// Quit if the input file no longer exists
if (!await f.exists()) {
return const ImportResult._(storeName: null, successful: false);
}
// Create the temporary directory
final tmpDir = FMTC.instance.rootDirectory.directory >> 'import';
await tmpDir.create();
// Construct temporary structures to read the Isar database at an
// appropriate location
final tmpPath = tmpDir >
'.import${DateTime.now().millisecondsSinceEpoch}.isar';
final tmpFile = File(tmpPath);
Isar? tmpDb;
// Copy the target file to the temporary file and try to open it
await f.copy(tmpPath);
try {
tmpDb = await Isar.open(
[DbStoreDescriptorSchema, DbTileSchema, DbMetadataSchema],
name: tmpPath.replaceAll('.isar', ''),
directory: tmpDir.absolute.path,
maxSizeMiB: FMTC.instance.settings.databaseMaxSize,
compactOnLaunch:
FMTC.instance.settings.databaseCompactCondition,
inspector: FMTC.instance.debugMode,
);
} catch (_) {
if (tmpDb != null && tmpDb.isOpen) await tmpDb.close();
if (await tmpDir.exists()) await tmpDir.delete(recursive: true);
return const ImportResult._(storeName: null, successful: false);
}
// Read the store name from within the temporary database, closing
// it afterward
final storeName = (await tmpDb.storeDescriptor.get(0))?.name;
if (storeName == null) {
return const ImportResult._(storeName: null, successful: false);
}
if (tmpDb.isOpen) await tmpDb.close();
// Check if there is a conflict with an existing store
if (FMTC.instance(storeName).manage.ready) {
if (!await (collisionHandler?.call(
p.basename(f.path),
storeName,
) ??
false)) {
if (await tmpDir.exists()) {
await tmpDir.delete(recursive: true);
}
return ImportResult._(
storeName: storeName,
successful: false,
);
}
await FMTC.instance(storeName).manage.delete();
}
// Calculate the store's ID, and rename the temporary file to it
final storeId = DatabaseTools.hash(storeName);
await tmpFile.rename(
(FMTC.instance.rootDirectory.directory >> '$storeId.isar')
.absolute
.path,
);
// Register the new store instance
// Doesn't require error catching, as the same database has already
// been opened.
FMTCRegistry.instance.register(
storeId,
await Isar.open(
[DbStoreDescriptorSchema, DbTileSchema, DbMetadataSchema],
name: storeId.toString(),
directory: FMTC.instance.rootDirectory.directory.path,
maxSizeMiB: FMTC.instance.settings.databaseMaxSize,
compactOnLaunch:
FMTC.instance.settings.databaseCompactCondition,
inspector: FMTC.instance.debugMode,
),
);
// Delete temporary structures
if (await tmpDir.exists()) await tmpDir.delete(recursive: true);
return ImportResult._(storeName: storeName, successful: true);
}(),
),
),
);