blob: 557bddeda5dd6f2dec6d232ca45177ac52d0c0f2 [file] [log] [blame]
/* 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_FOLDER
#include "Lucy/Util/ToolSet.h"
#include <ctype.h>
#include <limits.h>
#include "charmony.h"
#include "Clownfish/Blob.h"
#include "Lucy/Store/Folder.h"
#include "Lucy/Store/CompoundFileReader.h"
#include "Lucy/Store/CompoundFileWriter.h"
#include "Lucy/Store/DirHandle.h"
#include "Lucy/Store/FileHandle.h"
#include "Lucy/Store/InStream.h"
#include "Lucy/Store/OutStream.h"
#include "Lucy/Util/IndexFileNames.h"
Folder*
Folder_init(Folder *self, String *path) {
FolderIVARS *const ivars = Folder_IVARS(self);
// Init.
ivars->entries = Hash_new(16);
// Copy.
if (path == NULL) {
ivars->path = Str_new_from_trusted_utf8("", 0);
}
else {
// Copy path, strip trailing slash or equivalent.
if (Str_Ends_With_Utf8(path, CHY_DIR_SEP, strlen(CHY_DIR_SEP))) {
ivars->path = Str_SubString(path, 0, Str_Length(path) - 1);
}
else {
ivars->path = Str_Clone(path);
}
}
ABSTRACT_CLASS_CHECK(self, FOLDER);
return self;
}
void
Folder_Destroy_IMP(Folder *self) {
FolderIVARS *const ivars = Folder_IVARS(self);
DECREF(ivars->path);
DECREF(ivars->entries);
SUPER_DESTROY(self, FOLDER);
}
InStream*
Folder_Open_In_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
InStream *instream = NULL;
if (enclosing_folder) {
String *name = IxFileNames_local_part(path);
instream = Folder_Local_Open_In(enclosing_folder, name);
if (!instream) {
ERR_ADD_FRAME(Err_get_error());
}
DECREF(name);
}
else {
Err_set_error(Err_new(Str_newf("Invalid path: '%o'", path)));
}
return instream;
}
/* This method exists as a hook for CompoundFileReader to override; it is
* necessary because calling CFReader_Local_Open_FileHandle() won't find
* virtual files. No other class should need to override it. */
InStream*
Folder_Local_Open_In_IMP(Folder *self, String *name) {
FileHandle *fh = Folder_Local_Open_FileHandle(self, name, FH_READ_ONLY);
InStream *instream = NULL;
if (fh) {
instream = InStream_open((Obj*)fh);
DECREF(fh);
if (!instream) {
ERR_ADD_FRAME(Err_get_error());
}
}
else {
ERR_ADD_FRAME(Err_get_error());
}
return instream;
}
OutStream*
Folder_Open_Out_IMP(Folder *self, String *path) {
const uint32_t flags = FH_WRITE_ONLY | FH_CREATE | FH_EXCLUSIVE;
FileHandle *fh = Folder_Open_FileHandle(self, path, flags);
OutStream *outstream = NULL;
if (fh) {
outstream = OutStream_open((Obj*)fh);
DECREF(fh);
if (!outstream) {
ERR_ADD_FRAME(Err_get_error());
}
}
else {
ERR_ADD_FRAME(Err_get_error());
}
return outstream;
}
FileHandle*
Folder_Open_FileHandle_IMP(Folder *self, String *path,
uint32_t flags) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
FileHandle *fh = NULL;
if (enclosing_folder) {
String *name = IxFileNames_local_part(path);
fh = Folder_Local_Open_FileHandle(enclosing_folder, name, flags);
if (!fh) {
ERR_ADD_FRAME(Err_get_error());
}
DECREF(name);
}
else {
Err_set_error(Err_new(Str_newf("Invalid path: '%o'", path)));
}
return fh;
}
bool
Folder_Delete_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
if (enclosing_folder) {
String *name = IxFileNames_local_part(path);
bool result = Folder_Local_Delete(enclosing_folder, name);
DECREF(name);
return result;
}
else {
return false;
}
}
bool
Folder_Delete_Tree_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
// Don't allow Folder to delete itself.
if (!path || !Str_Get_Size(path)) { return false; }
if (enclosing_folder) {
String *local = IxFileNames_local_part(path);
if (Folder_Local_Is_Directory(enclosing_folder, local)) {
Folder *inner_folder
= Folder_Local_Find_Folder(enclosing_folder, local);
DirHandle *dh = Folder_Local_Open_Dir(inner_folder);
if (dh) {
Vector *files = Vec_new(20);
Vector *dirs = Vec_new(20);
while (DH_Next(dh)) {
String *entry = DH_Get_Entry(dh);
Vec_Push(files, (Obj*)Str_Clone(entry));
if (DH_Entry_Is_Dir(dh) && !DH_Entry_Is_Symlink(dh)) {
Vec_Push(dirs, (Obj*)Str_Clone(entry));
}
DECREF(entry);
}
for (size_t i = 0, max = Vec_Get_Size(dirs); i < max; i++) {
String *name = (String*)Vec_Fetch(files, i);
bool success = Folder_Delete_Tree(inner_folder, name);
if (!success && Folder_Local_Exists(inner_folder, name)) {
break;
}
}
for (size_t i = 0, max = Vec_Get_Size(files); i < max; i++) {
String *name = (String*)Vec_Fetch(files, i);
bool success = Folder_Local_Delete(inner_folder, name);
if (!success && Folder_Local_Exists(inner_folder, name)) {
break;
}
}
DECREF(dirs);
DECREF(files);
DECREF(dh);
}
}
bool retval = Folder_Local_Delete(enclosing_folder, local);
DECREF(local);
return retval;
}
else {
// Return failure if the entry wasn't there in the first place.
return false;
}
}
static bool
S_is_updir(String *path) {
if (Str_Equals_Utf8(path, ".", 1) || Str_Equals_Utf8(path, "..", 2)) {
return true;
}
else {
return false;
}
}
static void
S_add_to_file_list(Folder *self, Vector *list, String *dir,
String *path) {
DirHandle *dh = Folder_Open_Dir(self, dir);
if (!dh) {
RETHROW(INCREF(Err_get_error()));
}
while (DH_Next(dh)) { // Updates entry
String *entry = DH_Get_Entry(dh);
if (!S_is_updir(entry)) {
String *relpath = path && Str_Get_Size(path)
? Str_newf("%o/%o", path, entry)
: Str_Clone(entry);
if (Vec_Get_Size(list) == Vec_Get_Capacity(list)) {
Vec_Grow(list, Vec_Get_Size(list) * 2);
}
Vec_Push(list, (Obj*)relpath);
if (DH_Entry_Is_Dir(dh) && !DH_Entry_Is_Symlink(dh)) {
String *subdir = Str_Get_Size(dir)
? Str_newf("%o/%o", dir, entry)
: Str_Clone(entry);
S_add_to_file_list(self, list, subdir, relpath); // recurse
DECREF(subdir);
}
}
DECREF(entry);
}
if (!DH_Close(dh)) {
RETHROW(INCREF(Err_get_error()));
}
DECREF(dh);
}
DirHandle*
Folder_Open_Dir_IMP(Folder *self, String *path) {
DirHandle *dh = NULL;
Folder *folder;
if (path) {
folder = Folder_Find_Folder(self, path);
}
else {
String *empty = SSTR_BLANK();
folder = Folder_Find_Folder(self, empty);
}
if (!folder) {
Err_set_error(Err_new(Str_newf("Invalid path: '%o'", path)));
}
else {
dh = Folder_Local_Open_Dir(folder);
if (!dh) {
ERR_ADD_FRAME(Err_get_error());
}
}
return dh;
}
bool
Folder_MkDir_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
bool result = false;
if (!Str_Get_Size(path)) {
Err_set_error(Err_new(Str_newf("Invalid path: '%o'", path)));
}
else if (!enclosing_folder) {
Err_set_error(Err_new(Str_newf("Can't recursively create dir %o",
path)));
}
else {
String *name = IxFileNames_local_part(path);
result = Folder_Local_MkDir(enclosing_folder, name);
if (!result) {
ERR_ADD_FRAME(Err_get_error());
}
DECREF(name);
}
return result;
}
bool
Folder_Exists_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
bool retval = false;
if (enclosing_folder) {
String *name = IxFileNames_local_part(path);
if (Folder_Local_Exists(enclosing_folder, name)) {
retval = true;
}
DECREF(name);
}
return retval;
}
bool
Folder_Is_Directory_IMP(Folder *self, String *path) {
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
bool retval = false;
if (enclosing_folder) {
String *name = IxFileNames_local_part(path);
if (Folder_Local_Is_Directory(enclosing_folder, name)) {
retval = true;
}
DECREF(name);
}
return retval;
}
Vector*
Folder_List_IMP(Folder *self, String *path) {
Folder *local_folder = Folder_Find_Folder(self, path);
Vector *list = NULL;
DirHandle *dh = Folder_Local_Open_Dir(local_folder);
if (dh) {
list = Vec_new(32);
while (DH_Next(dh)) {
String *entry = DH_Get_Entry(dh);
Vec_Push(list, (Obj*)Str_Clone(entry));
DECREF(entry);
}
DECREF(dh);
}
else {
ERR_ADD_FRAME(Err_get_error());
}
return list;
}
Vector*
Folder_List_R_IMP(Folder *self, String *path) {
Folder *local_folder = Folder_Find_Folder(self, path);
Vector *list = Vec_new(0);
if (local_folder) {
String *dir = Str_new_from_trusted_utf8("", 0);
S_add_to_file_list(local_folder, list, dir, path);
DECREF(dir);
}
return list;
}
Blob*
Folder_Slurp_File_IMP(Folder *self, String *path) {
InStream *instream = Folder_Open_In(self, path);
Blob *retval = NULL;
if (!instream) {
RETHROW(INCREF(Err_get_error()));
}
else {
int64_t length = InStream_Length(instream);
if (SIZE_MAX < INT64_MAX && length >= ((int64_t)SIZE_MAX)) {
InStream_Close(instream);
DECREF(instream);
THROW(ERR, "File %o is too big to slurp (%i64 bytes)", path,
length);
}
else {
size_t size = (size_t)length;
char *ptr = (char*)MALLOCATE((size_t)size + 1);
InStream_Read_Bytes(instream, ptr, size);
ptr[size] = '\0';
retval = Blob_new_steal(ptr, size);
InStream_Close(instream);
DECREF(instream);
}
}
return retval;
}
String*
Folder_Get_Path_IMP(Folder *self) {
return Folder_IVARS(self)->path;
}
void
Folder_Set_Path_IMP(Folder *self, String *path) {
FolderIVARS *const ivars = Folder_IVARS(self);
String *temp = ivars->path;
ivars->path = Str_Clone(path);
DECREF(temp);
}
void
Folder_Consolidate_IMP(Folder *self, String *path) {
Folder *folder = Folder_Find_Folder(self, path);
Folder *enclosing_folder = Folder_Enclosing_Folder(self, path);
if (!folder) {
THROW(ERR, "Can't consolidate %o", path);
}
else if (Folder_is_a(folder, COMPOUNDFILEREADER)) {
THROW(ERR, "Can't consolidate %o twice", path);
}
else {
CompoundFileWriter *cf_writer = CFWriter_new(folder);
CFWriter_Consolidate(cf_writer);
DECREF(cf_writer);
if (Str_Get_Size(path)) {
CompoundFileReader *cf_reader = CFReader_open(folder);
if (!cf_reader) { RETHROW(INCREF(Err_get_error())); }
Hash *entries = Folder_IVARS(enclosing_folder)->entries;
String *name = IxFileNames_local_part(path);
Hash_Store(entries, name, (Obj*)cf_reader);
DECREF(name);
}
}
}
static Folder*
S_enclosing_folder(Folder *self, StringIterator *path) {
int32_t code_point;
// Find first component of the file path.
String *path_component = NULL;
StringIterator *iter = StrIter_Clone(path);
while (STR_OOB != (code_point = StrIter_Next(iter))) {
if (code_point == '/' && StrIter_Has_Next(iter)) {
StrIter_Recede(iter, 1);
path_component = StrIter_crop(path, iter);
StrIter_Advance(iter, 1);
StrIter_Assign(path, iter);
break;
}
}
DECREF(iter);
// If we've eaten up the entire filepath, self is enclosing folder.
if (!path_component) { return self; }
Folder *local_folder = Folder_Local_Find_Folder(self, path_component);
DECREF(path_component);
if (!local_folder) {
/* This element of the filepath doesn't exist, or it's not a
* directory. However, there are filepath characters left over,
* implying that this component ought to be a directory -- so the
* original file path is invalid. */
return NULL;
}
// This file path component is a folder. Recurse into it.
return S_enclosing_folder(local_folder, path);
}
Folder*
Folder_Enclosing_Folder_IMP(Folder *self, String *path) {
StringIterator *iter = Str_Top(path);
Folder *folder = S_enclosing_folder(self, iter);
DECREF(iter);
return folder;
}
Folder*
Folder_Find_Folder_IMP(Folder *self, String *path) {
if (!path || !Str_Get_Size(path)) {
return self;
}
else {
Folder *folder = NULL;
StringIterator *iter = Str_Top(path);
Folder *enclosing_folder = S_enclosing_folder(self, iter);
if (enclosing_folder) {
String *folder_name = StrIter_crop(iter, NULL);
folder = Folder_Local_Find_Folder(enclosing_folder, folder_name);
DECREF(folder_name);
}
DECREF(iter);
return folder;
}
}