Closed pishguy closed 3 years ago
Hi, thanks for your issue, The disk cache supports customization now: https://github.com/hurshi/dio-http-cache/issues/33#issuecomment-698268181
@hurshi is any simple implementation about this feature to know how can i set that?
@hurshi i read the library source code and i can't find any disk store implementation and it seems supported database internally
Yes, by default only database storage is implemented, but now there is support for custom disk caching, so you can implement it yourself, whether it's file storage, Hive or Moor.
Yes, by default only database storage is implemented, but now there is support for custom disk caching, so you can implement it yourself, whether it's file storage, Hive or Moor.
hi @hurshi i try using hive but it seems not working, it keep call to network after first request, where did i miss, please help
class HiveStore implements ICacheStore {
final boxName = 'hive_cache';
@override
Future<bool> clearAll() async {
var box = await Hive.openBox(boxName);
box.clear();
return true;
}
@override
Future<bool> clearExpired() async {
var now = DateTime.now().millisecondsSinceEpoch;
var box = await Hive.openBox<CacheObj>(boxName);
var expired = box.values.where((e) =>
e.maxStaleDate > 0 && e.maxStaleDate < now ||
e.maxStaleDate <= 0 && e.maxAgeDate < now);
box.deleteAll(expired);
return true;
}
@override
Future<bool> delete(String key, {String subKey}) async {
var box = await Hive.openBox<CacheObj>(boxName);
var delTarget = box.values.where((e) => e.key == key);
if (subKey != null) {
delTarget = box.values.where((e) => e.key == key && e.subKey == subKey);
}
box.deleteAll(delTarget);
return true;
}
@override
Future<CacheObj> getCacheObj(String key, {String subKey}) async {
var box = await Hive.openBox<CacheObj>(boxName);
if (box.values.length == 0) {
return null;
}
var cache = subKey != null
? box.values.firstWhere((e) => e.key == key && e.subKey == subKey)
: box.values.firstWhere((e) => e.key == key);
return cache;
}
@override
Future<bool> setCacheObj(CacheObj obj) async {
var box = await Hive.openBox<CacheObj>(boxName);
box.put(obj.key, obj);
return true;
}
}
class HiveCacheAdapter extends TypeAdapter<CacheObj> {
HiveCacheAdapter({int typeId = TYPE_ID}) : _typeId = typeId;
static const TYPE_ID = 101;
final int _typeId;
@override
CacheObj read(BinaryReader reader) {
return CacheObj.fromJson(reader.readMap());
}
@override
int get typeId => _typeId;
@override
void write(BinaryWriter writer, CacheObj obj) {
writer.writeMap(obj.toJson());
}
}
and at my config
static CacheConfig get cacheConfig {
var config = CacheConfig(
defaultRequestMethod: 'GET',
baseUrl: Endpoint.baseUrl,
defaultMaxAge: const Duration(days: 3),
skipDiskCache: true,
diskStore: HiveStore(),
);
return config;
}
@5arif hi, could you solve your issue?
@MahdiPishguy not yet, any clue ?
@MahdiPishguy not yet, any clue ?
not yet. i'm trying to check your implementation
Hi,
Based on the class DiskCacheStore I did some implementation, where I could save the content in files into the app filesystem and use SQLite to apply the cache rules for deletion, creation and querying. This may solve the Issue 56
@hurshi By some weird reason if I implement the ICacheStore interface I have a runtime error that said that my class is not a subclass of DiskCacheStore and I had to implement this class directly as workaround to this problem.
import 'dart:io';
import 'package:dio_http_cache/dio_http_cache.dart';
import 'package:dio_http_cache/src/store/store_disk.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:path/path.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:uuid/uuid.dart';
/// Cache file store
class CacheFileStore implements DiskCacheStore {
final String cacheFolderName;
final String databaseName;
final String _tableCacheObject = "cache_file_dio";
final String _columnKey = "key";
final String _columnSubKey = "subKey";
final String _columnMaxAgeDate = "max_age_date";
final String _columnMaxStaleDate = "max_stale_date";
final String _columnFileName = "file_name";
final String _columnStatusCode = "statusCode";
final String _columnHeaders = "headers";
Database _db;
static const int _curDBVersion = 3;
Future<Database> get _database async {
if (null == _db) {
var path = await getDatabasesPath();
await Directory(path).create(recursive: true);
path = join(path, "$databaseName.db");
_db = await openDatabase(path,
version: _curDBVersion,
onConfigure: (db) => _tryFixDbNoVersionBug(db, path),
onCreate: _onCreate,
onUpgrade: _onUpgrade);
await _clearExpired(_db);
}
return _db;
}
_tryFixDbNoVersionBug(Database db, String dbPath) async {
if ((await db.getVersion()) == 0) {
var isTableUserLogExist = await db
.rawQuery(
"select DISTINCT tbl_name from sqlite_master where tbl_name = '$_tableCacheObject'")
.then((v) => (null != v && v.length > 0));
if (isTableUserLogExist) {
await db.setVersion(1);
}
}
}
_getCreateTableSql() => '''
CREATE TABLE IF NOT EXISTS $_tableCacheObject (
$_columnKey text,
$_columnSubKey text,
$_columnMaxAgeDate integer,
$_columnMaxStaleDate integer,
$_columnFileName text,
$_columnStatusCode integer,
$_columnHeaders BLOB,
PRIMARY KEY ($_columnKey, $_columnSubKey)
)
''';
_onCreate(Database db, int version) async {
await db.execute(_getCreateTableSql());
}
List<List<String>> _dbUpgradeList() => [
// 0 -> 1
null,
// 1 -> 2
[
"ALTER TABLE $_tableCacheObject ADD COLUMN $_columnStatusCode integer;"
],
// 2 -> 3 : Change $_columnContent from text to BLOB
["DROP TABLE IF EXISTS $_tableCacheObject;", _getCreateTableSql()],
];
_onUpgrade(Database db, int oldVersion, int newVersion) async {
var mergeLength = _dbUpgradeList().length;
if (oldVersion < 0 || oldVersion >= mergeLength) return;
await db.transaction((txn) async {
var tempVersion = oldVersion;
while (tempVersion < newVersion) {
if (tempVersion < mergeLength) {
var sqlList = _dbUpgradeList()[tempVersion];
if (null != sqlList && sqlList.length > 0) {
sqlList.forEach((sql) async {
sql = sql.trim();
if (null != sql && sql.length > 0) {
await txn.execute(sql);
}
});
}
}
tempVersion++;
}
});
}
CacheFileStore(
{this.databaseName = 'CacheFileDioDB',
this.cacheFolderName = 'cache_file_dio'}) {
if (this.databaseName == null || this.databaseName.trim().isEmpty) {
throw Exception('databaseName cannot be null or empty');
}
if (this.cacheFolderName == null || this.cacheFolderName.trim().isEmpty) {
throw Exception('cacheFolderName cannot be null or empty');
}
}
@override
Future<CacheObj> getCacheObj(String key, {String subKey}) async {
var db = await _database;
if (null == db) return null;
final cacheDirPath = p.join(
(await getApplicationDocumentsDirectory()).path, cacheFolderName);
final cacheDir = Directory(cacheDirPath);
if (!cacheDir.existsSync()) {
cacheDir.createSync(recursive: true);
}
var where = "$_columnKey=\"$key\"";
if (null != subKey) where += " and $_columnSubKey=\"$subKey\"";
var resultList = await db.query(_tableCacheObject, where: where);
if (null == resultList || resultList.length <= 0) return null;
final cacheFileObj = _CacheFileObj.fromJson(resultList[0]);
final cacheFilePath = p.join(cacheDirPath, cacheFileObj.fileName);
final file = File(cacheFilePath);
var cacheObj = CacheObj.fromJson(cacheFileObj.toJson());
cacheObj.content = file.readAsBytesSync();
return cacheObj;
}
@override
Future<bool> setCacheObj(CacheObj obj) async {
var db = await _database;
if (null == db) return false;
final cacheDirPath = p.join(
(await getApplicationDocumentsDirectory()).path, cacheFolderName);
final cacheDir = Directory(cacheDirPath);
if (!cacheDir.existsSync()) {
cacheDir.createSync(recursive: true);
}
var fileName = 'cache_' + Uuid().v4().replaceAll('-', '');
final cacheFilePath = p.join(cacheDirPath, fileName);
final fileToSave = File(cacheFilePath);
var content = obj.content;
var headers = obj.headers;
fileToSave.writeAsBytesSync(content);
await db.insert(
_tableCacheObject,
{
_columnKey: obj.key,
_columnSubKey: obj.subKey ?? "",
_columnMaxAgeDate: obj.maxAgeDate ?? 0,
_columnMaxStaleDate: obj.maxStaleDate ?? 0,
_columnFileName: fileName,
_columnStatusCode: obj.statusCode,
_columnHeaders: headers
},
conflictAlgorithm: ConflictAlgorithm.replace);
return true;
}
@override
Future<bool> delete(String key, {String subKey}) async {
var db = await _database;
if (null == db) return false;
final cacheDirPath = p.join(
(await getApplicationDocumentsDirectory()).path, cacheFolderName);
final cacheDir = Directory(cacheDirPath);
if (!cacheDir.existsSync()) {
cacheDir.createSync(recursive: true);
}
var where = "$_columnKey=\"$key\"";
if (null != subKey) where += " and $_columnSubKey=\"$subKey\"";
var resultList = await db.query(_tableCacheObject, where: where);
if (null == resultList || resultList.length <= 0) return false;
resultList.forEach((ri) {
final item = _CacheFileObj.fromJson(ri);
final cacheFilePath = p.join(cacheDirPath, item.fileName);
final fileItem = File(cacheFilePath);
if (fileItem.existsSync()) {
fileItem.deleteSync();
}
});
return 0 != await db.delete(_tableCacheObject, where: where);
}
@override
Future<bool> clearExpired() async {
var db = await _database;
return _clearExpired(db);
}
Future<bool> _clearExpired(Database db) async {
if (null == db) return false;
final cacheDirPath = p.join(
(await getApplicationDocumentsDirectory()).path, cacheFolderName);
final cacheDir = Directory(cacheDirPath);
if (!cacheDir.existsSync()) {
cacheDir.createSync(recursive: true);
}
var now = DateTime.now().millisecondsSinceEpoch;
var where1 = "$_columnMaxStaleDate > 0 and $_columnMaxStaleDate < $now";
var where2 = "$_columnMaxStaleDate <= 0 and $_columnMaxAgeDate < $now";
var resultList =
await db.query(_tableCacheObject, where: "( $where1 ) or ( $where2 )");
if (null == resultList || resultList.length <= 0) return false;
resultList.forEach((ri) {
final item = _CacheFileObj.fromJson(ri);
final cacheFilePath = p.join(cacheDirPath, item.fileName);
final fileItem = File(cacheFilePath);
if (fileItem.existsSync()) {
fileItem.deleteSync();
}
});
return 0 !=
await db.delete(_tableCacheObject, where: "( $where1 ) or ( $where2 )");
}
@override
Future<bool> clearAll() async {
var db = await _database;
if (null == db) return false;
final cacheDirPath = p.join(
(await getApplicationDocumentsDirectory()).path, cacheFolderName);
final cacheDir = Directory(cacheDirPath);
if (cacheDir.existsSync()) {
cacheDir.deleteSync(recursive: true);
}
return 0 != await db.delete(_tableCacheObject);
}
}
@JsonSerializable()
class _CacheFileObj {
String key;
String subKey;
@JsonKey(name: "max_age_date")
int maxAgeDate;
@JsonKey(name: "max_stale_date")
int maxStaleDate;
@JsonKey(name: "file_name")
String fileName;
int statusCode;
List<int> headers;
_CacheFileObj._(
this.key, this.subKey, this.fileName, this.statusCode, this.headers);
factory _CacheFileObj(String key, String fileName,
{String subKey = "",
Duration maxAge,
Duration maxStale,
int statusCode = 200,
List<int> headers}) {
return _CacheFileObj._(key, subKey, fileName, statusCode, headers)
..maxAge = maxAge
..maxStale = maxStale;
}
set maxAge(Duration duration) {
if (null != duration) this.maxAgeDate = _convertDuration(duration);
}
set maxStale(Duration duration) {
if (null != duration) this.maxStaleDate = _convertDuration(duration);
}
_convertDuration(Duration duration) =>
DateTime.now().add(duration).millisecondsSinceEpoch;
factory _CacheFileObj.fromJson(Map<String, dynamic> json) =>
_$CacheFileObjFromJson(json);
toJson() => _$CacheFileObjToJson(this);
static _CacheFileObj _$CacheFileObjFromJson(Map json) {
return _CacheFileObj(
json['key'] as String,
json['file_name'] as String,
subKey: json['subKey'] as String,
statusCode: json['statusCode'] as int,
headers: (json['headers'] as List)?.map((e) => e as int)?.toList(),
)
..maxAgeDate = json['max_age_date'] as int
..maxStaleDate = json['max_stale_date'] as int;
}
Map<String, dynamic> _$CacheFileObjToJson(_CacheFileObj instance) =>
<String, dynamic>{
'key': instance.key,
'subKey': instance.subKey,
'max_age_date': instance.maxAgeDate,
'max_stale_date': instance.maxStaleDate,
'file_name': instance.fileName,
'statusCode': instance.statusCode,
'headers': instance.headers,
};
}
Finally, my configuration is:
final dio = Dio();
dio.interceptors.add(DioCacheManager(
CacheConfig(
baseUrl: urlApi,
defaultRequestMethod: 'POST',
diskStore: CacheFileStore(
databaseName: 'MyCache',
cacheFolderName: 'MyCache'),
),
).interceptor);
how can i set configuration to file-cache instead of database?