1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2002-2009, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
// $Id$
package scala.xml
package persistent
import java.io.{File, FileOutputStream}
import java.nio.ByteBuffer
import java.nio.channels.Channels
/** <p>
* Mutable storage of immutable xml trees. Everything is kept in memory,
* with a thread periodically checking for changes and writing to file.
* To ensure atomicity, two files are used, filename1 and '$'+filename1.
* The implementation switches between the two, deleting the older one
* after a complete dump of the database has been written.
* </p>
*
* @author Burak Emir
*/
abstract class CachedFileStorage(private val file1: File)
extends java.lang.Thread with scala.util.logging.Logged {
private val file2 = new File(file1.getParent, file1.getName+"$")
/** either equals file1 or file2, references the next file in which updates will be stored
*/
private var theFile: File = null
private def switch = { theFile = if (theFile == file1) file2 else file1; }
/** this storage modified since last modification check */
protected var dirty = false
/** period between modification checks, in milliseconds */
protected val interval = 1000
/** finds and loads the storage file. subclasses should call this method
* prior to any other, but only once, to obtain the initial sequence of nodes.
*/
protected def initialNodes: Iterator[Node] = (file1.exists, file2.exists) match {
case (false,false) =>
theFile = file1
Iterator.empty
case (true, true ) if (file1.lastModified < file2.lastModified) =>
theFile = file2
load
case (true, _ ) =>
theFile = file1
load
case _ =>
theFile = file2
load
}
/** returns an iterator over the nodes in this storage */
def nodes: Iterator[Node]
/** adds a node, setting this.dirty to true as a side effect */
def += (e: Node): Unit
/** removes a tree, setting this.dirty to true as a side effect */
def -= (e: Node): Unit
/* loads and parses XML from file */
private def load: Iterator[Node] = {
import scala.io.Source
import scala.xml.parsing.ConstructingParser
log("[load]\nloading "+theFile)
val src = Source.fromFile(theFile)()
log("parsing "+theFile)
val res = ConstructingParser.fromSource(src,false).document.docElem(0)
switch
log("[load done]")
res.child.iterator
}
/** saves the XML to file */
private def save = if (this.dirty) {
log("[save]\ndeleting "+theFile);
theFile.delete();
log("creating new "+theFile);
theFile.createNewFile();
val fos = new FileOutputStream(theFile)
val c = fos.getChannel()
// @todo: optimize
val storageNode = <nodes>{ nodes.toList }</nodes>
val w = Channels.newWriter(c, "utf-8")
XML.write(w, storageNode, "utf-8", true, null)
log("writing to "+theFile);
w.close
c.close
fos.close
dirty = false
switch
log("[save done]")
}
/** run method of the thread. remember to use start() to start a thread, not run. */
override def run = {
log("[run]\nstarting storage thread, checking every "+interval+" ms");
while(true) {
Thread.sleep( this.interval );
save
}
}
/** forces writing of contents to the file, even if there has not been any update. */
def flush = {
this.dirty = true;
save
}
}
|