| /* 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; |
| } |
| } |
| |
| |