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 <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.persistence.stringset;
15 
16 import ae.sys.persistence.core;
17 
18 // ****************************************************************************
19 
20 // http://d.puremagic.com/issues/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 	alias Cache = FileCache!(load, save, FlushPolicy.manual);
45 	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 unittest
74 {
75 	import std.file, std.conv, core.thread;
76 
77 	enum FN = "test.txt";
78 	if (FN.exists) remove(FN);
79 	scope(exit) if (FN.exists) remove(FN);
80 
81 	{
82 		auto s = PersistentStringSet(FN);
83 		assert("foo" !in s);
84 		assert(s.length == 0);
85 		s.add("foo");
86 	}
87 	{
88 		auto s = PersistentStringSet(FN);
89 		assert("foo" in s);
90 		assert(s.length == 1);
91 		s.remove("foo");
92 	}
93 	{
94 		auto s = PersistentStringSet(FN);
95 		assert("foo" !in s);
96 		Thread.sleep(filesystemTimestampGranularity);
97 		std.file.write(FN, "foo\n");
98 		assert("foo" in s);
99 		Thread.sleep(filesystemTimestampGranularity);
100 		std.file.write(FN, "bar\n");
101 		assert(s.lines == ["bar"], text(s.lines));
102 	}
103 }