Index: tools/gardening_tools/base_lib/lib/src/cache.dart |
diff --git a/tools/gardening_tools/base_lib/lib/src/cache.dart b/tools/gardening_tools/base_lib/lib/src/cache.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8a4b28affc2628c26289e8baee8b0201d51be2e4 |
--- /dev/null |
+++ b/tools/gardening_tools/base_lib/lib/src/cache.dart |
@@ -0,0 +1,156 @@ |
+// 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'; |
+import 'try.dart'; |
+ |
+typedef Future<String> Call(); |
Johnni Winther
2017/08/23 12:40:46
Rename to [ReadResultFunction] and variables of th
|
+typedef Future<String> WithCache(Call call, [String key]); |
+typedef WithCache PerformWithCache({String overrideKey, Duration duration}); |
Johnni Winther
2017/08/23 12:40:47
Rename to [CreateCacheFunction] and variables of t
|
+ |
+PerformWithCache initCache(Uri baseUri, [Logger logger]) { |
+ if (logger == null) { |
+ logger = new StdOutLogger(Level.warning); |
Johnni Winther
2017/08/23 12:40:47
Just write this as:
logger ??= new StdOutLogger(L
|
+ } |
+ |
+ final cache = new Cache(baseUri, logger); |
+ |
+ return ({String overrideKey, Duration duration}) { |
+ if (duration == null) { |
+ duration = new Duration(hours: 24); |
+ } |
+ |
+ return (Call call, [String key]) async { |
+ if (overrideKey != null) { |
+ key = overrideKey; |
+ } |
+ |
+ if (key == null || key.isEmpty) { |
+ logger.warning("Key is null or empty - cannot cache result"); |
+ } else { |
+ // format key |
+ key = key.replaceAll("/", "_").replaceAll(".", "_"); |
+ |
+ Try<ReadFromCacheResult> readResult = |
Johnni Winther
2017/08/23 12:40:46
Rename to [cacheResult].
|
+ await cache.tryRead(key, duration); |
+ if (!readResult.isError() && readResult.get().hasResult) { |
+ logger.debug("Found key $key in cache"); |
+ return readResult.get().res; |
+ } |
+ if (readResult.isError()) { |
+ logger.error("Error when reading from cache", readResult.getError(), |
+ readResult.getStackTrace()); |
+ } |
+ } |
+ |
+ 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; |
+ }; |
+ }; |
+} |
+ |
+PerformWithCache noCache() { |
+ return ({String overrideKey, Duration duration}) { |
+ return (Call call, [String key]) { |
+ return call(); |
+ }; |
+ }; |
+} |
+ |
+/// Simple cache for caching data. |
+class Cache { |
Johnni Winther
2017/08/23 12:40:46
Add a TODO (for me?) to use this instead of the Ca
|
+ 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<Try<ReadFromCacheResult>> tryRead(String path, |
Johnni Winther
2017/08/23 12:40:46
Rename to [read] (since we always just try)
|
+ [Duration duration]) async { |
+ if (memoryCache.containsKey(path)) { |
+ logger.debug('Found $path in memory cache'); |
+ return new Try.from(new ReadFromCacheResult.result(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 Try.from(new ReadFromCacheResult.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'); |
Johnni Winther
2017/08/23 12:40:47
Split long strings manual:
logger.debug(
'Fil
|
+ return new Try.from(new ReadFromCacheResult.noResult()); |
+ } |
+ |
+ return tryStartAsync(() async { |
+ logger.debug('Found $path in file cache'); |
+ var text = await file.readAsString(); |
+ memoryCache[path] = text; |
+ return new ReadFromCacheResult.result(text); |
+ }); |
+ } |
+ |
+ /// 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 cache |
+ Future clearCache(Uri baseUri) async { |
+ // needs to be done |
Johnni Winther
2017/08/23 12:40:47
Write this as
// TODO(mkroghj): Implement this.
|
+ } |
+} |
+ |
+class ReadFromCacheResult { |
Johnni Winther
2017/08/23 12:40:46
Rename to [CacheResult] (the correctly name reads
|
+ bool hasResult; |
Johnni Winther
2017/08/23 12:40:47
Make the fields final.
|
+ String res; |
Johnni Winther
2017/08/23 12:40:47
Rename to [result]. We generally use unabbreviated
Bill Hesse
2017/08/23 15:41:25
I agree - alway use full, correctly-spelled words
|
+ |
+ ReadFromCacheResult.noResult() { |
+ hasResult = false; |
+ res = null; |
+ } |
+ |
+ ReadFromCacheResult.result(String result) { |
Johnni Winther
2017/08/23 12:40:47
Rename to the no-name constructor and use initiali
|
+ hasResult = true; |
+ this.res = result; |
+ } |
+} |