The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define C_LUCY_RAMFOLDER
#include "Lucy/Util/ToolSet.h"

#include "Lucy/Store/RAMFolder.h"
#include "Lucy/Store/CompoundFileReader.h"
#include "Lucy/Store/InStream.h"
#include "Lucy/Store/OutStream.h"
#include "Lucy/Store/RAMDirHandle.h"
#include "Lucy/Store/RAMFile.h"
#include "Lucy/Store/RAMFileHandle.h"
#include "Lucy/Util/IndexFileNames.h"

// Return the concatenation of the Folder's path and the supplied path.
static String*
S_fullpath(RAMFolder *self, String *path);

RAMFolder*
RAMFolder_new(String *path) {
    RAMFolder *self = (RAMFolder*)Class_Make_Obj(RAMFOLDER);
    return RAMFolder_init(self, path);
}

RAMFolder*
RAMFolder_init(RAMFolder *self, String *path) {
    Folder_init((Folder*)self, path);
    return self;
}

void
RAMFolder_Initialize_IMP(RAMFolder *self) {
    UNUSED_VAR(self);
}

bool
RAMFolder_Check_IMP(RAMFolder *self) {
    UNUSED_VAR(self);
    return true;
}

bool
RAMFolder_Local_MkDir_IMP(RAMFolder *self, String *name) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    if (Hash_Fetch(ivars->entries, name)) {
        Err_set_error(Err_new(Str_newf("Can't MkDir, '%o' already exists",
                                       name)));
        return false;
    }
    else {
        String *fullpath = S_fullpath(self, name);
        Hash_Store(ivars->entries, name, (Obj*)RAMFolder_new(fullpath));
        DECREF(fullpath);
        return true;
    }
}

FileHandle*
RAMFolder_Local_Open_FileHandle_IMP(RAMFolder *self, String *name,
                                    uint32_t flags) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    RAMFileHandle *fh;
    String *fullpath = S_fullpath(self, name);
    RAMFile *file = (RAMFile*)Hash_Fetch(ivars->entries, name);
    bool can_create
        = (flags & (FH_WRITE_ONLY | FH_CREATE)) == (FH_WRITE_ONLY | FH_CREATE)
          ? true : false;

    // Make sure the filepath isn't a directory, and that it either exists
    // or we have permission to create it.
    if (file) {
        if (!RAMFile_is_a(file, RAMFILE)) {
            Err_set_error(Err_new(Str_newf("Not a file: '%o'", fullpath)));
            DECREF(fullpath);
            return NULL;
        }
    }
    else if (!can_create) {
        Err_set_error(Err_new(Str_newf("File not found: '%o'", fullpath)));
        DECREF(fullpath);
        return NULL;
    }

    // Open the file and store it if it was just created.
    fh = RAMFH_open(fullpath, flags, file);
    if (fh) {
        if (!file) {
            file = RAMFH_Get_File(fh);
            Hash_Store(ivars->entries, name, INCREF(file));
        }
    }
    else {
        Err *error = Err_get_error();
        ERR_ADD_FRAME(error);
    }

    DECREF(fullpath);

    return (FileHandle*)fh;
}

DirHandle*
RAMFolder_Local_Open_Dir_IMP(RAMFolder *self) {
    RAMDirHandle *dh = RAMDH_new(self);
    if (!dh) { ERR_ADD_FRAME(Err_get_error()); }
    return (DirHandle*)dh;
}

bool
RAMFolder_Local_Exists_IMP(RAMFolder *self, String *name) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    return !!Hash_Fetch(ivars->entries, name);
}

bool
RAMFolder_Local_Is_Directory_IMP(RAMFolder *self, String *name) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    Obj *entry = Hash_Fetch(ivars->entries, name);
    if (entry && Obj_is_a(entry, FOLDER)) { return true; }
    return false;
}

#define OP_RENAME    1
#define OP_HARD_LINK 2

static bool
S_rename_or_hard_link(RAMFolder *self, String* from, String *to,
                      Folder *from_folder, Folder *to_folder,
                      String *from_name, String *to_name,
                      int op) {
    Obj       *elem              = NULL;
    RAMFolder *inner_from_folder = NULL;
    RAMFolder *inner_to_folder   = NULL;
    UNUSED_VAR(self);

    // Make sure the source and destination folders exist.
    if (!from_folder) {
        Err_set_error(Err_new(Str_newf("File not found: '%o'", from)));
        return false;
    }
    if (!to_folder) {
        Err_set_error(Err_new(Str_newf("Invalid file path (can't find dir): '%o'",
                                       to)));
        return false;
    }

    // Extract RAMFolders from compound reader wrappers, if necessary.
    if (Folder_is_a(from_folder, COMPOUNDFILEREADER)) {
        inner_from_folder = (RAMFolder*)CFReader_Get_Real_Folder(
                                (CompoundFileReader*)from_folder);
    }
    else {
        inner_from_folder = (RAMFolder*)from_folder;
    }
    if (Folder_is_a(to_folder, COMPOUNDFILEREADER)) {
        inner_to_folder = (RAMFolder*)CFReader_Get_Real_Folder(
                              (CompoundFileReader*)to_folder);
    }
    else {
        inner_to_folder = (RAMFolder*)to_folder;
    }
    if (!RAMFolder_is_a(inner_from_folder, RAMFOLDER)) {
        Err_set_error(Err_new(Str_newf("Not a RAMFolder, but a '%o'",
                                       Obj_get_class_name((Obj*)inner_from_folder))));
        return false;
    }
    if (!RAMFolder_is_a(inner_to_folder, RAMFOLDER)) {
        Err_set_error(Err_new(Str_newf("Not a RAMFolder, but a '%o'",
                                       Obj_get_class_name((Obj*)inner_to_folder))));
        return false;
    }

    // Find the original element.
    elem = Hash_Fetch(RAMFolder_IVARS(inner_from_folder)->entries, from_name);
    if (!elem) {
        if (Folder_is_a(from_folder, COMPOUNDFILEREADER)
            && Folder_Local_Exists(from_folder, from_name)
           ) {
            Err_set_error(Err_new(Str_newf("Source file '%o' is virtual",
                                           from)));
        }
        else {
            Err_set_error(Err_new(Str_newf("File not found: '%o'", from)));
        }
        return false;
    }

    // Execute the rename/hard-link.
    if (op == OP_RENAME) {
        Obj *existing = Hash_Fetch(RAMFolder_IVARS(inner_to_folder)->entries,
                                   to_name);
        if (existing) {
            bool conflict = false;

            // Return success fast if file is copied on top of itself.
            if (inner_from_folder == inner_to_folder
                && Str_Equals(from_name, (Obj*)to_name)
               ) {
                return true;
            }

            // Don't allow clobbering of different entry type.
            if (Obj_is_a(elem, RAMFILE)) {
                if (!Obj_is_a(existing, RAMFILE)) {
                    conflict = true;
                }
            }
            else if (Obj_is_a(elem, FOLDER)) {
                if (!Obj_is_a(existing, FOLDER)) {
                    conflict = true;
                }
            }
            if (conflict) {
                Err_set_error(Err_new(Str_newf("Can't clobber a %o with a %o",
                                               Obj_get_class_name(existing),
                                               Obj_get_class_name(elem))));
                return false;
            }
        }

        // Perform the store first, then the delete. Inform Folder objects
        // about the relocation.
        Hash_Store(RAMFolder_IVARS(inner_to_folder)->entries,
                   to_name, INCREF(elem));
        DECREF(Hash_Delete(RAMFolder_IVARS(inner_from_folder)->entries,
                           from_name));
        if (Obj_is_a(elem, FOLDER)) {
            String *newpath = S_fullpath(inner_to_folder, to_name);
            Folder_Set_Path((Folder*)elem, newpath);
            DECREF(newpath);
        }
    }
    else if (op == OP_HARD_LINK) {
        if (!Obj_is_a(elem, RAMFILE)) {
            Err_set_error(Err_new(Str_newf("'%o' isn't a file, it's a %o",
                                           from, Obj_get_class_name(elem))));
            return false;
        }
        else {
            Obj *existing
                = Hash_Fetch(RAMFolder_IVARS(inner_to_folder)->entries,
                             to_name);
            if (existing) {
                Err_set_error(Err_new(Str_newf("'%o' already exists", to)));
                return false;
            }
            else {
                Hash_Store(RAMFolder_IVARS(inner_to_folder)->entries,
                           to_name, INCREF(elem));
            }
        }
    }
    else {
        THROW(ERR, "Unexpected op: %i32", (int32_t)op);
    }

    return true;
}

bool
RAMFolder_Rename_IMP(RAMFolder *self, String* from,
                     String *to) {
    Folder *from_folder = RAMFolder_Enclosing_Folder(self, from);
    Folder *to_folder   = RAMFolder_Enclosing_Folder(self, to);
    String *from_name   = IxFileNames_local_part(from);
    String *to_name     = IxFileNames_local_part(to);
    bool result = S_rename_or_hard_link(self, from, to, from_folder, to_folder,
                                        from_name, to_name, OP_RENAME);
    if (!result) { ERR_ADD_FRAME(Err_get_error()); }
    DECREF(to_name);
    DECREF(from_name);
    return result;
}

bool
RAMFolder_Hard_Link_IMP(RAMFolder *self, String *from,
                        String *to) {
    Folder *from_folder = RAMFolder_Enclosing_Folder(self, from);
    Folder *to_folder   = RAMFolder_Enclosing_Folder(self, to);
    String *from_name   = IxFileNames_local_part(from);
    String *to_name     = IxFileNames_local_part(to);
    bool result = S_rename_or_hard_link(self, from, to, from_folder, to_folder,
                                        from_name, to_name, OP_HARD_LINK);
    if (!result) { ERR_ADD_FRAME(Err_get_error()); }
    DECREF(to_name);
    DECREF(from_name);
    return result;
}

bool
RAMFolder_Local_Delete_IMP(RAMFolder *self, String *name) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    Obj *entry = Hash_Fetch(ivars->entries, name);
    if (entry) {
        if (Obj_is_a(entry, RAMFILE)) {
            ;
        }
        else if (Obj_is_a(entry, FOLDER)) {
            RAMFolder *inner_folder;
            if (Obj_is_a(entry, COMPOUNDFILEREADER)) {
                inner_folder = (RAMFolder*)CERTIFY(
                                   CFReader_Get_Real_Folder((CompoundFileReader*)entry),
                                   RAMFOLDER);
            }
            else {
                inner_folder = (RAMFolder*)CERTIFY(entry, RAMFOLDER);
            }
            if (Hash_Get_Size(RAMFolder_IVARS(inner_folder)->entries)) {
                // Can't delete non-empty dir.
                return false;
            }
        }
        else {
            return false;
        }
        DECREF(Hash_Delete(ivars->entries, name));
        return true;
    }
    else {
        return false;
    }
}

Folder*
RAMFolder_Local_Find_Folder_IMP(RAMFolder *self, String *path) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    Folder *local_folder = (Folder*)Hash_Fetch(ivars->entries, path);
    if (local_folder && Folder_is_a(local_folder, FOLDER)) {
        return local_folder;
    }
    return NULL;
}

void
RAMFolder_Close_IMP(RAMFolder *self) {
    UNUSED_VAR(self);
}

static String*
S_fullpath(RAMFolder *self, String *path) {
    RAMFolderIVARS *const ivars = RAMFolder_IVARS(self);
    if (Str_Get_Size(ivars->path)) {
        return Str_newf("%o/%o", ivars->path, path);
    }
    else {
        return Str_Clone(path);
    }
}