blob: 4f8a60f59c1e57dd484a3b86e57c8a3146831de7 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'logger.dart';
typedef Future<String> FetchDataFunction();
typedef Future<String> WithCacheFunction(FetchDataFunction fetchData,
[String key]);
typedef WithCacheFunction CreateCacheFunction(
{String overrideKey, Duration duration});
CreateCacheFunction initCache(Uri baseUri, [Logger logger]) {
final cache = new Cache(baseUri, logger);
logger ??= new StdOutLogger(Level.warning);
return ({String overrideKey, Duration duration}) {
if (duration == null) {
duration = new Duration(hours: 24);
}
return (FetchDataFunction call, [String key]) async {
if (overrideKey != null) {
key = overrideKey;
}
if (key == null || key.isEmpty) {
logger.warning("Key is null or empty - cannot cache result");
return null;
}
// format key
key = key
.replaceAll("/", "_")
.replaceAll(":", "")
.replaceAll(".", "_")
.replaceAll("?", "")
.replaceAll("%2F", "_")
.replaceAll("*", "x");
var cacheResult = await cache.read(key, duration);
if (cacheResult.hasResult) {
logger.debug("Found key $key in cache");
return cacheResult.result;
}
logger.debug("Could not find key $key in cache");
// we have to make a call
String result = await call();
// insert/update the cache
if (key != null && !key.isEmpty) {
await cache.write(key, result);
}
return result;
};
};
}
CreateCacheFunction noCache() {
return ({String overrideKey, Duration duration}) {
return (FetchDataFunction fetchData, [String key]) {
return fetchData();
};
};
}
/// Simple cache for caching data.
class Cache {
// TODO(mkroghj) use this instead of cache.dart
Uri base;
Logger logger;
Cache(this.base, this.logger);
Map<String, String> memoryCache = <String, String>{};
/// Checks if key [path] is in cache
Future<bool> containsKey(String path, [Duration duration]) async {
if (memoryCache.containsKey(path)) return true;
File file = new File.fromUri(base.resolve(path));
if (await file.exists()) {
return duration == null
? true
: new DateTime.now().difference(await file.lastModified()) <=
duration;
}
return false;
}
/// Try reading [path] from cache
Future<CacheResult> read(String path, [Duration duration]) async {
if (memoryCache.containsKey(path)) {
logger.debug('Found $path in memory cache');
return new CacheResult(memoryCache[path]);
}
File file = new File.fromUri(base.resolve(path));
if (!await file.exists()) {
logger.debug('Could not find file $path in file cache');
return new CacheResult.noResult();
}
if (duration != null &&
new DateTime.now().difference(await file.lastModified()) > duration) {
logger.debug('File $path was found but the information is too stale,'
'for the duration: $duration');
return new CacheResult.noResult();
}
logger.debug('Found $path in file cache');
try {
String text = await file.readAsString();
memoryCache[path] = text;
return new CacheResult(text);
} catch (error, st) {
logger.error("Could not read $path:", error, st);
return new CacheResult.noResult();
}
}
/// Store [text] as the cache data for [path].
Future write(String path, String text) async {
logger.debug('Creating $path in file cache');
File file = new File.fromUri(base.resolve(path));
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(text);
memoryCache[path] = text;
}
/// Clears the cache at [baseUri]
Future clearCache(Uri baseUri) async {
await new Directory(baseUri.toFilePath()).delete(recursive: true);
}
}
class CacheResult {
final bool hasResult;
final String result;
CacheResult.noResult()
: hasResult = false,
result = null {}
CacheResult(this.result) : hasResult = true {}
}