/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2002-2009, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// $Id: CachedFileStorage.scala 18626 2009-09-01 16:05:38Z extempore $

package scala.xml
package persistent

import scala.io.File
import java.io.{ File => JFile, 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 = (file1.parent.get / (file1.name + "$")).toFile
    
  /**  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 lazy val initialNodes: Iterator[Node] = {
    val (e1, e2) = (file1.exists, file2.exists)
    
    theFile = if (e2 && (file2 isFresher file1)) file2 else file1
    if (!e1 && !e2) Iterator.empty else 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 = theFile.chars()
    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.createFile();
    val fos = theFile.outputStream()
    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 %d ms" format interval)
    
    while (true) { 
      Thread sleep interval
      save
    }
  }
  
  /** forces writing of contents to the file, even if there has not been any update. */
  def flush = { 
    this.dirty = true
    save
  }
}