Skip to content

Commit

Permalink
⚡ Add basic file caching
Browse files Browse the repository at this point in the history
  • Loading branch information
techouse committed Mar 12, 2022
1 parent faad542 commit fddde31
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 83 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ build/
.idea/

# macOS specific
.DS_Store
.DS_Store

bin/query_cache/
205 changes: 129 additions & 76 deletions bin/main.dart
Original file line number Diff line number Diff line change
@@ -1,81 +1,54 @@
import 'dart:io' show exitCode, stdout;
import 'dart:io' show Platform, exitCode, stdout;

import 'package:alfred_workflow/alfred_workflow.dart'
show AlfredItem, AlfredItemIcon, AlfredItemText, AlfredWorkflow;
show
AlfredItem,
AlfredItemIcon,
AlfredItemText,
AlfredItems,
AlfredWorkflow;
import 'package:algolia/algolia.dart' show AlgoliaQuerySnapshot;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:collection/collection.dart' show IterableExtension;
import 'package:html_unescape/html_unescape.dart' show HtmlUnescape;
import 'package:path/path.dart' show dirname;
import 'package:stash/stash_api.dart'
show
Cache,
CacheEntryCreatedEvent,
CacheEntryEvictedEvent,
CacheEntryExpiredEvent,
CacheEntryRemovedEvent,
CacheEntryUpdatedEvent,
CreatedExpiryPolicy,
EventListenerMode,
LruEvictionPolicy,
CacheExtension;
import 'package:stash_file/stash_file.dart'
show FileCacheStore, newFileLocalCacheStore;

import 'src/constants/config.dart' show Config;
import 'src/extensions/string_helpers.dart' show StringHelpers;
import 'src/models/search_result.dart' show SearchResult;
import 'src/services/algolia_search.dart' show AlgoliaSearch;

void _showPlaceholder() {
workflow.addItem(
const AlfredItem(
title: 'Search the Tailwind CSS docs...',
icon: AlfredItemIcon(path: 'icon.png'),
),
);
}
final HtmlUnescape unescape = HtmlUnescape();

Future<void> _performSearch(String query, {String? version}) async {
final AlgoliaQuerySnapshot snapshot = await AlgoliaSearch.query(
query,
version: version,
);
final AlfredWorkflow workflow = AlfredWorkflow();

if (snapshot.nbHits > 0) {
final HtmlUnescape unescape = HtmlUnescape();

workflow.addItems(
snapshot.hits.map((snapshot) => SearchResult.fromJson(snapshot.data)).map(
(result) {
final int level = int.tryParse(result.type.substring(3)) ?? 0;
final String? title = result.hierarchy.getLevel(level);
final Map<String, String?> hierarchy = result.hierarchy.toJson()
..removeWhere((_, value) => value == null);

return AlfredItem(
uid: result.objectID,
title: title!,
subtitle: level > 0
? unescape.convert(hierarchy.values.join(' > '))
: null,
arg: result.url,
text: AlfredItemText(
largeType: title,
copy: result.url,
),
quickLookUrl: result.url,
icon: AlfredItemIcon(path: 'icon.png'),
valid: true,
);
},
),
);
} else {
final Uri url =
Uri.https('www.google.com', '/search', {'q': 'Tailwind CSS $query'});
final FileCacheStore store = newFileLocalCacheStore(
path: dirname(Platform.script.toFilePath()),
fromEncodable: (Map<String, dynamic> json) => AlfredItems.fromJson(json),
);

workflow.addItem(
AlfredItem(
title: 'No matching answers found',
subtitle: 'Shall I try and search Google?',
arg: url.toString(),
text: AlfredItemText(
copy: url.toString(),
),
quickLookUrl: url.toString(),
icon: AlfredItemIcon(path: 'google.png'),
valid: true,
),
);
}
}
final Cache cache = store.cache<AlfredItems>(
name: 'query_cache',
maxEntries: 10,
eventListenerMode: EventListenerMode.synchronous,
evictionPolicy: const LruEvictionPolicy(),
expiryPolicy: const CreatedExpiryPolicy(Duration(minutes: 1)),
);

final AlfredWorkflow workflow = AlfredWorkflow();
bool verbose = false;

void main(List<String> arguments) async {
Expand All @@ -89,40 +62,120 @@ void main(List<String> arguments) async {
..addFlag('verbose', abbr: 'v', defaultsTo: false);
final ArgResults args = parser.parse(arguments);

verbose = args['verbose'];

List<String> query =
args['query'].replaceAll(RegExp(r'\s+'), ' ').trim().split(' ');
String? version = query.firstWhereOrNull(
(el) => Config.supportedVersions.contains(el),
);
String? version =
query.firstWhereOrNull((el) => Config.supportedVersions.contains(el));
if (version != null) {
query.removeWhere((str) => str == version);
} else {
version = Config.supportedVersions.last;
}
final String queryString = query.join(' ').trim();

if (args['verbose']) verbose = true;

if (verbose) stdout.writeln('Query: "$queryString"');
if (verbose) {
stdout.writeln('Query: "$queryString"');
_cacheVerbosity();
}

if (queryString.isEmpty) {
_showPlaceholder();
} else {
await _performSearch(
queryString,
version: version,
);
final AlfredItems? cachedItem =
await cache.get('${queryString}_${version}'.md5hex);
if (cachedItem != null) {
workflow.addItems(cachedItem.items);
} else {
await _performSearch(queryString, version: version);
}
}
} on FormatException catch (err) {
exitCode = 2;
workflow.addItem(AlfredItem(title: err.toString()));
} catch (err) {
exitCode = 1;
workflow.addItem(AlfredItem(title: err.toString()));
if (verbose) {
rethrow;
}
if (verbose) rethrow;
} finally {
workflow.run();
}
}

void _cacheVerbosity() {
cache
..on<CacheEntryCreatedEvent<AlfredItems>>()
.listen((event) => print('Key "${event.entry.key}" added'))
..on<CacheEntryUpdatedEvent<AlfredItems>>()
.listen((event) => print('Key "${event.newEntry.key}" updated'))
..on<CacheEntryRemovedEvent<AlfredItems>>()
.listen((event) => print('Key "${event.entry.key}" removed'))
..on<CacheEntryExpiredEvent<AlfredItems>>()
.listen((event) => print('Key "${event.entry.key}" expired'))
..on<CacheEntryEvictedEvent<AlfredItems>>()
.listen((event) => print('Key "${event.entry.key}" evicted'));
}

void _showPlaceholder() {
workflow.addItem(
const AlfredItem(
title: 'Search the Tailwind CSS docs...',
icon: AlfredItemIcon(path: 'icon.png'),
),
);
}

Future<void> _performSearch(String query, {String? version}) async {
final AlgoliaQuerySnapshot snapshot = await AlgoliaSearch.query(
query,
version: version,
);

if (snapshot.nbHits > 0) {
final AlfredItems items = AlfredItems(
items: snapshot.hits
.map((snapshot) => SearchResult.fromJson(snapshot.data))
.map((result) {
final int level = int.tryParse(result.type.substring(3)) ?? 0;
final String? title = result.hierarchy.getLevel(level);
final Map<String, String?> hierarchy = result.hierarchy.toJson()
..removeWhere((_, value) => value == null);

return AlfredItem(
uid: result.objectID,
title: title!,
subtitle:
level > 0 ? unescape.convert(hierarchy.values.join(' > ')) : null,
arg: result.url,
text: AlfredItemText(
largeType: title,
copy: result.url,
),
quickLookUrl: result.url,
icon: AlfredItemIcon(path: 'icon.png'),
valid: true,
);
}).toList(),
);
cache.putIfAbsent('${query}_${version}'.md5hex, items);
workflow.addItems(items.items);
} else {
final Uri url =
Uri.https('www.google.com', '/search', {'q': 'Tailwind CSS $query'});

workflow.addItem(
AlfredItem(
title: 'No matching answers found',
subtitle: 'Shall I try and search Google?',
arg: url.toString(),
text: AlfredItemText(
copy: url.toString(),
),
quickLookUrl: url.toString(),
icon: AlfredItemIcon(path: 'google.png'),
valid: true,
),
);
}
}
7 changes: 7 additions & 0 deletions bin/src/extensions/string_helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'dart:convert' show utf8;

import 'package:crypto/crypto.dart' as crypto show md5;

extension StringHelpers on String {
String get md5hex => crypto.md5.convert(utf8.encode(this)).toString();
}
2 changes: 1 addition & 1 deletion info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
<key>variablesdontexport</key>
<array/>
<key>version</key>
<string>2.0.2</string>
<string>2.0.3</string>
<key>webaddress</key>
<string>https://github.com/techouse</string>
</dict>
Expand Down
50 changes: 46 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ packages:
description:
path: "."
ref: master
resolved-ref: f28c7ca39f588bfe68d0abd6b68b06408b365abe
resolved-ref: f94dac49b7cbae5a73590f6db5772edd5defdba2
url: "git@github.com:techouse/alfred_workflow.git"
source: git
version: "0.0.1"
version: "0.0.2"
algolia:
dependency: "direct main"
description:
Expand Down Expand Up @@ -66,6 +66,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: "direct main"
description:
Expand All @@ -81,7 +88,7 @@ packages:
source: hosted
version: "3.0.1"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
url: "https://pub.dartlang.org"
Expand All @@ -108,6 +115,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.2"
equatable:
dependency: transitive
description:
name: equatable
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
file:
dependency: transitive
description:
Expand Down Expand Up @@ -150,6 +164,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
lints:
dependency: "direct dev"
description:
Expand All @@ -172,7 +193,7 @@ packages:
source: hosted
version: "2.0.2"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"
Expand All @@ -192,6 +213,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
stash:
dependency: transitive
description:
name: stash
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
stash_file:
dependency: "direct main"
description:
name: stash_file
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
Expand Down
3 changes: 3 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ dependencies:
url: git@github.com:techouse/alfred_workflow.git
ref: master
collection: ^1.16.0
stash_file: ^4.0.1
path: ^1.8.1
crypto: ^3.0.1

dev_dependencies:
lints: ^1.0.0
Expand Down
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.2
2.0.3

0 comments on commit fddde31

Please sign in to comment.