From 117d397d770bbf386e9b898d94869a23d8cac15e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:49:28 +0530 Subject: [PATCH] Add offline db --- lib/store/offline_authenticator_db.dart | 170 ++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 lib/store/offline_authenticator_db.dart diff --git a/lib/store/offline_authenticator_db.dart b/lib/store/offline_authenticator_db.dart new file mode 100644 index 000000000..569acd396 --- /dev/null +++ b/lib/store/offline_authenticator_db.dart @@ -0,0 +1,170 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:ente_auth/models/authenticator/auth_entity.dart'; +import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; + +class OfflineAuthenticatorDB { + static const _databaseName = "ente.offline_authenticator.db"; + static const _databaseVersion = 1; + + static const entityTable = 'entities'; + + OfflineAuthenticatorDB._privateConstructor(); + static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor(); + + static Future? _dbFuture; + + Future get database async { + _dbFuture ??= _initDatabase(); + return _dbFuture!; + } + + Future _initDatabase() async { + final Directory documentsDirectory = + await getApplicationDocumentsDirectory(); + final String path = join(documentsDirectory.path, _databaseName); + debugPrint(path); + return await openDatabase( + path, + version: _databaseVersion, + onCreate: _onCreate, + ); + } + + Future _onCreate(Database db, int version) async { + await db.execute( + ''' + CREATE TABLE $entityTable ( + _generatedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + id TEXT, + encryptedData TEXT NOT NULL, + header TEXT NOT NULL, + createdAt INTEGER NOT NULL, + updatedAt INTEGER NOT NULL, + shouldSync INTEGER DEFAULT 0, + UNIQUE(id) + ); + ''', + ); + } + + Future insert(String encData, String header) async { + final db = await instance.database; + final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; + final insertedID = await db.insert( + entityTable, + { + "encryptedData": encData, + "header": header, + "shouldSync": 1, + "createdAt": timeInMicroSeconds, + "updatedAt": timeInMicroSeconds, + }, + ); + return insertedID; + } + + Future updateEntry( + int generatedID, + String encData, + String header, + ) async { + final db = await instance.database; + final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; + int affectedRows = await db.update( + entityTable, + { + "encryptedData": encData, + "header": header, + "shouldSync": 1, + "updatedAt": timeInMicroSeconds, + }, + where: '_generatedID = ?', + whereArgs: [generatedID], + ); + return affectedRows; + } + + Future insertOrReplace(List authEntities) async { + final db = await instance.database; + final batch = db.batch(); + for (AuthEntity authEntity in authEntities) { + final insertRow = authEntity.toMap(); + insertRow.remove('isDeleted'); + insertRow.putIfAbsent('shouldSync', () => 0); + batch.insert( + entityTable, + insertRow, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + await batch.commit(noResult: true); + } + + Future updateLocalEntity(LocalAuthEntity localAuthEntity) async { + final db = await instance.database; + await db.update( + entityTable, + localAuthEntity.toMap(), + where: '_generatedID = ?', + whereArgs: [localAuthEntity.generatedID], + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future getEntryByID(int genID) async { + final db = await instance.database; + final rows = await db + .query(entityTable, where: '_generatedID = ?', whereArgs: [genID]); + final listOfAuthEntities = _convertRows(rows); + if (listOfAuthEntities.isEmpty) { + return null; + } else { + return listOfAuthEntities.first; + } + } + + Future> getAll() async { + final db = await instance.database; + final rows = await db.rawQuery("SELECT * from $entityTable"); + return _convertRows(rows); + } + +// deleteByID will prefer generated id if both ids are passed during deletion + Future deleteByIDs({List? generatedIDs, List? ids}) async { + final db = await instance.database; + final batch = db.batch(); + const whereGenID = '_generatedID = ?'; + const whereID = 'id = ?'; + if (generatedIDs != null) { + for (int genId in generatedIDs) { + batch.delete(entityTable, where: whereGenID, whereArgs: [genId]); + } + } + if (ids != null) { + for (String id in ids) { + batch.delete(entityTable, where: whereID, whereArgs: [id]); + } + } + final result = await batch.commit(); + debugPrint("Done"); + } + + Future clearTable() async { + final db = await instance.database; + await db.delete(entityTable); + } + + List _convertRows(List> rows) { + final keys = []; + for (final row in rows) { + keys.add(LocalAuthEntity.fromMap(row)); + } + return keys; + } +}