sourcecamembert::ProjectRegistry.fan

// History:
//  Jan 25 13 tcolar Creation
//

using concurrent
using util
using netColarUtils
using fwt

**
** ProjectRegistry
**
const class ProjectRegistry : Actor
{
  const File stateFile
  const AtomicBool isScanning := AtomicBool()

  const Uri[] srcDirs

  new make(Uri[] srcDirs, File optionsDir) : super(ActorPool())
  {
    this.srcDirs = srcDirs
    this.stateFile = optionsDir + `state/projects.fog`
  }

  override Obj? receive(Obj? msg)
  {
    try
    {
      items := msg as Obj[]
      action := items[0] as Str
      ProjectCache? c := Actor.locals["camembert.projectCache"]
      if(action == "scan")
      {
        if(c == null)
        {
          // "Lazilly" init the cache on first scan
           c = ProjectCache(srcDirs)
          Actor.locals["camembert.projectCache"] = c
        }
        newProjects := _scan(c)

        saveProjects(c) // perisst for faster startup next time

        if( ! newProjects.isEmpty)
        {
          // let the plugins know about the found projects
          PluginManager.cur.onChangedProjects(newProjects.vals)
        }

      }
      else if(action == "projects")
      {
        // If a a scan wasn't performed yet then use the saved projects
        // from a previous run for faster startups
        Uri:Project projects := [:]
        if( ! isScanning.val && c != null)
          projects = c.projects
        else
          projects = savedProjects
        return projects
      }
      else if(action == "add")
      {
        newPrj := c.addProject(items[1] as Uri)
        if(newPrj != null)
          PluginManager.cur.onChangedProjects([newPrj])
      }
      else if(action == "remove")
      {
        c.removeProject(items[1] as Uri)
      }
      else
        Sys.log.err("Unexpected project reistry thread action: $action !")
    }catch(Err e)
    {
      Sys.log.err("Project Registry thread error", e)
    }
    return null
  }

  internal Void saveProjects(ProjectCache c)
  {
    out := stateFile.out
    out.writeObj(c.projects)
    out.flush
    out.close
  }

  internal Uri:Project savedProjects()
  {
    Uri:Project projects := [:]
    try
    {
      if(stateFile.exists)
      {
        obj := stateFile.in.readObj
        if(obj != null && obj is Uri:Project)
          projects = (Uri:Project) obj
      }
    }catch(Err e)
    {
      // if loading the file fails, then remove it (either corrupt or new format)
      stateFile.delete
    }
    return projects
  }

  internal Uri:Project _scan(ProjectCache c)
  {
    Uri:Project newProjects := [:]
    setIsScanning(true)
    try
      newProjects = c.scanProjects
    finally
      setIsScanning(false)
    return newProjects
  }

  internal Void setIsScanning(Bool val)
  {
    isScanning.val = val
    try
    {
      Desktop.callAsync |->|
      {
        frame := Sys.cur.frame
        frame.updateStatus
        if( ! val)
        {
          // At end of scan, refresh the indexSpace
          refreshIndexSpace
        }
      }
    }
    catch {}
  }

  Void refreshIndexSpace()
  {
    Sys.cur.frame.spaces.each
    {
      if(it is IndexSpace)
        it.refresh
    }
  }


  static Uri:Project projects()
  {
    return (Uri:Project) Sys.cur.prjReg.send(["projects"]).get
  }

  static Uri:Project pluginProjects(Str pluginName)
  {
    return projects.findAll{it.plugin == pluginName}
  }

  ** Manually register a new project
  static Void register(Uri dir)
  {
     Sys.cur.prjReg.send(["add", dir]).get
  }

  static Void unRegister(Uri dir)
  {
     Sys.cur.prjReg.send(["remove", dir]).get
  }

  ** start a sync (asynchronous)
  static Void scan()
  {
    Sys.cur.prjReg.send(["scan"])
  }
}

class ProjectCache
{
  FileWatcher watcher := FileWatcher()
  Uri[] rootDirs

  ** All known projects
  Uri:Project projects := [:]

  new make(Uri[] srcDirs)
  {
    rootDirs = srcDirs
  }

  ** Look for projects, return the list of new ones
  Uri:Project scanProjects(Uri[] dirs := rootDirs)
  {
    Sys.log.info("Starting project scan in $dirs")
    Uri:Project newProjects := [:]
    try
    {

      // remove projects whose sources are gone
      projects = projects.findAll |prj|
      {
        return prj.dir.toFile.exists
      }

      // scan for new projects
      |Uri -> Project?|[] pluginFuncs := [,]
      Sys.cur.plugins.each {pluginFuncs.add(it.projectFinder)}

      dirs.each |srcDir|
      {
        f := srcDir.toFile
        if(f.exists && f.isDir)
        {
          watcher.changedDirs(f, 10).each |dir|
          {
            Project? prj := pluginFuncs.eachWhile {it.call(dir)}
            if(prj != null)
              newProjects[dir] = prj
          }
        }
      }

      projects.setAll(newProjects)
    }
    catch(Err e)
    {
      Sys.log.err("Project Scanning failed.", e)
    }
    Sys.log.info("Found $newProjects.size projects during scan in $dirs")

    return newProjects
  }

  Project? addProject(Uri? dir)
  {
    |Uri -> Project?|[] pluginFuncs := [,]
    Sys.cur.plugins.each {pluginFuncs.add(it.projectFinder)}
    Project? prj := pluginFuncs.eachWhile {it.call(dir)}
    if(prj != null)
      projects[dir] = prj
    return prj
  }

  Void removeProject(Uri? dir)
  {
    projects.remove(dir)
  }
}