import 'dart:math'; import 'dart:typed_data'; import 'constants.dart'; /// An exception thrown by [VirtualFileSystem] implementations written in Dart /// to signal that an operation could not be completed. /// /// {@category common} final class VfsException implements Exception { /// The error code to return to sqlite3. final int returnCode; const VfsException(this.returnCode) : assert(returnCode != 0); @override String toString() { return 'VfsException($returnCode)'; } } /// A filename passed to [VirtualFileSystem.xOpen]. /// /// {@category common} base class Sqlite3Filename { final String? path; Sqlite3Filename(this.path); } /// A [virtual filesystem][vfs] used by sqlite3 to access the current I/O /// environment. /// /// Instead of having an integer return code, file system implementations should /// throw a [VfsException] to signal invalid operations. /// For details on the individual methods, consult the `sqlite3.h` header file /// and its documentation. /// /// For a file system implementation that implements a few methods already, /// consider extending [BaseVirtualFileSystem]. /// /// [vfs]: https://www.sqlite.org/c3ref/vfs.html /// /// {@category common} abstract base class VirtualFileSystem { /// The name of this virtual file system. /// /// This can be passed as an URI parameter when opening databases to select /// it. final String name; VirtualFileSystem(this.name); /// Opens a file, returning supported flags and a file instance. XOpenResult xOpen(Sqlite3Filename path, int flags); /// Delete a file. void xDelete(String path, int syncDir); /// Check whether a file can be accessed. int xAccess(String path, int flags); /// Resolves a [path] name supplied by the user into a path that can be used /// by the other methods of this VFS. String xFullPathName(String path); /// Fill the [target] with random bytes. /// /// __Safety warning__: Target may be a direct view over native memory that /// must not be used after this function returns. void xRandomness(Uint8List target); /// Sleeps for the passed [duration]. void xSleep(Duration duration); /// Returns the current time. DateTime xCurrentTime(); } /// The result of [VirtualFileSystem.xOpen]. typedef XOpenResult = ({int outFlags, VirtualFileSystemFile file}); /// A file implemented by a VFS author and returned by [VirtualFileSystem.xOpen]. /// /// To avoid common pitfalls, consider extending [BaseVfsFile] instead. /// /// {@category common} abstract interface class VirtualFileSystemFile { /// Close this file. void xClose(); /// Fill the [target] with bytes read from [fileOffset]. /// /// If the file is not large enough to fullfill the read, a [VfsException] /// with an error code of [SqlExtendedError.SQLITE_IOERR_SHORT_READ] must be /// thrown. Additional, the rest of [target] must be filled with zeroes. /// /// __Safety warning__: Target may be a direct view over native memory that /// must not be used after this function returns. void xRead(Uint8List target, int fileOffset); /// Writes the [buffer] into this file at [fileOffset], overwriting existing /// content or appending to it. /// /// If, for some reason, only a part of the buffer could be written, a /// [VfsException] must be thrown. /// /// __Safety warning__: Target may be a direct view over native memory that /// must not be used after this function returns. void xWrite(Uint8List buffer, int fileOffset); /// Truncates this file to a size of [size]. void xTruncate(int size); /// Synchronizes, or flushes, the contents of this file to the file system. void xSync(int flags); /// Returns the size of this file. int xFileSize(); /// Moves the lock state of this file to a more exclusive lock state. void xLock(int mode); /// Moves the lock state of this file to a less exclusive lock state. void xUnlock(int mode); /// Returns the lock state held by any process on this file. int xCheckReservedLock(); /// int get xDeviceCharacteristics; } /// A [VirtualFileSystem] implementation that uses a [Random] instance for /// [xRandomness] and [DateTime.now] for [xCurrentTime]. /// /// {@category common} abstract base class BaseVirtualFileSystem extends VirtualFileSystem { final Random random; BaseVirtualFileSystem({Random? random, required String name}) : random = random ?? _fallbackRandom, super(name); @override void xRandomness(Uint8List target) { generateRandomness(target, random); } @override DateTime xCurrentTime() => DateTime.now(); /// Fills [target] with random bytes. /// /// An optional [random] source can be provided, otherwise a default instance /// of [Random.secure] will be used. static void generateRandomness(Uint8List target, [Random? random]) { random ??= _fallbackRandom; for (var i = 0; i < target.length; i++) { target[i] = random.nextInt(1 << 8); } } static final Random _fallbackRandom = Random.secure(); } /// A [VirtualFileSystemFile] base class that implements [xRead] to zero-fill /// the buffer in case of short reads. /// /// {@category common} abstract class BaseVfsFile implements VirtualFileSystemFile { /// Reads from the file at [offset] into the [buffer] and returns the amount /// of bytes read. /// /// __Safety warning__: [buffer] may be a direct view over native memory that /// must not be used after this function returns. int readInto(Uint8List buffer, int offset); @override int get xDeviceCharacteristics => 0; @override void xRead(Uint8List target, int fileOffset) { final bytesRead = readInto(target, fileOffset); if (bytesRead < target.length) { // Remaining buffer must be filled with zeroes. target.fillRange(bytesRead, target.length, 0); // And we need to return a short read error throw const VfsException(SqlExtendedError.SQLITE_IOERR_SHORT_READ); } } }