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 opIn_r(string key)
50 	{
51 		return key in cache;
52 	}
53 
54 	void add(string key)
55 	{
56 		assert(key !in cache);
57 		cache.add(key);
58 		cache.save();
59 	}
60 
61 	void remove(string key)
62 	{
63 		assert(key in cache);
64 		cache.remove(key);
65 		cache.save();
66 	}
67 
68 	@property string[] lines() { return cache.keys; }
69 	@property size_t length() { return cache.length; }
70 }
71 
72 unittest
73 {
74 	import std.file, std.conv, core.thread;
75 
76 	enum FN = "test.txt";
77 	scope(exit) if (FN.exists) remove(FN);
78 
79 	{
80 		auto s = PersistentStringSet(FN);
81 		assert("foo" !in s);
82 		assert(s.length == 0);
83 		s.add("foo");
84 	}
85 	{
86 		auto s = PersistentStringSet(FN);
87 		assert("foo" in s);
88 		assert(s.length == 1);
89 		s.remove("foo");
90 	}
91 	{
92 		auto s = PersistentStringSet(FN);
93 		assert("foo" !in s);
94 		Thread.sleep(filesystemTimestampGranularity);
95 		std.file.write(FN, "foo\n");
96 		assert("foo" in s);
97 		Thread.sleep(filesystemTimestampGranularity);
98 		std.file.write(FN, "bar\n");
99 		assert(s.lines == ["bar"], text(s.lines));
100 	}
101 }