| /* 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_COMPOUNDFILEREADER |
| #define C_LUCY_CFREADERDIRHANDLE |
| #include "Lucy/Util/ToolSet.h" |
| |
| #include "Lucy/Store/CompoundFileReader.h" |
| #include "Lucy/Store/CompoundFileWriter.h" |
| #include "Lucy/Store/FileHandle.h" |
| #include "Lucy/Store/InStream.h" |
| #include "Lucy/Util/IndexFileNames.h" |
| #include "Lucy/Util/Json.h" |
| #include "Lucy/Util/StringHelper.h" |
| |
| CompoundFileReader* |
| CFReader_open(Folder *folder) { |
| CompoundFileReader *self |
| = (CompoundFileReader*)VTable_Make_Obj(COMPOUNDFILEREADER); |
| return CFReader_do_open(self, folder); |
| } |
| |
| CompoundFileReader* |
| CFReader_do_open(CompoundFileReader *self, Folder *folder) { |
| CharBuf *cfmeta_file = (CharBuf*)ZCB_WRAP_STR("cfmeta.json", 11); |
| Hash *metadata = (Hash*)Json_slurp_json((Folder*)folder, cfmeta_file); |
| Err *error = NULL; |
| |
| Folder_init((Folder*)self, Folder_Get_Path(folder)); |
| |
| // Parse metadata file. |
| if (!metadata || !Hash_Is_A(metadata, HASH)) { |
| error = Err_new(CB_newf("Can't read '%o' in '%o'", cfmeta_file, |
| Folder_Get_Path(folder))); |
| } |
| else { |
| Obj *format = Hash_Fetch_Str(metadata, "format", 6); |
| self->format = format ? (int32_t)Obj_To_I64(format) : 0; |
| self->records = (Hash*)INCREF(Hash_Fetch_Str(metadata, "files", 5)); |
| if (self->format < 1) { |
| error = Err_new(CB_newf("Corrupt %o file: Missing or invalid 'format'", |
| cfmeta_file)); |
| } |
| else if (self->format > CFWriter_current_file_format) { |
| error = Err_new(CB_newf("Unsupported compound file format: %i32 " |
| "(current = %i32", self->format, |
| CFWriter_current_file_format)); |
| } |
| else if (!self->records) { |
| error = Err_new(CB_newf("Corrupt %o file: missing 'files' key", |
| cfmeta_file)); |
| } |
| } |
| DECREF(metadata); |
| if (error) { |
| Err_set_error(error); |
| DECREF(self); |
| return NULL; |
| } |
| |
| // Open an instream which we'll clone over and over. |
| CharBuf *cf_file = (CharBuf*)ZCB_WRAP_STR("cf.dat", 6); |
| self->instream = Folder_Open_In(folder, cf_file); |
| if (!self->instream) { |
| ERR_ADD_FRAME(Err_get_error()); |
| DECREF(self); |
| return NULL; |
| } |
| |
| // Assign. |
| self->real_folder = (Folder*)INCREF(folder); |
| |
| // Strip directory name from filepaths for old format. |
| if (self->format == 1) { |
| VArray *files = Hash_Keys(self->records); |
| ZombieCharBuf *filename = ZCB_BLANK(); |
| ZombieCharBuf *folder_name |
| = IxFileNames_local_part(Folder_Get_Path(folder), ZCB_BLANK()); |
| size_t folder_name_len = ZCB_Length(folder_name); |
| |
| for (uint32_t i = 0, max = VA_Get_Size(files); i < max; i++) { |
| CharBuf *orig = (CharBuf*)VA_Fetch(files, i); |
| if (CB_Starts_With(orig, (CharBuf*)folder_name)) { |
| Obj *record = Hash_Delete(self->records, (Obj*)orig); |
| ZCB_Assign(filename, orig); |
| ZCB_Nip(filename, folder_name_len + sizeof(DIR_SEP) - 1); |
| Hash_Store(self->records, (Obj*)filename, (Obj*)record); |
| } |
| } |
| |
| DECREF(files); |
| } |
| |
| return self; |
| } |
| |
| void |
| CFReader_destroy(CompoundFileReader *self) { |
| DECREF(self->real_folder); |
| DECREF(self->instream); |
| DECREF(self->records); |
| SUPER_DESTROY(self, COMPOUNDFILEREADER); |
| } |
| |
| Folder* |
| CFReader_get_real_folder(CompoundFileReader *self) { |
| return self->real_folder; |
| } |
| |
| void |
| CFReader_set_path(CompoundFileReader *self, const CharBuf *path) { |
| Folder_Set_Path(self->real_folder, path); |
| Folder_set_path((Folder*)self, path); |
| } |
| |
| FileHandle* |
| CFReader_local_open_filehandle(CompoundFileReader *self, |
| const CharBuf *name, uint32_t flags) { |
| Hash *entry = (Hash*)Hash_Fetch(self->records, (Obj*)name); |
| FileHandle *fh = NULL; |
| |
| if (entry) { |
| Err_set_error(Err_new(CB_newf("Can't open FileHandle for virtual file %o in '%o'", |
| name, self->path))); |
| } |
| else { |
| fh = Folder_Local_Open_FileHandle(self->real_folder, name, flags); |
| if (!fh) { |
| ERR_ADD_FRAME(Err_get_error()); |
| } |
| } |
| |
| return fh; |
| } |
| |
| bool_t |
| CFReader_local_delete(CompoundFileReader *self, const CharBuf *name) { |
| Hash *record = (Hash*)Hash_Delete(self->records, (Obj*)name); |
| DECREF(record); |
| |
| if (record == NULL) { |
| return Folder_Local_Delete(self->real_folder, name); |
| } |
| else { |
| // Once the number of virtual files falls to 0, remove the compound |
| // files. |
| if (Hash_Get_Size(self->records) == 0) { |
| CharBuf *cf_file = (CharBuf*)ZCB_WRAP_STR("cf.dat", 6); |
| if (!Folder_Delete(self->real_folder, cf_file)) { |
| return false; |
| } |
| CharBuf *cfmeta_file = (CharBuf*)ZCB_WRAP_STR("cfmeta.json", 11); |
| if (!Folder_Delete(self->real_folder, cfmeta_file)) { |
| return false; |
| |
| } |
| } |
| return true; |
| } |
| } |
| |
| InStream* |
| CFReader_local_open_in(CompoundFileReader *self, const CharBuf *name) { |
| Hash *entry = (Hash*)Hash_Fetch(self->records, (Obj*)name); |
| |
| if (!entry) { |
| InStream *instream = Folder_Local_Open_In(self->real_folder, name); |
| if (!instream) { |
| ERR_ADD_FRAME(Err_get_error()); |
| } |
| return instream; |
| } |
| else { |
| Obj *len = Hash_Fetch_Str(entry, "length", 6); |
| Obj *offset = Hash_Fetch_Str(entry, "offset", 6); |
| if (!len || !offset) { |
| Err_set_error(Err_new(CB_newf("Malformed entry for '%o' in '%o'", |
| name, Folder_Get_Path(self->real_folder)))); |
| return NULL; |
| } |
| else if (CB_Get_Size(self->path)) { |
| CharBuf *fullpath = CB_newf("%o/%o", self->path, name); |
| InStream *instream = InStream_Reopen(self->instream, fullpath, |
| Obj_To_I64(offset), Obj_To_I64(len)); |
| DECREF(fullpath); |
| return instream; |
| } |
| else { |
| return InStream_Reopen(self->instream, name, Obj_To_I64(offset), |
| Obj_To_I64(len)); |
| } |
| } |
| } |
| |
| bool_t |
| CFReader_local_exists(CompoundFileReader *self, const CharBuf *name) { |
| if (Hash_Fetch(self->records, (Obj*)name)) { return true; } |
| if (Folder_Local_Exists(self->real_folder, name)) { return true; } |
| return false; |
| } |
| |
| bool_t |
| CFReader_local_is_directory(CompoundFileReader *self, const CharBuf *name) { |
| if (Hash_Fetch(self->records, (Obj*)name)) { return false; } |
| if (Folder_Local_Is_Directory(self->real_folder, name)) { return true; } |
| return false; |
| } |
| |
| void |
| CFReader_close(CompoundFileReader *self) { |
| InStream_Close(self->instream); |
| } |
| |
| bool_t |
| CFReader_local_mkdir(CompoundFileReader *self, const CharBuf *name) { |
| if (Hash_Fetch(self->records, (Obj*)name)) { |
| Err_set_error(Err_new(CB_newf("Can't MkDir: '%o' exists", name))); |
| return false; |
| } |
| else { |
| bool_t result = Folder_Local_MkDir(self->real_folder, name); |
| if (!result) { ERR_ADD_FRAME(Err_get_error()); } |
| return result; |
| } |
| } |
| |
| Folder* |
| CFReader_local_find_folder(CompoundFileReader *self, const CharBuf *name) { |
| if (Hash_Fetch(self->records, (Obj*)name)) { return false; } |
| return Folder_Local_Find_Folder(self->real_folder, name); |
| } |
| |
| DirHandle* |
| CFReader_local_open_dir(CompoundFileReader *self) { |
| return (DirHandle*)CFReaderDH_new(self); |
| } |
| |
| /****************************************************************************/ |
| |
| CFReaderDirHandle* |
| CFReaderDH_new(CompoundFileReader *cf_reader) { |
| CFReaderDirHandle *self |
| = (CFReaderDirHandle*)VTable_Make_Obj(CFREADERDIRHANDLE); |
| return CFReaderDH_init(self, cf_reader); |
| } |
| |
| CFReaderDirHandle* |
| CFReaderDH_init(CFReaderDirHandle *self, CompoundFileReader *cf_reader) { |
| DH_init((DirHandle*)self, CFReader_Get_Path(cf_reader)); |
| self->cf_reader = (CompoundFileReader*)INCREF(cf_reader); |
| self->elems = Hash_Keys(self->cf_reader->records); |
| self->tick = -1; |
| // Accumulate entries from real Folder. |
| DirHandle *dh = Folder_Local_Open_Dir(self->cf_reader->real_folder); |
| CharBuf *entry = DH_Get_Entry(dh); |
| while (DH_Next(dh)) { |
| VA_Push(self->elems, (Obj*)CB_Clone(entry)); |
| } |
| DECREF(dh); |
| return self; |
| } |
| |
| bool_t |
| CFReaderDH_close(CFReaderDirHandle *self) { |
| if (self->elems) { |
| VA_Dec_RefCount(self->elems); |
| self->elems = NULL; |
| } |
| if (self->cf_reader) { |
| CFReader_Dec_RefCount(self->cf_reader); |
| self->cf_reader = NULL; |
| } |
| return true; |
| } |
| |
| bool_t |
| CFReaderDH_next(CFReaderDirHandle *self) { |
| if (self->elems) { |
| self->tick++; |
| if (self->tick < (int32_t)VA_Get_Size(self->elems)) { |
| CharBuf *path = (CharBuf*)CERTIFY( |
| VA_Fetch(self->elems, self->tick), CHARBUF); |
| CB_Mimic(self->entry, (Obj*)path); |
| return true; |
| } |
| else { |
| self->tick--; |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| bool_t |
| CFReaderDH_entry_is_dir(CFReaderDirHandle *self) { |
| if (self->elems) { |
| CharBuf *name = (CharBuf*)VA_Fetch(self->elems, self->tick); |
| if (name) { |
| return CFReader_Local_Is_Directory(self->cf_reader, name); |
| } |
| } |
| return false; |
| } |
| |
| |