add http_get_cache
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
// Clear-Site-Data: cache can be used to clear every stored response for a site in the browser cache, so use this with care. Note that this will not affected shared or intermediate caches.
|
||||
class CacheControl {
|
||||
final String value;
|
||||
CacheControl(this.value);
|
||||
|
||||
Iterable<(String, String?)> get parts => value.split(',').map((e) {
|
||||
final p = e.split('=').map((e) => e.trim()).toList();
|
||||
return (p.first, p.length > 1 ? p.last : null);
|
||||
});
|
||||
|
||||
int _getSeconds(String key, [bool allowBool = false]) {
|
||||
final part = parts.firstWhereOrNull((e) {
|
||||
return e.$1.toLowerCase() == key.toLowerCase();
|
||||
});
|
||||
final val = int.tryParse(part?.$2 ?? '');
|
||||
if (val != null) return val;
|
||||
if (allowBool && _getBool(key)) return double.minPositive.toInt();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool _getBool(String key) {
|
||||
return parts.any((e) => e.$1.toLowerCase() == key.toLowerCase());
|
||||
}
|
||||
|
||||
(int, bool) _getMixed(String key) => (_getSeconds(key), _getBool(key));
|
||||
}
|
||||
|
||||
class RequestCacheControl extends CacheControl {
|
||||
RequestCacheControl(super.value);
|
||||
int get maxAge => _getSeconds('max-age');
|
||||
int get maxStale => _getSeconds('max-stale');
|
||||
int get minFresh => _getSeconds('min-fresh');
|
||||
bool get noCache => _getBool('no-cache');
|
||||
bool get noStore => _getBool('no-store');
|
||||
bool get noTransform => _getBool('no-transform');
|
||||
bool get onlyIfCached => _getBool('only-of-cached');
|
||||
(int, bool) get staleIfError => _getMixed('stale-if-error');
|
||||
}
|
||||
|
||||
class ResponseCacheControl extends CacheControl {
|
||||
ResponseCacheControl(super.value);
|
||||
int get maxAge => _getSeconds('max-age');
|
||||
int get sMaxAge => _getSeconds('s-maxage');
|
||||
bool get noCache => _getBool('no-cache');
|
||||
bool get noStore => _getBool('no-store');
|
||||
bool get noTransform => _getBool('no-transform');
|
||||
bool get mustRevalidate => _getBool('must-revalidate');
|
||||
bool get mustUnderstand => _getBool('must-understand');
|
||||
bool get private => _getBool('private');
|
||||
bool get public => _getBool('public');
|
||||
bool get immutable => _getBool('immutable');
|
||||
(int, bool) get staleWhileRevalidate => _getMixed('stale-while-revalidate');
|
||||
(int, bool) get staleIfError => _getMixed('stale-if-error');
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'http_client_factory.dart'
|
||||
if (dart.library.js_interop) 'http_client_factory_web.dart';
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
|
||||
import '../database/database.dart';
|
||||
import '../http_get_cache.dart';
|
||||
import '../store/base.dart';
|
||||
import '../store/sqlite.dart';
|
||||
|
||||
Client httpClient({
|
||||
String userAgent = 'Example',
|
||||
Client? innerClient,
|
||||
HttpCacheStore? store,
|
||||
}) {
|
||||
final db = HttpGetCacheDatabase.instance;
|
||||
assert(
|
||||
db != null,
|
||||
'Database not initialized "HttpGetCacheDatabase.instance = ..."',
|
||||
);
|
||||
return HttpGetCache(
|
||||
() {
|
||||
if (innerClient != null) return innerClient;
|
||||
final client = HttpClient();
|
||||
client.userAgent = userAgent;
|
||||
return IOClient(client);
|
||||
}(),
|
||||
store ?? SqliteHttpCacheStore(db!),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:fetch_client/fetch_client.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../http_get_cache.dart';
|
||||
import '../store/base.dart';
|
||||
|
||||
Client httpClient({
|
||||
String userAgent = 'Example',
|
||||
Client? innerClient,
|
||||
HttpCacheStore? store,
|
||||
}) {
|
||||
return HttpGetCache(
|
||||
() {
|
||||
if (innerClient != null) return innerClient;
|
||||
return FetchClient(
|
||||
mode: RequestMode.cors,
|
||||
cache: RequestCache.byDefault,
|
||||
// streamRequests: true,
|
||||
);
|
||||
}(),
|
||||
store ?? HttpCacheStore(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
const bool kReleaseMode = bool.fromEnvironment('dart.vm.product');
|
||||
const bool kProfileMode = bool.fromEnvironment('dart.vm.profile');
|
||||
const bool kDebugMode = !kReleaseMode && !kProfileMode;
|
||||
bool kIsWeb = const bool.fromEnvironment('dart.library.js_util');
|
||||
@@ -0,0 +1 @@
|
||||
export 'connect.web.dart' if (dart.library.ffi) 'connect.io.dart';
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
||||
LazyDatabase createExecutor(DatabaseSettings settings) {
|
||||
return LazyDatabase(() async {
|
||||
final dbFile = File(p.join(settings.dbPath, settings.name));
|
||||
if (!dbFile.existsSync()) {
|
||||
await dbFile.create(recursive: true);
|
||||
if (settings.initializeDatabase != null) {
|
||||
final bytes = await settings.initializeDatabase!();
|
||||
if (bytes != null) {
|
||||
await dbFile.writeAsBytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NativeDatabase.createInBackground(
|
||||
dbFile,
|
||||
setup: settings.setup,
|
||||
logStatements: settings.logStatements,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:drift/native.dart';
|
||||
|
||||
import '../database.dart';
|
||||
import '../settings.dart';
|
||||
|
||||
HttpGetCacheDatabase createMemoryDatabase(DatabaseSettings settings) {
|
||||
final e = NativeDatabase.memory(
|
||||
logStatements: settings.logStatements,
|
||||
setup: settings.setup,
|
||||
);
|
||||
return HttpGetCacheDatabase(settings, e);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/wasm.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
||||
QueryExecutor createExecutor(DatabaseSettings settings) {
|
||||
return DatabaseConnection.delayed(Future(() async {
|
||||
final db = await WasmDatabase.open(
|
||||
databaseName: settings.name.split('.').first,
|
||||
sqlite3Uri: Uri.parse(settings.sqlite3Uri),
|
||||
driftWorkerUri: Uri.parse(settings.driftWorkerUri),
|
||||
localSetup: settings.setup,
|
||||
initializeDatabase: settings.initializeDatabase,
|
||||
);
|
||||
|
||||
if (db.missingFeatures.isNotEmpty) {
|
||||
log(
|
||||
'Using ${db.chosenImplementation} due to unsupported '
|
||||
'browser features: ${db.missingFeatures}',
|
||||
);
|
||||
}
|
||||
|
||||
return db.resolvedExecutor;
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(include: {
|
||||
"sql/http_cache.drift",
|
||||
})
|
||||
class HttpGetCacheDatabase extends _$HttpGetCacheDatabase {
|
||||
final DatabaseSettings settings;
|
||||
|
||||
HttpGetCacheDatabase(this.settings, super.e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 2;
|
||||
|
||||
static HttpGetCacheDatabase? instance;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
return MigrationStrategy(
|
||||
onCreate: (Migrator m) async {
|
||||
await m.createAll();
|
||||
},
|
||||
onUpgrade: (Migrator m, int from, int to) async {
|
||||
if (from < 1) {
|
||||
await m.createAll();
|
||||
}
|
||||
if (from < 2) {
|
||||
await m.addColumn(httpCache, httpCache.requestHeaders);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,865 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class HttpCache extends Table with TableInfo<HttpCache, HttpCacheData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
HttpCache(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT');
|
||||
static const VerificationMeta _urlMeta = const VerificationMeta('url');
|
||||
late final GeneratedColumn<String> url = GeneratedColumn<String>(
|
||||
'url', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _headersMeta =
|
||||
const VerificationMeta('headers');
|
||||
late final GeneratedColumn<String> headers = GeneratedColumn<String>(
|
||||
'headers', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _requestHeadersMeta =
|
||||
const VerificationMeta('requestHeaders');
|
||||
late final GeneratedColumn<String> requestHeaders = GeneratedColumn<String>(
|
||||
'request_headers', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _bodyMeta = const VerificationMeta('body');
|
||||
late final GeneratedColumn<String> body = GeneratedColumn<String>(
|
||||
'body', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _dateMeta = const VerificationMeta('date');
|
||||
late final GeneratedColumn<int> date = GeneratedColumn<int>(
|
||||
'date', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _maxAgeMeta = const VerificationMeta('maxAge');
|
||||
late final GeneratedColumn<int> maxAge = GeneratedColumn<int>(
|
||||
'max_age', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _staleWhileRevalidateMeta =
|
||||
const VerificationMeta('staleWhileRevalidate');
|
||||
late final GeneratedColumn<int> staleWhileRevalidate = GeneratedColumn<int>(
|
||||
'stale_while_revalidate', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _staleIfErrorMeta =
|
||||
const VerificationMeta('staleIfError');
|
||||
late final GeneratedColumn<int> staleIfError = GeneratedColumn<int>(
|
||||
'stale_if_error', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const VerificationMeta _immutableMeta =
|
||||
const VerificationMeta('immutable');
|
||||
late final GeneratedColumn<bool> immutable = GeneratedColumn<bool>(
|
||||
'immutable', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
url,
|
||||
headers,
|
||||
requestHeaders,
|
||||
body,
|
||||
date,
|
||||
maxAge,
|
||||
staleWhileRevalidate,
|
||||
staleIfError,
|
||||
immutable
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'http_cache';
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<HttpCacheData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('url')) {
|
||||
context.handle(
|
||||
_urlMeta, url.isAcceptableOrUnknown(data['url']!, _urlMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_urlMeta);
|
||||
}
|
||||
if (data.containsKey('headers')) {
|
||||
context.handle(_headersMeta,
|
||||
headers.isAcceptableOrUnknown(data['headers']!, _headersMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_headersMeta);
|
||||
}
|
||||
if (data.containsKey('request_headers')) {
|
||||
context.handle(
|
||||
_requestHeadersMeta,
|
||||
requestHeaders.isAcceptableOrUnknown(
|
||||
data['request_headers']!, _requestHeadersMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_requestHeadersMeta);
|
||||
}
|
||||
if (data.containsKey('body')) {
|
||||
context.handle(
|
||||
_bodyMeta, body.isAcceptableOrUnknown(data['body']!, _bodyMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_bodyMeta);
|
||||
}
|
||||
if (data.containsKey('date')) {
|
||||
context.handle(
|
||||
_dateMeta, date.isAcceptableOrUnknown(data['date']!, _dateMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_dateMeta);
|
||||
}
|
||||
if (data.containsKey('max_age')) {
|
||||
context.handle(_maxAgeMeta,
|
||||
maxAge.isAcceptableOrUnknown(data['max_age']!, _maxAgeMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_maxAgeMeta);
|
||||
}
|
||||
if (data.containsKey('stale_while_revalidate')) {
|
||||
context.handle(
|
||||
_staleWhileRevalidateMeta,
|
||||
staleWhileRevalidate.isAcceptableOrUnknown(
|
||||
data['stale_while_revalidate']!, _staleWhileRevalidateMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_staleWhileRevalidateMeta);
|
||||
}
|
||||
if (data.containsKey('stale_if_error')) {
|
||||
context.handle(
|
||||
_staleIfErrorMeta,
|
||||
staleIfError.isAcceptableOrUnknown(
|
||||
data['stale_if_error']!, _staleIfErrorMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_staleIfErrorMeta);
|
||||
}
|
||||
if (data.containsKey('immutable')) {
|
||||
context.handle(_immutableMeta,
|
||||
immutable.isAcceptableOrUnknown(data['immutable']!, _immutableMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_immutableMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
List<Set<GeneratedColumn>> get uniqueKeys => [
|
||||
{url, requestHeaders},
|
||||
];
|
||||
@override
|
||||
HttpCacheData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return HttpCacheData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||
url: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}url'])!,
|
||||
headers: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}headers'])!,
|
||||
requestHeaders: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string, data['${effectivePrefix}request_headers'])!,
|
||||
body: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}body'])!,
|
||||
date: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}date'])!,
|
||||
maxAge: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}max_age'])!,
|
||||
staleWhileRevalidate: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int, data['${effectivePrefix}stale_while_revalidate'])!,
|
||||
staleIfError: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}stale_if_error'])!,
|
||||
immutable: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}immutable'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
HttpCache createAlias(String alias) {
|
||||
return HttpCache(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const ['UNIQUE(url, request_headers)'];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class HttpCacheData extends DataClass implements Insertable<HttpCacheData> {
|
||||
int id;
|
||||
String url;
|
||||
String headers;
|
||||
String requestHeaders;
|
||||
String body;
|
||||
int date;
|
||||
int maxAge;
|
||||
int staleWhileRevalidate;
|
||||
int staleIfError;
|
||||
bool immutable;
|
||||
HttpCacheData(
|
||||
{required this.id,
|
||||
required this.url,
|
||||
required this.headers,
|
||||
required this.requestHeaders,
|
||||
required this.body,
|
||||
required this.date,
|
||||
required this.maxAge,
|
||||
required this.staleWhileRevalidate,
|
||||
required this.staleIfError,
|
||||
required this.immutable});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['url'] = Variable<String>(url);
|
||||
map['headers'] = Variable<String>(headers);
|
||||
map['request_headers'] = Variable<String>(requestHeaders);
|
||||
map['body'] = Variable<String>(body);
|
||||
map['date'] = Variable<int>(date);
|
||||
map['max_age'] = Variable<int>(maxAge);
|
||||
map['stale_while_revalidate'] = Variable<int>(staleWhileRevalidate);
|
||||
map['stale_if_error'] = Variable<int>(staleIfError);
|
||||
map['immutable'] = Variable<bool>(immutable);
|
||||
return map;
|
||||
}
|
||||
|
||||
HttpCacheCompanion toCompanion(bool nullToAbsent) {
|
||||
return HttpCacheCompanion(
|
||||
id: Value(id),
|
||||
url: Value(url),
|
||||
headers: Value(headers),
|
||||
requestHeaders: Value(requestHeaders),
|
||||
body: Value(body),
|
||||
date: Value(date),
|
||||
maxAge: Value(maxAge),
|
||||
staleWhileRevalidate: Value(staleWhileRevalidate),
|
||||
staleIfError: Value(staleIfError),
|
||||
immutable: Value(immutable),
|
||||
);
|
||||
}
|
||||
|
||||
factory HttpCacheData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return HttpCacheData(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
url: serializer.fromJson<String>(json['url']),
|
||||
headers: serializer.fromJson<String>(json['headers']),
|
||||
requestHeaders: serializer.fromJson<String>(json['request_headers']),
|
||||
body: serializer.fromJson<String>(json['body']),
|
||||
date: serializer.fromJson<int>(json['date']),
|
||||
maxAge: serializer.fromJson<int>(json['max_age']),
|
||||
staleWhileRevalidate:
|
||||
serializer.fromJson<int>(json['stale_while_revalidate']),
|
||||
staleIfError: serializer.fromJson<int>(json['stale_if_error']),
|
||||
immutable: serializer.fromJson<bool>(json['immutable']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'url': serializer.toJson<String>(url),
|
||||
'headers': serializer.toJson<String>(headers),
|
||||
'request_headers': serializer.toJson<String>(requestHeaders),
|
||||
'body': serializer.toJson<String>(body),
|
||||
'date': serializer.toJson<int>(date),
|
||||
'max_age': serializer.toJson<int>(maxAge),
|
||||
'stale_while_revalidate': serializer.toJson<int>(staleWhileRevalidate),
|
||||
'stale_if_error': serializer.toJson<int>(staleIfError),
|
||||
'immutable': serializer.toJson<bool>(immutable),
|
||||
};
|
||||
}
|
||||
|
||||
HttpCacheData copyWith(
|
||||
{int? id,
|
||||
String? url,
|
||||
String? headers,
|
||||
String? requestHeaders,
|
||||
String? body,
|
||||
int? date,
|
||||
int? maxAge,
|
||||
int? staleWhileRevalidate,
|
||||
int? staleIfError,
|
||||
bool? immutable}) =>
|
||||
HttpCacheData(
|
||||
id: id ?? this.id,
|
||||
url: url ?? this.url,
|
||||
headers: headers ?? this.headers,
|
||||
requestHeaders: requestHeaders ?? this.requestHeaders,
|
||||
body: body ?? this.body,
|
||||
date: date ?? this.date,
|
||||
maxAge: maxAge ?? this.maxAge,
|
||||
staleWhileRevalidate: staleWhileRevalidate ?? this.staleWhileRevalidate,
|
||||
staleIfError: staleIfError ?? this.staleIfError,
|
||||
immutable: immutable ?? this.immutable,
|
||||
);
|
||||
HttpCacheData copyWithCompanion(HttpCacheCompanion data) {
|
||||
return HttpCacheData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
url: data.url.present ? data.url.value : this.url,
|
||||
headers: data.headers.present ? data.headers.value : this.headers,
|
||||
requestHeaders: data.requestHeaders.present
|
||||
? data.requestHeaders.value
|
||||
: this.requestHeaders,
|
||||
body: data.body.present ? data.body.value : this.body,
|
||||
date: data.date.present ? data.date.value : this.date,
|
||||
maxAge: data.maxAge.present ? data.maxAge.value : this.maxAge,
|
||||
staleWhileRevalidate: data.staleWhileRevalidate.present
|
||||
? data.staleWhileRevalidate.value
|
||||
: this.staleWhileRevalidate,
|
||||
staleIfError: data.staleIfError.present
|
||||
? data.staleIfError.value
|
||||
: this.staleIfError,
|
||||
immutable: data.immutable.present ? data.immutable.value : this.immutable,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('HttpCacheData(')
|
||||
..write('id: $id, ')
|
||||
..write('url: $url, ')
|
||||
..write('headers: $headers, ')
|
||||
..write('requestHeaders: $requestHeaders, ')
|
||||
..write('body: $body, ')
|
||||
..write('date: $date, ')
|
||||
..write('maxAge: $maxAge, ')
|
||||
..write('staleWhileRevalidate: $staleWhileRevalidate, ')
|
||||
..write('staleIfError: $staleIfError, ')
|
||||
..write('immutable: $immutable')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, url, headers, requestHeaders, body, date,
|
||||
maxAge, staleWhileRevalidate, staleIfError, immutable);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is HttpCacheData &&
|
||||
other.id == this.id &&
|
||||
other.url == this.url &&
|
||||
other.headers == this.headers &&
|
||||
other.requestHeaders == this.requestHeaders &&
|
||||
other.body == this.body &&
|
||||
other.date == this.date &&
|
||||
other.maxAge == this.maxAge &&
|
||||
other.staleWhileRevalidate == this.staleWhileRevalidate &&
|
||||
other.staleIfError == this.staleIfError &&
|
||||
other.immutable == this.immutable);
|
||||
}
|
||||
|
||||
class HttpCacheCompanion extends UpdateCompanion<HttpCacheData> {
|
||||
Value<int> id;
|
||||
Value<String> url;
|
||||
Value<String> headers;
|
||||
Value<String> requestHeaders;
|
||||
Value<String> body;
|
||||
Value<int> date;
|
||||
Value<int> maxAge;
|
||||
Value<int> staleWhileRevalidate;
|
||||
Value<int> staleIfError;
|
||||
Value<bool> immutable;
|
||||
HttpCacheCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.url = const Value.absent(),
|
||||
this.headers = const Value.absent(),
|
||||
this.requestHeaders = const Value.absent(),
|
||||
this.body = const Value.absent(),
|
||||
this.date = const Value.absent(),
|
||||
this.maxAge = const Value.absent(),
|
||||
this.staleWhileRevalidate = const Value.absent(),
|
||||
this.staleIfError = const Value.absent(),
|
||||
this.immutable = const Value.absent(),
|
||||
});
|
||||
HttpCacheCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required String url,
|
||||
required String headers,
|
||||
required String requestHeaders,
|
||||
required String body,
|
||||
required int date,
|
||||
required int maxAge,
|
||||
required int staleWhileRevalidate,
|
||||
required int staleIfError,
|
||||
required bool immutable,
|
||||
}) : url = Value(url),
|
||||
headers = Value(headers),
|
||||
requestHeaders = Value(requestHeaders),
|
||||
body = Value(body),
|
||||
date = Value(date),
|
||||
maxAge = Value(maxAge),
|
||||
staleWhileRevalidate = Value(staleWhileRevalidate),
|
||||
staleIfError = Value(staleIfError),
|
||||
immutable = Value(immutable);
|
||||
static Insertable<HttpCacheData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? url,
|
||||
Expression<String>? headers,
|
||||
Expression<String>? requestHeaders,
|
||||
Expression<String>? body,
|
||||
Expression<int>? date,
|
||||
Expression<int>? maxAge,
|
||||
Expression<int>? staleWhileRevalidate,
|
||||
Expression<int>? staleIfError,
|
||||
Expression<bool>? immutable,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (url != null) 'url': url,
|
||||
if (headers != null) 'headers': headers,
|
||||
if (requestHeaders != null) 'request_headers': requestHeaders,
|
||||
if (body != null) 'body': body,
|
||||
if (date != null) 'date': date,
|
||||
if (maxAge != null) 'max_age': maxAge,
|
||||
if (staleWhileRevalidate != null)
|
||||
'stale_while_revalidate': staleWhileRevalidate,
|
||||
if (staleIfError != null) 'stale_if_error': staleIfError,
|
||||
if (immutable != null) 'immutable': immutable,
|
||||
});
|
||||
}
|
||||
|
||||
HttpCacheCompanion copyWith(
|
||||
{Value<int>? id,
|
||||
Value<String>? url,
|
||||
Value<String>? headers,
|
||||
Value<String>? requestHeaders,
|
||||
Value<String>? body,
|
||||
Value<int>? date,
|
||||
Value<int>? maxAge,
|
||||
Value<int>? staleWhileRevalidate,
|
||||
Value<int>? staleIfError,
|
||||
Value<bool>? immutable}) {
|
||||
return HttpCacheCompanion(
|
||||
id: id ?? this.id,
|
||||
url: url ?? this.url,
|
||||
headers: headers ?? this.headers,
|
||||
requestHeaders: requestHeaders ?? this.requestHeaders,
|
||||
body: body ?? this.body,
|
||||
date: date ?? this.date,
|
||||
maxAge: maxAge ?? this.maxAge,
|
||||
staleWhileRevalidate: staleWhileRevalidate ?? this.staleWhileRevalidate,
|
||||
staleIfError: staleIfError ?? this.staleIfError,
|
||||
immutable: immutable ?? this.immutable,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (url.present) {
|
||||
map['url'] = Variable<String>(url.value);
|
||||
}
|
||||
if (headers.present) {
|
||||
map['headers'] = Variable<String>(headers.value);
|
||||
}
|
||||
if (requestHeaders.present) {
|
||||
map['request_headers'] = Variable<String>(requestHeaders.value);
|
||||
}
|
||||
if (body.present) {
|
||||
map['body'] = Variable<String>(body.value);
|
||||
}
|
||||
if (date.present) {
|
||||
map['date'] = Variable<int>(date.value);
|
||||
}
|
||||
if (maxAge.present) {
|
||||
map['max_age'] = Variable<int>(maxAge.value);
|
||||
}
|
||||
if (staleWhileRevalidate.present) {
|
||||
map['stale_while_revalidate'] = Variable<int>(staleWhileRevalidate.value);
|
||||
}
|
||||
if (staleIfError.present) {
|
||||
map['stale_if_error'] = Variable<int>(staleIfError.value);
|
||||
}
|
||||
if (immutable.present) {
|
||||
map['immutable'] = Variable<bool>(immutable.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('HttpCacheCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('url: $url, ')
|
||||
..write('headers: $headers, ')
|
||||
..write('requestHeaders: $requestHeaders, ')
|
||||
..write('body: $body, ')
|
||||
..write('date: $date, ')
|
||||
..write('maxAge: $maxAge, ')
|
||||
..write('staleWhileRevalidate: $staleWhileRevalidate, ')
|
||||
..write('staleIfError: $staleIfError, ')
|
||||
..write('immutable: $immutable')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$HttpGetCacheDatabase extends GeneratedDatabase {
|
||||
_$HttpGetCacheDatabase(QueryExecutor e) : super(e);
|
||||
$HttpGetCacheDatabaseManager get managers =>
|
||||
$HttpGetCacheDatabaseManager(this);
|
||||
late final HttpCache httpCache = HttpCache(this);
|
||||
late final Index httpCacheIdx = Index('http_cache_idx',
|
||||
'CREATE UNIQUE INDEX http_cache_idx ON http_cache (url, request_headers, date, max_age, stale_if_error, stale_while_revalidate)');
|
||||
Future<List<HttpCacheData>> setHttpCache(
|
||||
{required String url,
|
||||
required String headers,
|
||||
required String requestHeaders,
|
||||
required String body,
|
||||
required int date,
|
||||
required int maxAge,
|
||||
required int staleWhileRevalidate,
|
||||
required int staleIfError,
|
||||
required bool immutable}) {
|
||||
return customWriteReturning(
|
||||
'INSERT OR REPLACE INTO http_cache (url, headers, request_headers, body, date, max_age, stale_while_revalidate, stale_if_error, immutable) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) RETURNING *',
|
||||
variables: [
|
||||
Variable<String>(url),
|
||||
Variable<String>(headers),
|
||||
Variable<String>(requestHeaders),
|
||||
Variable<String>(body),
|
||||
Variable<int>(date),
|
||||
Variable<int>(maxAge),
|
||||
Variable<int>(staleWhileRevalidate),
|
||||
Variable<int>(staleIfError),
|
||||
Variable<bool>(immutable)
|
||||
],
|
||||
updates: {
|
||||
httpCache
|
||||
}).then((rows) => Future.wait(rows.map(httpCache.mapFromRow)));
|
||||
}
|
||||
|
||||
Selectable<HttpCacheData> getHttpCacheByUrl({required String url}) {
|
||||
return customSelect('SELECT * FROM http_cache WHERE url = ?1', variables: [
|
||||
Variable<String>(url)
|
||||
], readsFrom: {
|
||||
httpCache,
|
||||
}).asyncMap(httpCache.mapFromRow);
|
||||
}
|
||||
|
||||
Selectable<HttpCacheData> getHttpCache({required String url}) {
|
||||
return customSelect('SELECT * FROM http_cache WHERE url = ?1', variables: [
|
||||
Variable<String>(url)
|
||||
], readsFrom: {
|
||||
httpCache,
|
||||
}).asyncMap(httpCache.mapFromRow);
|
||||
}
|
||||
|
||||
Future<int> deleteHttpCache({required String url}) {
|
||||
return customUpdate(
|
||||
'DELETE FROM http_cache WHERE url = ?1',
|
||||
variables: [Variable<String>(url)],
|
||||
updates: {httpCache},
|
||||
updateKind: UpdateKind.delete,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteHttpCacheStale() {
|
||||
return customUpdate(
|
||||
'DELETE FROM http_cache WHERE unixepoch() >(date +(max_age * 1000)+(MAX(stale_while_revalidate, stale_if_error) * 1000))',
|
||||
variables: [],
|
||||
updates: {httpCache},
|
||||
updateKind: UpdateKind.delete,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteHttpCacheStaleWithoutRevalidate() {
|
||||
return customUpdate(
|
||||
'DELETE FROM http_cache WHERE unixepoch() >(date +(max_age * 1000))',
|
||||
variables: [],
|
||||
updates: {httpCache},
|
||||
updateKind: UpdateKind.delete,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [httpCache, httpCacheIdx];
|
||||
@override
|
||||
DriftDatabaseOptions get options =>
|
||||
const DriftDatabaseOptions(storeDateTimeAsText: true);
|
||||
}
|
||||
|
||||
typedef $HttpCacheCreateCompanionBuilder = HttpCacheCompanion Function({
|
||||
Value<int> id,
|
||||
required String url,
|
||||
required String headers,
|
||||
required String requestHeaders,
|
||||
required String body,
|
||||
required int date,
|
||||
required int maxAge,
|
||||
required int staleWhileRevalidate,
|
||||
required int staleIfError,
|
||||
required bool immutable,
|
||||
});
|
||||
typedef $HttpCacheUpdateCompanionBuilder = HttpCacheCompanion Function({
|
||||
Value<int> id,
|
||||
Value<String> url,
|
||||
Value<String> headers,
|
||||
Value<String> requestHeaders,
|
||||
Value<String> body,
|
||||
Value<int> date,
|
||||
Value<int> maxAge,
|
||||
Value<int> staleWhileRevalidate,
|
||||
Value<int> staleIfError,
|
||||
Value<bool> immutable,
|
||||
});
|
||||
|
||||
class $HttpCacheFilterComposer
|
||||
extends Composer<_$HttpGetCacheDatabase, HttpCache> {
|
||||
$HttpCacheFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get url => $composableBuilder(
|
||||
column: $table.url, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get headers => $composableBuilder(
|
||||
column: $table.headers, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get requestHeaders => $composableBuilder(
|
||||
column: $table.requestHeaders,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get body => $composableBuilder(
|
||||
column: $table.body, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get date => $composableBuilder(
|
||||
column: $table.date, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get maxAge => $composableBuilder(
|
||||
column: $table.maxAge, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get staleWhileRevalidate => $composableBuilder(
|
||||
column: $table.staleWhileRevalidate,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get staleIfError => $composableBuilder(
|
||||
column: $table.staleIfError, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get immutable => $composableBuilder(
|
||||
column: $table.immutable, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $HttpCacheOrderingComposer
|
||||
extends Composer<_$HttpGetCacheDatabase, HttpCache> {
|
||||
$HttpCacheOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get url => $composableBuilder(
|
||||
column: $table.url, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get headers => $composableBuilder(
|
||||
column: $table.headers, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get requestHeaders => $composableBuilder(
|
||||
column: $table.requestHeaders,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get body => $composableBuilder(
|
||||
column: $table.body, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get date => $composableBuilder(
|
||||
column: $table.date, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get maxAge => $composableBuilder(
|
||||
column: $table.maxAge, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get staleWhileRevalidate => $composableBuilder(
|
||||
column: $table.staleWhileRevalidate,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get staleIfError => $composableBuilder(
|
||||
column: $table.staleIfError,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get immutable => $composableBuilder(
|
||||
column: $table.immutable, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $HttpCacheAnnotationComposer
|
||||
extends Composer<_$HttpGetCacheDatabase, HttpCache> {
|
||||
$HttpCacheAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get url =>
|
||||
$composableBuilder(column: $table.url, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get headers =>
|
||||
$composableBuilder(column: $table.headers, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get requestHeaders => $composableBuilder(
|
||||
column: $table.requestHeaders, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get body =>
|
||||
$composableBuilder(column: $table.body, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get date =>
|
||||
$composableBuilder(column: $table.date, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get maxAge =>
|
||||
$composableBuilder(column: $table.maxAge, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get staleWhileRevalidate => $composableBuilder(
|
||||
column: $table.staleWhileRevalidate, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get staleIfError => $composableBuilder(
|
||||
column: $table.staleIfError, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get immutable =>
|
||||
$composableBuilder(column: $table.immutable, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $HttpCacheTableManager extends RootTableManager<
|
||||
_$HttpGetCacheDatabase,
|
||||
HttpCache,
|
||||
HttpCacheData,
|
||||
$HttpCacheFilterComposer,
|
||||
$HttpCacheOrderingComposer,
|
||||
$HttpCacheAnnotationComposer,
|
||||
$HttpCacheCreateCompanionBuilder,
|
||||
$HttpCacheUpdateCompanionBuilder,
|
||||
(
|
||||
HttpCacheData,
|
||||
BaseReferences<_$HttpGetCacheDatabase, HttpCache, HttpCacheData>
|
||||
),
|
||||
HttpCacheData,
|
||||
PrefetchHooks Function()> {
|
||||
$HttpCacheTableManager(_$HttpGetCacheDatabase db, HttpCache table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$HttpCacheFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$HttpCacheOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$HttpCacheAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<String> url = const Value.absent(),
|
||||
Value<String> headers = const Value.absent(),
|
||||
Value<String> requestHeaders = const Value.absent(),
|
||||
Value<String> body = const Value.absent(),
|
||||
Value<int> date = const Value.absent(),
|
||||
Value<int> maxAge = const Value.absent(),
|
||||
Value<int> staleWhileRevalidate = const Value.absent(),
|
||||
Value<int> staleIfError = const Value.absent(),
|
||||
Value<bool> immutable = const Value.absent(),
|
||||
}) =>
|
||||
HttpCacheCompanion(
|
||||
id: id,
|
||||
url: url,
|
||||
headers: headers,
|
||||
requestHeaders: requestHeaders,
|
||||
body: body,
|
||||
date: date,
|
||||
maxAge: maxAge,
|
||||
staleWhileRevalidate: staleWhileRevalidate,
|
||||
staleIfError: staleIfError,
|
||||
immutable: immutable,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
required String url,
|
||||
required String headers,
|
||||
required String requestHeaders,
|
||||
required String body,
|
||||
required int date,
|
||||
required int maxAge,
|
||||
required int staleWhileRevalidate,
|
||||
required int staleIfError,
|
||||
required bool immutable,
|
||||
}) =>
|
||||
HttpCacheCompanion.insert(
|
||||
id: id,
|
||||
url: url,
|
||||
headers: headers,
|
||||
requestHeaders: requestHeaders,
|
||||
body: body,
|
||||
date: date,
|
||||
maxAge: maxAge,
|
||||
staleWhileRevalidate: staleWhileRevalidate,
|
||||
staleIfError: staleIfError,
|
||||
immutable: immutable,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $HttpCacheProcessedTableManager = ProcessedTableManager<
|
||||
_$HttpGetCacheDatabase,
|
||||
HttpCache,
|
||||
HttpCacheData,
|
||||
$HttpCacheFilterComposer,
|
||||
$HttpCacheOrderingComposer,
|
||||
$HttpCacheAnnotationComposer,
|
||||
$HttpCacheCreateCompanionBuilder,
|
||||
$HttpCacheUpdateCompanionBuilder,
|
||||
(
|
||||
HttpCacheData,
|
||||
BaseReferences<_$HttpGetCacheDatabase, HttpCache, HttpCacheData>
|
||||
),
|
||||
HttpCacheData,
|
||||
PrefetchHooks Function()>;
|
||||
|
||||
class $HttpGetCacheDatabaseManager {
|
||||
final _$HttpGetCacheDatabase _db;
|
||||
$HttpGetCacheDatabaseManager(this._db);
|
||||
$HttpCacheTableManager get httpCache =>
|
||||
$HttpCacheTableManager(_db, _db.httpCache);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:sqlite3/common.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
|
||||
class DatabaseSettings {
|
||||
final String name;
|
||||
final bool logStatements;
|
||||
final String driftWorkerUri;
|
||||
final String sqlite3Uri;
|
||||
final String databaseDir;
|
||||
final String cacheDir;
|
||||
final void Function(CommonDatabase)? setup;
|
||||
final Future<Uint8List?> Function()? initializeDatabase;
|
||||
|
||||
const DatabaseSettings(
|
||||
this.name, {
|
||||
this.logStatements = false,
|
||||
this.driftWorkerUri = 'drift_worker.js',
|
||||
this.sqlite3Uri = kDebugMode ? 'sqlite3.debug.wasm' : 'sqlite3.wasm',
|
||||
this.setup,
|
||||
this.initializeDatabase,
|
||||
required this.databaseDir,
|
||||
required this.cacheDir,
|
||||
});
|
||||
|
||||
String get dbPath => p.join(databaseDir, name);
|
||||
String get cachePath => p.join(cacheDir, 'cache');
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
CREATE TABLE http_cache (
|
||||
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
url TEXT NOT NULL,
|
||||
headers TEXT NOT NULL,
|
||||
request_headers TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
date INT NOT NULL,
|
||||
max_age INT NOT NULL,
|
||||
stale_while_revalidate INT NOT NULL,
|
||||
stale_if_error INT NOT NULL,
|
||||
immutable BOOLEAN NOT NULL,
|
||||
UNIQUE(url, request_headers)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX http_cache_idx ON http_cache (
|
||||
url,
|
||||
request_headers,
|
||||
date,
|
||||
max_age,
|
||||
stale_if_error,
|
||||
stale_while_revalidate
|
||||
);
|
||||
|
||||
setHttpCache:
|
||||
INSERT OR REPLACE
|
||||
INTO http_cache (
|
||||
url,
|
||||
headers,
|
||||
request_headers,
|
||||
body,
|
||||
date,
|
||||
max_age,
|
||||
stale_while_revalidate,
|
||||
stale_if_error,
|
||||
immutable
|
||||
)
|
||||
VALUES (
|
||||
:url,
|
||||
:headers,
|
||||
:requestHeaders,
|
||||
:body,
|
||||
:date,
|
||||
:maxAge,
|
||||
:staleWhileRevalidate,
|
||||
:staleIfError,
|
||||
:immutable
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
getHttpCacheByUrl:
|
||||
SELECT * FROM http_cache
|
||||
WHERE url = :url;
|
||||
|
||||
getHttpCache:
|
||||
SELECT * FROM http_cache
|
||||
WHERE url = :url;
|
||||
|
||||
deleteHttpCache:
|
||||
DELETE FROM http_cache
|
||||
WHERE url = :url;
|
||||
|
||||
deleteHttpCacheStale:
|
||||
DELETE FROM http_cache
|
||||
WHERE unixepoch() > (date + (max_age * 1000) + (MAX(stale_while_revalidate, stale_if_error) * 1000));
|
||||
|
||||
deleteHttpCacheStaleWithoutRevalidate:
|
||||
DELETE FROM http_cache
|
||||
WHERE unixepoch() > (date + (max_age * 1000));
|
||||
@@ -0,0 +1,48 @@
|
||||
/// Parse a date from a request/response header value into
|
||||
/// a valid dart DateTime.
|
||||
///
|
||||
/// Example: Wed, 17 Jul 2024 23:19:37 GMT
|
||||
class DateParser {
|
||||
const DateParser();
|
||||
DateTime? call(String value) {
|
||||
if (value.isEmpty) return null;
|
||||
final d = DateTime.tryParse(value);
|
||||
if (d != null) return d;
|
||||
final [
|
||||
_,
|
||||
dayOfMonth,
|
||||
monthName,
|
||||
yearName,
|
||||
time,
|
||||
_,
|
||||
] = value.trim().split(' ');
|
||||
var year = int.parse(yearName).toString();
|
||||
var month = (switch (monthName.toLowerCase()) {
|
||||
'jan' => DateTime.january,
|
||||
'feb' => DateTime.february,
|
||||
'mar' => DateTime.march,
|
||||
'apr' => DateTime.april,
|
||||
'may' => DateTime.may,
|
||||
'jun' => DateTime.june,
|
||||
'jul' => DateTime.july,
|
||||
'aug' => DateTime.august,
|
||||
'sep' => DateTime.september,
|
||||
'oct' => DateTime.october,
|
||||
'nov' => DateTime.november,
|
||||
'dec' => DateTime.december,
|
||||
(_) => -1,
|
||||
})
|
||||
.toString()
|
||||
.padLeft(2, '0');
|
||||
var day = int.parse(dayOfMonth).toString().padLeft(2, '0');
|
||||
final str = '$year-$month-$day $time';
|
||||
return DateTime.tryParse(str);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime getDate() => DateTime.now();
|
||||
int getDateInt() => getDate().toSeconds();
|
||||
|
||||
extension DateTimeUtils on DateTime {
|
||||
int toSeconds() => Duration(milliseconds: millisecondsSinceEpoch).inSeconds;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:cachecontrol/cachecontrol.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
const _cacheControlHeader = 'cache-control';
|
||||
|
||||
extension CacheControlRequest on BaseRequest {
|
||||
RequestCacheControl get cacheControl {
|
||||
final raw = headers[_cacheControlHeader] ?? '';
|
||||
return RequestCacheControl.parse(raw);
|
||||
}
|
||||
|
||||
set cacheControl(RequestCacheControl value) {
|
||||
headers[_cacheControlHeader] = value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
extension CacheControlBaseResponse on BaseResponse {
|
||||
ResponseCacheControl get cacheControl {
|
||||
final raw = headers[_cacheControlHeader] ?? '';
|
||||
return ResponseCacheControl.parse(raw);
|
||||
}
|
||||
|
||||
set cacheControl(ResponseCacheControl value) {
|
||||
headers[_cacheControlHeader] = value.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
import 'request_headers.dart';
|
||||
import 'date_parser.dart';
|
||||
import 'store/base.dart';
|
||||
|
||||
class HttpGetCache extends BaseClient {
|
||||
final HttpCacheStore store;
|
||||
final Client inner;
|
||||
|
||||
HttpGetCache(this.inner, this.store);
|
||||
|
||||
static Future<StreamedResponse> networkError(Object? error) async {
|
||||
return StreamedResponse(
|
||||
Stream.value(utf8.encode('$error')),
|
||||
500,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<StreamedResponse> offlineError(Object? error) async {
|
||||
return StreamedResponse(
|
||||
Stream.value(utf8.encode('$error')),
|
||||
503,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<StreamedResponse> gatewayTimeout() async {
|
||||
return StreamedResponse(
|
||||
Stream.value(utf8.encode('Gateway Timeout')),
|
||||
504,
|
||||
);
|
||||
}
|
||||
|
||||
Future<StreamedResponse> save(BaseRequest request) async {
|
||||
final reqCC = request.cacheControl;
|
||||
final res = await inner.send(request);
|
||||
final resCC = res.cacheControl;
|
||||
if (res.statusCode == 200) {
|
||||
if (reqCC.noStore || resCC.noStore || resCC.noCache) {
|
||||
return res;
|
||||
} else {
|
||||
return await store.set(request, res);
|
||||
}
|
||||
} else {
|
||||
final cached = await store.get(request);
|
||||
if (cached != null) {
|
||||
final cachedCC = cached.cacheControl;
|
||||
final now = getDateInt();
|
||||
final date = cached.requestHeaders.created;
|
||||
if (reqCC.staleIfError.$2 || resCC.staleIfError.$2) {
|
||||
return cached;
|
||||
} else if (date != null &&
|
||||
now <
|
||||
(date +
|
||||
cachedCC.maxAge +
|
||||
max(
|
||||
cachedCC.staleIfError.$1,
|
||||
reqCC.staleIfError.$1,
|
||||
))) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Stream<StreamedResponse> stream(BaseRequest request) async* {
|
||||
final method = request.method.toLowerCase();
|
||||
final reqCC = request.cacheControl;
|
||||
|
||||
if (kIsWeb || method != 'get') {
|
||||
yield await inner.send(request);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for early fetch
|
||||
var cached = await store.get(request);
|
||||
bool needsUpdate = true;
|
||||
|
||||
if (cached == null && reqCC.onlyIfCached) {
|
||||
yield await gatewayTimeout();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for cache
|
||||
if (cached != null) {
|
||||
final cachedCC = cached.cacheControl;
|
||||
if (cachedCC.mustRevalidate || cachedCC.noCache || reqCC.noCache) {
|
||||
needsUpdate = true;
|
||||
} else {
|
||||
final now = getDateInt();
|
||||
final date = cached.requestHeaders.created;
|
||||
if (date != null && now < (date + cachedCC.maxAge)) {
|
||||
needsUpdate = false;
|
||||
yield cached;
|
||||
} else if (date != null &&
|
||||
now < (date + cachedCC.maxAge + cachedCC.staleWhileRevalidate.$1)) {
|
||||
yield cached;
|
||||
} else if (cachedCC.staleWhileRevalidate.$2) {
|
||||
yield cached;
|
||||
}
|
||||
|
||||
if (!cachedCC.immutable && needsUpdate) {
|
||||
// Check for new eTag, Last-Modified change
|
||||
final headRes = await inner.head(request.url);
|
||||
final headHeaders = headRes.requestHeaders;
|
||||
if (headRes.statusCode == 200) {
|
||||
// Check eTag
|
||||
if ((cached.requestHeaders.eTag + headHeaders.eTag).isNotEmpty &&
|
||||
cached.requestHeaders.eTag == headHeaders.eTag) {
|
||||
needsUpdate = false;
|
||||
}
|
||||
if ((max(
|
||||
cached.requestHeaders.lastModified ?? 0,
|
||||
headHeaders.lastModified ?? 0,
|
||||
) >
|
||||
0) &&
|
||||
cached.requestHeaders.lastModified ==
|
||||
headHeaders.lastModified) {
|
||||
needsUpdate = false;
|
||||
}
|
||||
} else if (headRes.statusCode == 304) {
|
||||
// Or 304 cache is still good to reuse
|
||||
needsUpdate = false;
|
||||
if (cachedCC.mustRevalidate || cachedCC.noCache || reqCC.noCache) {
|
||||
yield cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsUpdate) return;
|
||||
yield await save(request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StreamedResponse> send(BaseRequest request) {
|
||||
return stream(request).last;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'cache_control.dart';
|
||||
import 'date_parser.dart';
|
||||
|
||||
class RequestHeaders {
|
||||
final Map<String, String> value;
|
||||
RequestHeaders(this.value);
|
||||
|
||||
static DateParser dateParser = const DateParser();
|
||||
|
||||
String getValue(String key) {
|
||||
final result = value.entries.firstWhere(
|
||||
(e) => e.key.toLowerCase() == key.toLowerCase(),
|
||||
orElse: () => MapEntry(key, ''),
|
||||
);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
String get eTag => getValue('eTag');
|
||||
|
||||
int get age => int.tryParse(getValue('Age')) ?? 0;
|
||||
|
||||
int? get lastModified => dateParser(getValue('Last-Modified'))?.toSeconds();
|
||||
|
||||
int? get date => dateParser(getValue('Date'))?.toSeconds();
|
||||
|
||||
int? get expires => dateParser(getValue('Expires'))?.toSeconds();
|
||||
|
||||
int? get created {
|
||||
final lastModifiedKey = value.keys.firstWhereOrNull(
|
||||
(e) => e.toLowerCase() == 'last-modified',
|
||||
);
|
||||
final dateKey = value.keys.firstWhereOrNull(
|
||||
(e) => e.toLowerCase() == 'date',
|
||||
);
|
||||
final key = lastModifiedKey ?? dateKey;
|
||||
if (key != null) {
|
||||
final d = dateParser(value[key]!);
|
||||
if (d != null) return d.millisecondsSinceEpoch;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseRequestCCUtils on BaseRequest {
|
||||
RequestHeaders get requestHeaders {
|
||||
return RequestHeaders(headers);
|
||||
}
|
||||
|
||||
RequestCacheControl get cacheControl {
|
||||
return RequestCacheControl(requestHeaders.getValue('cache-control'));
|
||||
}
|
||||
}
|
||||
|
||||
extension StreamedResponseCCUtils on StreamedResponse {
|
||||
RequestHeaders get requestHeaders {
|
||||
return RequestHeaders(headers);
|
||||
}
|
||||
|
||||
ResponseCacheControl get cacheControl {
|
||||
return ResponseCacheControl(requestHeaders.getValue('cache-control'));
|
||||
}
|
||||
}
|
||||
|
||||
extension ResponseCCUtils on Response {
|
||||
RequestHeaders get requestHeaders {
|
||||
return RequestHeaders(headers);
|
||||
}
|
||||
|
||||
ResponseCacheControl get cacheControl {
|
||||
return ResponseCacheControl(requestHeaders.getValue('cache-control'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:http/http.dart';
|
||||
|
||||
class HttpCacheStore {
|
||||
Future<StreamedResponse?> get(BaseRequest request) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<StreamedResponse> set(
|
||||
BaseRequest request,
|
||||
StreamedResponse response,
|
||||
) async {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../constants.dart';
|
||||
import '../database/database.dart';
|
||||
import '../date_parser.dart';
|
||||
import '../request_headers.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class SqliteHttpCacheStore extends HttpCacheStore {
|
||||
final HttpGetCacheDatabase database;
|
||||
|
||||
SqliteHttpCacheStore(this.database);
|
||||
|
||||
static Future<String> getCacheDir(HttpGetCacheDatabase database) async {
|
||||
if (kIsWeb) return '';
|
||||
final cachePath = database.settings.cachePath;
|
||||
final cacheDir = Directory(cachePath);
|
||||
if (!cacheDir.existsSync()) {
|
||||
await cacheDir.create(recursive: true);
|
||||
}
|
||||
return cacheDir.path;
|
||||
}
|
||||
|
||||
static Future<void> deleteCacheDir(HttpGetCacheDatabase database) async {
|
||||
if (kIsWeb) return;
|
||||
final cachePath = await getCacheDir(database);
|
||||
if (cachePath.isEmpty) return;
|
||||
final cacheDir = Directory(cachePath);
|
||||
|
||||
if (cacheDir.existsSync()) {
|
||||
cacheDir.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> getCacheDirSize(HttpGetCacheDatabase database) async {
|
||||
if (kIsWeb) return 0;
|
||||
final cachePath = await getCacheDir(database);
|
||||
if (cachePath.isEmpty) return 0;
|
||||
final cacheDir = Directory(cachePath);
|
||||
|
||||
if (cacheDir.existsSync()) {
|
||||
final files = await cacheDir.list(recursive: true).toList();
|
||||
final dirSize =
|
||||
files.fold(0, (int sum, file) => sum + file.statSync().size);
|
||||
return dirSize;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Future<void> deleteExpired(HttpGetCacheDatabase database) async {
|
||||
await database.deleteHttpCacheStale();
|
||||
}
|
||||
|
||||
Stream<List<int>> _streamAndSaveBytes(
|
||||
BaseRequest req,
|
||||
StreamedResponse res,
|
||||
File file,
|
||||
) async* {
|
||||
final date = res.requestHeaders.created ?? getDateInt();
|
||||
if (!file.existsSync()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
final writer = file.openWrite();
|
||||
await for (final event in res.stream) {
|
||||
yield event;
|
||||
writer.add(event);
|
||||
}
|
||||
await writer.flush();
|
||||
await writer.close();
|
||||
|
||||
final vary = res.headers['vary'] ?? '';
|
||||
final requestHeaders = <String, String>{};
|
||||
if (vary.isNotEmpty && vary != '*') {
|
||||
final keys =
|
||||
vary.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty);
|
||||
for (final key in keys) {
|
||||
requestHeaders[key] = req.headers[key] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
await database.setHttpCache(
|
||||
url: req.url.key,
|
||||
headers: jsonEncode(res.headers),
|
||||
requestHeaders: jsonEncode(requestHeaders),
|
||||
body: file.path,
|
||||
maxAge: res.cacheControl.maxAge,
|
||||
staleWhileRevalidate: res.cacheControl.staleWhileRevalidate.$1,
|
||||
staleIfError: res.cacheControl.staleIfError.$1,
|
||||
immutable: res.cacheControl.immutable,
|
||||
date: date,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StreamedResponse> set(
|
||||
BaseRequest request,
|
||||
StreamedResponse response,
|
||||
) async {
|
||||
if (kIsWeb) return response;
|
||||
// Don't cache if Vary is *
|
||||
if (response.headers['vary'] == '*') {
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.cacheControl.noStore || request.cacheControl.noStore) {
|
||||
return response;
|
||||
}
|
||||
final dirPath = await getCacheDir(database);
|
||||
if (dirPath.isEmpty) return response;
|
||||
final dir = Directory(dirPath);
|
||||
// Use a unique file name to allow multiple responses per URL.
|
||||
// We can't rely just on URL key anymore.
|
||||
// We'll append a random string or use UUID if we had it, but here just use random hash or
|
||||
// simply use the existing method but append request headers hash.
|
||||
// To safe space and keep it simple, let's use a combination.
|
||||
// However, if we change the file naming, we orphan old files or we need to manage them.
|
||||
// Since we delete by DB entry (which stores path), it's fine.
|
||||
// Just need a unique name.
|
||||
|
||||
// Simple unique name strategy: hash(url + vary request headers)
|
||||
// But we don't know vary headers yet... wait, we do get them from response.
|
||||
// We computed `requestHeaders` in _streamAndSaveBytes.
|
||||
// Ideally we compute path there too? But we pass file into it.
|
||||
|
||||
// Let's modify behavior: Generate a random ID for the file?
|
||||
// Or just make `key` unique-ish.
|
||||
// sha256(url + timestamp + random) is safe enough.
|
||||
|
||||
final uniqueId = sha256
|
||||
.convert(utf8.encode(
|
||||
'${request.url.key}${DateTime.now().microsecondsSinceEpoch}'))
|
||||
.toString();
|
||||
final file = File(p.join(dir.path, uniqueId));
|
||||
|
||||
return StreamedResponse(
|
||||
_streamAndSaveBytes(request, response, file),
|
||||
response.statusCode,
|
||||
contentLength: response.contentLength,
|
||||
request: response.request,
|
||||
headers: response.headers,
|
||||
isRedirect: response.isRedirect,
|
||||
persistentConnection: response.persistentConnection,
|
||||
reasonPhrase: response.reasonPhrase,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StreamedResponse?> get(BaseRequest request) async {
|
||||
final candidates = await database //
|
||||
.getHttpCache(url: request.url.key)
|
||||
.get();
|
||||
|
||||
for (final candidate in candidates) {
|
||||
final responseHeaders = _parseHeaders(candidate.headers);
|
||||
final vary = responseHeaders['vary'];
|
||||
|
||||
if (vary == '*') {
|
||||
continue; // Should not happen if we don't store it, but check anyway.
|
||||
}
|
||||
|
||||
bool match = true;
|
||||
if (vary != null && vary.isNotEmpty) {
|
||||
final storedRequestHeaders = _parseHeaders(candidate.requestHeaders);
|
||||
final varyKeys =
|
||||
vary.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty);
|
||||
for (final key in varyKeys) {
|
||||
if (request.headers[key] != storedRequestHeaders[key]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
final path = candidate.body;
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
final stats = await file.stat();
|
||||
final h = {...responseHeaders};
|
||||
final rq = RequestHeaders(h);
|
||||
if (rq.created == null) {
|
||||
final d = DateTime.fromMillisecondsSinceEpoch(candidate.date);
|
||||
h['Date'] = d.toIso8601String();
|
||||
}
|
||||
return StreamedResponse(
|
||||
file.openRead(),
|
||||
200,
|
||||
contentLength: stats.size,
|
||||
headers: h,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// extension on BaseRequest {
|
||||
// String get key => sha256.convert(utf8.encode(url.key)).toString();
|
||||
// }
|
||||
|
||||
extension on Uri {
|
||||
String get key => toString();
|
||||
}
|
||||
|
||||
Map<String, String> _parseHeaders(String key) {
|
||||
final headers = <String, String>{};
|
||||
if (key.isNotEmpty) {
|
||||
final data = jsonDecode(key) as Map<String, Object?>;
|
||||
for (final entry in data.entries) {
|
||||
headers[entry.key] = entry.value?.toString() ?? '';
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
Reference in New Issue
Block a user