int
moveToPreloadedDir (const char *path, const char *iRODSPath) {
    int status;
    char preloadCachePath[MAX_NAME_LEN];
    off_t cacheSize;

    if (path == NULL || iRODSPath == NULL) {
        rodsLog (LOG_DEBUG, "moveToPreloadedDir: input path or iRODSPath is NULL");
        return (SYS_INTERNAL_NULL_INPUT_ERR);
    }

    status = _getCachePath(iRODSPath, preloadCachePath);
    if(status < 0) {
        rodsLog (LOG_DEBUG, "moveToPreloadedDir: failed to get cache path - %s", path);
        return status;
    }

    // make dir
    makeParentDirs(preloadCachePath);
    
    // move the file
    status = rename(path, preloadCachePath);
    if(status < 0) {
        rodsLog (LOG_DEBUG, "moveToPreloadedDir: rename error : %d", status);
        return status;
    }

    // check whether preload cache exceeds limit
    if(PreloadConfig.cacheMaxSize > 0) {
        cacheSize = getFileSizeRecursive(PreloadConfig.cachePath);
        if(cacheSize > (off_t)PreloadConfig.cacheMaxSize) {
            // evict?
            status = _evictOldCache(cacheSize - (off_t)PreloadConfig.cacheMaxSize);
            if(status < 0) {
                rodsLog (LOG_DEBUG, "moveToPreloadedDir: failed to evict old cache");
                return status;
            }
        }
    }

    return (0);
}
off_t 
getFileSizeRecursive(const char *path) {
    off_t accumulatedSize = 0;
    DIR *dir = NULL;
    char filepath[MAX_NAME_LEN];
    struct dirent *entry;
    struct stat statbuf;
    off_t localSize;

    dir = opendir(path);
    if (dir != NULL) {
        while ((entry = readdir(dir)) != NULL) {
            if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
                continue;
            }

            snprintf(filepath, MAX_NAME_LEN, "%s/%s", path, entry->d_name);

            if (!stat(filepath, &statbuf)) {
                // has entry
                if (S_ISDIR(statbuf.st_mode)) {
                    // directory
                    // call myself recursively
                    localSize = getFileSizeRecursive(filepath);
                    if(localSize > 0) {
                        accumulatedSize += localSize;
                    }
                } else {
                    // file
                    localSize = statbuf.st_size;
                    if(localSize > 0) {
                        accumulatedSize += localSize;
                    }
                }
            }
        }
        closedir(dir);
    }

    return accumulatedSize;
}
int
preloadFile (const char *path, struct stat *stbuf) {
    int status;
    preloadThreadInfo_t *existingThreadInfo = NULL;
    preloadThreadInfo_t *threadInfo = NULL;
    preloadThreadData_t *threadData = NULL;
    char iRODSPath[MAX_NAME_LEN];
    off_t cacheSize;

    // convert input path to iRODSPath
    status = _getiRODSPath(path, iRODSPath);
    if(status < 0) {
        rodsLogError(LOG_ERROR, status, "preloadFile: _getiRODSPath error.");
        rodsLog (LOG_ERROR, "preloadFile: failed to get iRODS path - %s", path);
        return status;
    }

    // check the given file is already preloaded or preloading
    LOCK(PreloadLock);

    // check the given file is preloading
    existingThreadInfo = (preloadThreadInfo_t *)lookupFromHashTable(PreloadThreadTable, iRODSPath);
    if(existingThreadInfo != NULL) {
        rodsLog (LOG_DEBUG, "preloadFile: preloading is already running - %s", iRODSPath);
        UNLOCK(PreloadLock);
        return 0;
    }

    if(_hasValidCache(iRODSPath, stbuf) != 0) {
        // invalidate cache - this may fail if cache file does not exists
        // if old cache exists in local, invalidate it
        _invalidateCache(iRODSPath);

        if(stbuf->st_size < PreloadConfig.preloadMinSize) {
            rodsLog (LOG_DEBUG, "preloadFile: given file is smaller than preloadMinSize, canceling preloading - %s", iRODSPath);
            UNLOCK(PreloadLock);
            return (0);
        }

        // check whether preload cache exceeds limit
        if(PreloadConfig.cacheMaxSize > 0) {
            // cache max size is set
            if(stbuf->st_size > (off_t)PreloadConfig.cacheMaxSize) {
                rodsLog (LOG_DEBUG, "preloadFile: given file is bigger than cacheMaxSize, canceling preloading - %s", iRODSPath);
                UNLOCK(PreloadLock);
                return (0);
            }

            cacheSize = getFileSizeRecursive(PreloadConfig.cachePath);
            if((cacheSize + stbuf->st_size) > (off_t)PreloadConfig.cacheMaxSize) {
                // evict?
                status = _evictOldCache((cacheSize + stbuf->st_size) - (off_t)PreloadConfig.cacheMaxSize);
                if(status < 0) {
                    rodsLog (LOG_ERROR, "preloadFile: failed to evict old cache");
                    UNLOCK(PreloadLock);
                    return status;
                }
            }
        }

        // does not have valid cache. now, start a new preloading

        // create a new thread to preload
        threadInfo = (preloadThreadInfo_t *)malloc(sizeof(preloadThreadInfo_t));
        threadInfo->path = strdup(iRODSPath);
        threadInfo->running = PRELOAD_THREAD_RUNNING;
        INIT_STRUCT_LOCK((*threadInfo));

        insertIntoHashTable(PreloadThreadTable, iRODSPath, threadInfo);

        // prepare thread argument
        threadData = (preloadThreadData_t *)malloc(sizeof(preloadThreadData_t));
        threadData->path = strdup(iRODSPath);
        memcpy(&threadData->stbuf, stbuf, sizeof(struct stat));
        threadData->threadInfo = threadInfo;

        rodsLog (LOG_DEBUG, "preloadFile: start preloading - %s", iRODSPath);
#ifdef USE_BOOST
        status = threadInfo->thread = new boost::thread(_preloadThread, (void *)threadData);
#else
        status = pthread_create(&threadInfo->thread, NULL, _preloadThread, (void *)threadData);
#endif
    } else {
        rodsLog (LOG_DEBUG, "preloadFile: given file is already preloaded - %s", iRODSPath);
        status = 0;
    }

    UNLOCK(PreloadLock);
    return status;
}