1 /**
2  * ae.sys.persistence.stringset
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.sys.persistence.stringset;
15 
16 import ae.sys.persistence.core;
17 
18 // ****************************************************************************
19 
20 // https://issues.dlang.org/show_bug.cgi?id=7016
21 static import ae.sys.file;
22 
23 /// A string hashset, stored one line per entry.
24 struct PersistentStringSet
25 {
26 	import ae.utils.aa : HashSet;
27 
28 	static HashSet!string _load(string fileName)
29 	{
30 		import std.file : readText;
31 		import std.string : splitLines;
32 
33 		return HashSet!string(fileName.readText().splitLines());
34 	} ///
35 
36 	static void _save(string fileName, HashSet!string data)
37 	{
38 		import std.array : join;
39 		import ae.sys.file : atomicWrite;
40 
41 		atomicWrite(fileName, data.keys.join("\n"));
42 	} ///
43 
44 	private alias Cache = FileCache!(_load, _save, FlushPolicy.manual);
45 	private Cache cache;
46 
47 	this(string fileName) { cache = Cache(fileName); } ///
48 
49 	auto opBinaryRight(string op)(string key)
50 	if (op == "in")
51 	{
52 		return key in cache;
53 	} ///
54 
55 	void add(string key)
56 	{
57 		assert(key !in cache);
58 		cache.add(key);
59 		cache.save();
60 	} ///
61 
62 	void remove(string key)
63 	{
64 		assert(key in cache);
65 		cache.remove(key);
66 		cache.save();
67 	} ///
68 
69 	@property string[] lines() { return cache.keys; } ///
70 	@property size_t length() { return cache.length; } ///
71 }
72 
73 ///
74 unittest
75 {
76 	import std.file, std.conv, core.thread;
77 
78 	enum FN = "test.txt";
79 	if (FN.exists) remove(FN);
80 	scope(exit) if (FN.exists) remove(FN);
81 
82 	{
83 		auto s = PersistentStringSet(FN);
84 		assert("foo" !in s);
85 		assert(s.length == 0);
86 		s.add("foo");
87 	}
88 	{
89 		auto s = PersistentStringSet(FN);
90 		assert("foo" in s);
91 		assert(s.length == 1);
92 		s.remove("foo");
93 	}
94 	{
95 		auto s = PersistentStringSet(FN);
96 		assert("foo" !in s);
97 		Thread.sleep(filesystemTimestampGranularity);
98 		std.file.write(FN, "foo\n");
99 		assert("foo" in s);
100 		Thread.sleep(filesystemTimestampGranularity);
101 		std.file.write(FN, "bar\n");
102 		assert(s.lines == ["bar"], text(s.lines));
103 	}
104 }