Fantom

 

//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//    3 May 11  Brian Frank  Creation
//

using concurrent

**
** FileRepo implements a repository on the file system using
** a simple directory structure:
**
**   alpha/
**     alpha-1.0.1.pod
**     alpha-1.0.2.pod
**   beta/
**     beta-1.0.2.pod
**
** A background actor is used to manage the cached data
** structures read from the disk.
**
internal const class FileRepo : Repo
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  ** Make for given URI which must reference a local dir
  new make(Uri uri)
  {
    // ensure URI maps to valid directory
    obj := uri.scheme == null ? File(uri, false) : uri.get
    file := obj as File
    if (file == null) throw Err("FileRepo uri does not resolve to file: $uri")
    if (!file.exists) throw Err("FileRepo uri does not exist: $uri")
    if (!file.isDir)  throw Err("FileRepo uri does not resolve to dir: $uri")

    // save normalized file/URI
    this.dir = file.normalize
    this.uri = this.dir.uri

    // construct actor and kick off load
    this.actor = Actor(actorPool) |msg| { receive(msg) }
    actor.send(FileRepoMsg(FileRepoMsg.load))
  }

//////////////////////////////////////////////////////////////////////////
// Repo
//////////////////////////////////////////////////////////////////////////

  override const Uri uri

  override Str:Str ping()
  {
    ["fanr.type":    typeof.toStr,
     "fanr.version": FileRepo#.pod.version.toStr]
  }

  override PodSpec? find(Str name, Version? ver, Bool checked := true)
  {
    msg := FileRepoMsg(FileRepoMsg.find, name, ver)
    spec := actor.send(msg).get(timeout) as PodSpec
    if (spec != null) return spec
    if (checked) throw UnknownPodErr("$name-$ver")
    return null
  }

  override PodSpec[] query(Str query, Int numVersions := 1)
  {
    msg := FileRepoMsg(FileRepoMsg.query, query, numVersions)
    Unsafe r := actor.send(msg).get(timeout)
    return r.val
  }

  override InStream read(PodSpec spec)
  {
    specToFile(spec).in
  }

  override PodSpec publish(File podFile)
  {
    msg := FileRepoMsg(FileRepoMsg.publish, podFile)
    PodSpec r := actor.send(msg).get(timeout)
    return r
  }

  ** Send actor message to refresh entire cache from disk
  Future refresh()
  {
    msg := FileRepoMsg(FileRepoMsg.refresh)
    return actor.send(msg)
  }

  ** Dispatch actor message to our FileRepoDb
  private Obj? receive(Obj? msg)
  {
    db := Actor.locals["fanr.file.db"] as FileRepoDb
    if (db == null)  Actor.locals["fanr.file.db"] = db = FileRepoDb(this)
    return db.dispatch(msg)
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  internal File specToFile(PodSpec spec)
  {
    dir + `${spec.name}/${spec.toStr}.pod`
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  private static const ActorPool actorPool := ActorPool()
  private static const Duration timeout    := 30sec

  const File dir              // Root directory of the repo
  private const Actor actor   // FileRepoActor which manages database
}