/*  Functions to cache open files between requests

    ©2000 Joseph Walton

    This software is distributed under the terms of the GNU General Public
    License; either version 2 of the License, or (at your option) any
    later version.
*/

#include <stdio.h>

#include "nfsd.h"
#include "nfs.h"
#include "util.h"
#include "handle_list.h"
#include "file_cache.h"

#include <proto/dos.h>
#include <proto/timer.h>

/* How many entries can we keep cached? */
#define CACHE_SIZE 16

/* How long should we hold an unused file for? */
#define CACHE_SECONDS 60

/* This will be default initialised, so all entries will be free */
static struct CachedFile cfa[CACHE_SIZE];

static struct CachedFile *cfGetVacant(void);
static void cfClose(struct CachedFile *cf);

/* Fetch a CachedFile for the passed handle, or return NULL and fill in
    an appropriate error code */
struct CachedFile *cfGetByHandle(nfs_fh *fh, nfsstat *status)
{
    ULONG hash;
    int i;
    BPTR handle;
    struct LocalFile *lf;
    struct CachedFile *cf;

    hash = handleHash(fh);

    /* Is that file currently open? */
    for (i = 0 ; i < CACHE_SIZE ; i++) {
        if (cfa[i].cf_Handle && (cfa[i].cf_HandleHash == hash)
            && fh_match(fh, &cfa[i].cf_NFSHandle))
        {
            lf = find_handle(fh); /* Find the LocalFile for this file */
            if (lf) {
                cfa[i].cf_LocalFile = lf;
                global.infodata.id_BytesPerBlock = cfa[i].cf_BytesPerBlock;
                return &cfa[i];
            } else {
                cfClose(&cfa[i]);
                *status = NFSERR_STALE;
                return NULL;
            }
        }
    }

    /* It's not, so try to open it now */
    lf = get_by_handle(fh, status, ACCESS_READ);

    if (lf == NULL) {
        return NULL;
    }

    handle = OpenFromLock(lf->lf_Lock);
    if (handle) {
        lf->lf_Lock = NULL;
    } else {
        *status = NFS_wt_err(NFSERR_ISDIR);
    }

    release_lf(lf);

    if (!handle)
        return NULL; // And also that error

    /* We must create a new structure */
    cf = cfGetVacant();

    cf->cf_HandleHash = hash;
    cf->cf_NFSHandle = *fh; // Structure assignment
    cf->cf_Handle = handle;
    cf->cf_BytesPerBlock = global.infodata.id_BytesPerBlock;
    // We leave lf_LastUsed alone for now
    cf->cf_LocalFile = lf;

    return cf;
}

/* Find a vacant CachedFile to use */
static struct CachedFile *cfGetVacant()
{
    int earliest = 0, i;

    for (i = 0 ; i < CACHE_SIZE ; i++) {
        if (!cfa[i].cf_Handle) {
            return &cfa[i];
        }

        if (CmpTime(&cfa[earliest].cf_LastUsed, &cfa[i].cf_LastUsed) < 0)
        {
            earliest = i;
        }
    }

    cfClose(&cfa[earliest]);
    return &cfa[earliest];
}

/* Indicate that a CachedFile is no longer being used */
void cfRelease(struct CachedFile *cf)
{
    cf->cf_LocalFile = NULL;
    GetSysTime(&cf->cf_LastUsed);
    if (!global.keep_open) {
        cfClose(cf);
    }
}

static void cfClose(struct CachedFile *cf)
{
    if (cf->cf_Handle) {
        DEBUG(puts("Closing a file"));
        if (!Close(cf->cf_Handle)) {
            PrintFault(IoErr(), "Error closing file");
        }
        cf->cf_Handle = NULL;
    }
}

/* Indicate that we should let go of any file with this handle */
void cfCloseByHandle(nfs_fh *fh)
{
    ULONG hash = handleHash(fh);
    int i;
    for (i = 0 ; i < CACHE_SIZE ; i++) {
        if (cfa[i].cf_Handle && (cfa[i].cf_HandleHash == hash)
            && fh_match(fh, &cfa[i].cf_NFSHandle))
        {
            cfClose(&cfa[i]);
        }
    }
}

BOOL cfFlush()
{
    BOOL callAgain = FALSE;
    int i;
    struct timeval flush_before;

    GetSysTime(&flush_before);
    flush_before.tv_secs -= CACHE_SECONDS;

    for (i = 0 ; i < CACHE_SIZE ; i++) {
        if (cfa[i].cf_Handle) {
            if (CmpTime(&flush_before, &cfa[i].cf_LastUsed) < 0) {
                cfClose(&cfa[i]);
            } else {
                callAgain = TRUE;
            }
        }
    }

    return callAgain;
}

void cfCloseAll()
{
    int i;
    for (i = 0 ; i < CACHE_SIZE ; i++) {
        cfClose(&cfa[i]);
    }
}

