|
|
|
@ -4,6 +4,8 @@ import (
|
|
|
|
|
"os"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
"path"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func startFileMonitor(sleepTime time.Duration) {
|
|
|
|
@ -23,12 +25,12 @@ func startFileMonitor(sleepTime time.Duration) {
|
|
|
|
|
|
|
|
|
|
func checkCacheFreshness() {
|
|
|
|
|
/* Before anything, get cache write lock (in case we have to delete) */
|
|
|
|
|
Config.FileCache.CacheMutex.Lock()
|
|
|
|
|
Config.FileSystem.CacheMutex.Lock()
|
|
|
|
|
|
|
|
|
|
/* Iterate through paths in cache map to query file last modified times */
|
|
|
|
|
for path := range Config.FileCache.CacheMap.Map {
|
|
|
|
|
for path := range Config.FileSystem.CacheMap.Map {
|
|
|
|
|
/* Get file pointer, no need for lock as we have write lock */
|
|
|
|
|
file := Config.FileCache.CacheMap.Get(path)
|
|
|
|
|
file := Config.FileSystem.CacheMap.Get(path)
|
|
|
|
|
|
|
|
|
|
/* If this is a generated file, we skip */
|
|
|
|
|
if isGeneratedType(file) {
|
|
|
|
@ -39,7 +41,7 @@ func checkCacheFreshness() {
|
|
|
|
|
if err != nil {
|
|
|
|
|
/* Log file as not in cache, then delete */
|
|
|
|
|
Config.LogSystemError("Failed to stat file in cache: %s\n", path)
|
|
|
|
|
Config.FileCache.CacheMap.Remove(path)
|
|
|
|
|
Config.FileSystem.CacheMap.Remove(path)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
timeModified := stat.ModTime().UnixNano()
|
|
|
|
@ -51,7 +53,7 @@ func checkCacheFreshness() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Done! We can release cache read lock */
|
|
|
|
|
Config.FileCache.CacheMutex.Unlock()
|
|
|
|
|
Config.FileSystem.CacheMutex.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isGeneratedType(file *File) bool {
|
|
|
|
@ -69,44 +71,78 @@ func isGeneratedType(file *File) bool {
|
|
|
|
|
* to remove cached files in a LRU style. Uses a RW mutex to lock the
|
|
|
|
|
* cache map for appropriate functions and ensure thread safety.
|
|
|
|
|
*/
|
|
|
|
|
type FileCache struct {
|
|
|
|
|
CacheMap *FixedMap
|
|
|
|
|
CacheMutex sync.RWMutex
|
|
|
|
|
FileSizeMax int64
|
|
|
|
|
type FileSystem struct {
|
|
|
|
|
CacheMap *FixedMap
|
|
|
|
|
CacheMutex sync.RWMutex
|
|
|
|
|
CacheFileMax int64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fc *FileCache) Init(size int, fileSizeMax float64) {
|
|
|
|
|
fc.CacheMap = NewFixedMap(size)
|
|
|
|
|
fc.CacheMutex = sync.RWMutex{}
|
|
|
|
|
fc.FileSizeMax = int64(BytesInMegaByte * fileSizeMax)
|
|
|
|
|
func (fs *FileSystem) Init(size int, fileSizeMax float64) {
|
|
|
|
|
fs.CacheMap = NewFixedMap(size)
|
|
|
|
|
fs.CacheMutex = sync.RWMutex{}
|
|
|
|
|
fs.CacheFileMax = int64(BytesInMegaByte * fileSizeMax)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fc *FileCache) FetchRegular(request *FileSystemRequest) ([]byte, *GophorError) {
|
|
|
|
|
/* Calls fc.Fetch() but with the filecontents init function for a regular file */
|
|
|
|
|
return fc.Fetch(request, func(path string) FileContents {
|
|
|
|
|
return &RegularFileContents{
|
|
|
|
|
path,
|
|
|
|
|
nil,
|
|
|
|
|
func (fs *FileSystem) HandleRequest(requestPath string, host *ConnHost) ([]byte, *GophorError) {
|
|
|
|
|
/* Stat filesystem for request file type */
|
|
|
|
|
fileType := FileTypeDir;
|
|
|
|
|
if requestPath != "." {
|
|
|
|
|
stat, err := os.Stat(requestPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
/* Check file isn't in cache before throwing in the towel */
|
|
|
|
|
fs.CacheMutex.RLock()
|
|
|
|
|
file := fs.CacheMap.Get(requestPath)
|
|
|
|
|
if file == nil {
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return nil, &GophorError{ FileStatErr, err }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* It's there! Get contents, unlock and return */
|
|
|
|
|
file.Mutex.RLock()
|
|
|
|
|
b := file.Contents(&FileSystemRequest{ requestPath, host })
|
|
|
|
|
file.Mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return b, nil
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fc *FileCache) FetchGophermap(request *FileSystemRequest) ([]byte, *GophorError) {
|
|
|
|
|
/* Calls fc.Fetch() but with the filecontents init function for a gophermap */
|
|
|
|
|
return fc.Fetch(request, func(path string) FileContents {
|
|
|
|
|
return &GophermapContents{
|
|
|
|
|
path,
|
|
|
|
|
nil,
|
|
|
|
|
/* Set file type for later handling */
|
|
|
|
|
switch {
|
|
|
|
|
case stat.Mode() & os.ModeDir != 0:
|
|
|
|
|
/* do nothing, already set :) */
|
|
|
|
|
case stat.Mode() & os.ModeType == 0:
|
|
|
|
|
fileType = FileTypeRegular
|
|
|
|
|
default:
|
|
|
|
|
fileType = FileTypeBad
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch fileType {
|
|
|
|
|
/* Directory */
|
|
|
|
|
case FileTypeDir:
|
|
|
|
|
_, err := os.Stat(path.Join(requestPath, GophermapFileStr))
|
|
|
|
|
if err == nil {
|
|
|
|
|
/* Gophermap exists, serve this! */
|
|
|
|
|
return fs.FetchFile(&FileSystemRequest{ requestPath, host })
|
|
|
|
|
} else {
|
|
|
|
|
/* No gophermap, serve directory listing */
|
|
|
|
|
return listDir(&FileSystemRequest{ requestPath, host }, map[string]bool{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Regular file */
|
|
|
|
|
case FileTypeRegular:
|
|
|
|
|
return fs.FetchFile(&FileSystemRequest{ requestPath, host })
|
|
|
|
|
|
|
|
|
|
/* Unsupported type */
|
|
|
|
|
default:
|
|
|
|
|
return nil, &GophorError{ FileTypeErr, nil }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(string) FileContents) ([]byte, *GophorError) {
|
|
|
|
|
func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorError) {
|
|
|
|
|
/* Get cache map read lock then check if file in cache map */
|
|
|
|
|
fc.CacheMutex.RLock()
|
|
|
|
|
file := fc.CacheMap.Get(request.Path)
|
|
|
|
|
|
|
|
|
|
/* TODO: work on efficiency. improve use of mutex?? */
|
|
|
|
|
fs.CacheMutex.RLock()
|
|
|
|
|
file := fs.CacheMap.Get(request.Path)
|
|
|
|
|
|
|
|
|
|
if file != nil {
|
|
|
|
|
/* File in cache -- before doing anything get file read lock */
|
|
|
|
@ -123,7 +159,7 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri
|
|
|
|
|
if gophorErr != nil {
|
|
|
|
|
/* Error loading contents, unlock all mutex then return error */
|
|
|
|
|
file.Mutex.Unlock()
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return nil, gophorErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -138,12 +174,17 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri
|
|
|
|
|
stat, err := os.Stat(request.Path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
/* Error stat'ing file, unlock read mutex then return error */
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return nil, &GophorError{ FileStatErr, err }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create new file contents object using supplied function */
|
|
|
|
|
contents := newFileContents(request.Path)
|
|
|
|
|
var contents FileContents
|
|
|
|
|
if strings.HasSuffix(request.Path, "/"+GophermapFileStr) {
|
|
|
|
|
contents = &GophermapContents{ request.Path, nil }
|
|
|
|
|
} else {
|
|
|
|
|
contents = &RegularFileContents{ request.Path, nil }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create new file wrapper around contents */
|
|
|
|
|
file = NewFile(contents)
|
|
|
|
@ -152,32 +193,32 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri
|
|
|
|
|
gophorErr := file.LoadContents()
|
|
|
|
|
if gophorErr != nil {
|
|
|
|
|
/* Error loading contents, unlock read mutex then return error */
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return nil, gophorErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Compare file size (in MB) to CacheFileSizeMax, if larger just get file
|
|
|
|
|
* contents, unlock all mutex and don't bother caching.
|
|
|
|
|
*/
|
|
|
|
|
if stat.Size() > fc.FileSizeMax {
|
|
|
|
|
if stat.Size() > fs.CacheFileMax {
|
|
|
|
|
b := file.Contents(request)
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
return b, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* File not in cache -- Swap cache map read for write lock. */
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fc.CacheMutex.Lock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.Lock()
|
|
|
|
|
|
|
|
|
|
/* Put file in the FixedMap */
|
|
|
|
|
fc.CacheMap.Put(request.Path, file)
|
|
|
|
|
fs.CacheMap.Put(request.Path, file)
|
|
|
|
|
|
|
|
|
|
/* Before unlocking cache mutex, lock file read for upcoming call to .Contents() */
|
|
|
|
|
file.Mutex.RLock()
|
|
|
|
|
|
|
|
|
|
/* Swap cache lock back to read */
|
|
|
|
|
fc.CacheMutex.Unlock()
|
|
|
|
|
fc.CacheMutex.RLock()
|
|
|
|
|
fs.CacheMutex.Unlock()
|
|
|
|
|
fs.CacheMutex.RLock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read file contents into new variable for return, then unlock file read lock */
|
|
|
|
@ -185,7 +226,7 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri
|
|
|
|
|
file.Mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
/* Finally we can unlock the cache map read lock, we are done :) */
|
|
|
|
|
fc.CacheMutex.RUnlock()
|
|
|
|
|
fs.CacheMutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
return b, nil
|
|
|
|
|
}
|
|
|
|
|