/* 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 "charmony.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"
CompoundFileReader*
CFReader_open(Folder *folder) {
CompoundFileReader *self
= (CompoundFileReader*)Class_Make_Obj(COMPOUNDFILEREADER);
return CFReader_do_open(self, folder);
}
CompoundFileReader*
CFReader_do_open(CompoundFileReader *self, Folder *folder) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
String *cfmeta_file = SSTR_WRAP_C("cfmeta.json");
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(Str_newf("Can't read '%o' in '%o'", cfmeta_file,
Folder_Get_Path(folder)));
}
else {
Obj *format = Hash_Fetch_Utf8(metadata, "format", 6);
ivars->format = format ? (int32_t)Json_obj_to_i64(format) : 0;
ivars->records = (Hash*)INCREF(Hash_Fetch_Utf8(metadata, "files", 5));
if (ivars->format < 1) {
error = Err_new(Str_newf("Corrupt %o file: Missing or invalid 'format'",
cfmeta_file));
}
else if (ivars->format > CFWriter_current_file_format) {
error = Err_new(Str_newf("Unsupported compound file format: %i32 "
"(current = %i32", ivars->format,
CFWriter_current_file_format));
}
else if (!ivars->records) {
error = Err_new(Str_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.
String *cf_file = SSTR_WRAP_C("cf.dat");
ivars->instream = Folder_Open_In(folder, cf_file);
if (!ivars->instream) {
ERR_ADD_FRAME(Err_get_error());
DECREF(self);
return NULL;
}
// Assign.
ivars->real_folder = (Folder*)INCREF(folder);
// Strip directory name from filepaths for old format.
if (ivars->format == 1) {
Vector *files = Hash_Keys(ivars->records);
String *folder_name = IxFileNames_local_part(Folder_Get_Path(folder));
size_t folder_name_len = Str_Length(folder_name);
for (size_t i = 0, max = Vec_Get_Size(files); i < max; i++) {
String *orig = (String*)Vec_Fetch(files, i);
if (Str_Starts_With(orig, folder_name)) {
Obj *record = Hash_Delete(ivars->records, orig);
size_t offset = folder_name_len + sizeof(CHY_DIR_SEP) - 1;
size_t len = Str_Length(orig) - offset;
String *filename = Str_SubString(orig, offset, len);
Hash_Store(ivars->records, filename, (Obj*)record);
DECREF(filename);
}
}
DECREF(folder_name);
DECREF(files);
}
return self;
}
void
CFReader_Destroy_IMP(CompoundFileReader *self) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
DECREF(ivars->real_folder);
DECREF(ivars->instream);
DECREF(ivars->records);
SUPER_DESTROY(self, COMPOUNDFILEREADER);
}
Folder*
CFReader_Get_Real_Folder_IMP(CompoundFileReader *self) {
return CFReader_IVARS(self)->real_folder;
}
void
CFReader_Set_Path_IMP(CompoundFileReader *self, String *path) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
Folder_Set_Path(ivars->real_folder, path);
CFReader_Set_Path_t super_set_path
= (CFReader_Set_Path_t)SUPER_METHOD_PTR(COMPOUNDFILEREADER,
LUCY_CFReader_Set_Path);
super_set_path(self, path);
}
FileHandle*
CFReader_Local_Open_FileHandle_IMP(CompoundFileReader *self,
String *name, uint32_t flags) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
Hash *entry = (Hash*)Hash_Fetch(ivars->records, name);
FileHandle *fh = NULL;
if (entry) {
Err_set_error(Err_new(Str_newf("Can't open FileHandle for virtual file %o in '%o'",
name, ivars->path)));
}
else {
fh = Folder_Local_Open_FileHandle(ivars->real_folder, name, flags);
if (!fh) {
ERR_ADD_FRAME(Err_get_error());
}
}
return fh;
}
bool
CFReader_Local_Delete_IMP(CompoundFileReader *self, String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
Hash *record = (Hash*)Hash_Delete(ivars->records, name);
DECREF(record);
if (record == NULL) {
return Folder_Local_Delete(ivars->real_folder, name);
}
else {
// Once the number of virtual files falls to 0, remove the compound
// files.
if (Hash_Get_Size(ivars->records) == 0) {
String *cf_file = SSTR_WRAP_C("cf.dat");
if (!Folder_Delete(ivars->real_folder, cf_file)) {
return false;
}
String *cfmeta_file = SSTR_WRAP_C("cfmeta.json");
if (!Folder_Delete(ivars->real_folder, cfmeta_file)) {
return false;
}
}
return true;
}
}
InStream*
CFReader_Local_Open_In_IMP(CompoundFileReader *self, String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
Hash *entry = (Hash*)Hash_Fetch(ivars->records, name);
if (!entry) {
InStream *instream = Folder_Local_Open_In(ivars->real_folder, name);
if (!instream) {
ERR_ADD_FRAME(Err_get_error());
}
return instream;
}
else {
Obj *len = Hash_Fetch_Utf8(entry, "length", 6);
Obj *offset = Hash_Fetch_Utf8(entry, "offset", 6);
if (!len || !offset) {
Err_set_error(Err_new(Str_newf("Malformed entry for '%o' in '%o'",
name, Folder_Get_Path(ivars->real_folder))));
return NULL;
}
else if (Str_Get_Size(ivars->path)) {
String *fullpath = Str_newf("%o/%o", ivars->path, name);
InStream *instream = InStream_Reopen(ivars->instream, fullpath,
Json_obj_to_i64(offset), Json_obj_to_i64(len));
DECREF(fullpath);
return instream;
}
else {
return InStream_Reopen(ivars->instream, name, Json_obj_to_i64(offset),
Json_obj_to_i64(len));
}
}
}
bool
CFReader_Local_Exists_IMP(CompoundFileReader *self, String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
if (Hash_Fetch(ivars->records, name)) { return true; }
if (Folder_Local_Exists(ivars->real_folder, name)) { return true; }
return false;
}
bool
CFReader_Local_Is_Directory_IMP(CompoundFileReader *self,
String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
if (Hash_Fetch(ivars->records, name)) { return false; }
if (Folder_Local_Is_Directory(ivars->real_folder, name)) { return true; }
return false;
}
void
CFReader_Close_IMP(CompoundFileReader *self) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
InStream_Close(ivars->instream);
}
bool
CFReader_Local_MkDir_IMP(CompoundFileReader *self, String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
if (Hash_Fetch(ivars->records, name)) {
Err_set_error(Err_new(Str_newf("Can't MkDir: '%o' exists", name)));
return false;
}
else {
bool result = Folder_Local_MkDir(ivars->real_folder, name);
if (!result) { ERR_ADD_FRAME(Err_get_error()); }
return result;
}
}
Folder*
CFReader_Local_Find_Folder_IMP(CompoundFileReader *self,
String *name) {
CompoundFileReaderIVARS *const ivars = CFReader_IVARS(self);
if (Hash_Fetch(ivars->records, name)) { return false; }
return Folder_Local_Find_Folder(ivars->real_folder, name);
}
DirHandle*
CFReader_Local_Open_Dir_IMP(CompoundFileReader *self) {
return (DirHandle*)CFReaderDH_new(self);
}
/****************************************************************************/
CFReaderDirHandle*
CFReaderDH_new(CompoundFileReader *cf_reader) {
CFReaderDirHandle *self
= (CFReaderDirHandle*)Class_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));
CFReaderDirHandleIVARS *const ivars = CFReaderDH_IVARS(self);
ivars->cf_reader = (CompoundFileReader*)INCREF(cf_reader);
Hash *cf_records = CFReader_IVARS(ivars->cf_reader)->records;
ivars->elems = Hash_Keys(cf_records);
ivars->tick = -1;
// Accumulate entries from real Folder.
Folder *real_folder = CFReader_Get_Real_Folder(ivars->cf_reader);
DirHandle *dh = Folder_Local_Open_Dir(real_folder);
while (DH_Next(dh)) {
String *entry = DH_Get_Entry(dh);
Vec_Push(ivars->elems, (Obj*)Str_Clone(entry));
DECREF(entry);
}
DECREF(dh);
return self;
}
bool
CFReaderDH_Close_IMP(CFReaderDirHandle *self) {
CFReaderDirHandleIVARS *const ivars = CFReaderDH_IVARS(self);
if (ivars->elems) {
DECREF(ivars->elems);
ivars->elems = NULL;
}
if (ivars->cf_reader) {
DECREF(ivars->cf_reader);
ivars->cf_reader = NULL;
}
return true;
}
bool
CFReaderDH_Next_IMP(CFReaderDirHandle *self) {
CFReaderDirHandleIVARS *const ivars = CFReaderDH_IVARS(self);
if (ivars->elems) {
ivars->tick++;
if (ivars->tick < (int32_t)Vec_Get_Size(ivars->elems)) {
String *path = (String*)CERTIFY(
Vec_Fetch(ivars->elems, (size_t)ivars->tick), STRING);
DECREF(ivars->entry);
ivars->entry = (String*)INCREF(path);
return true;
}
else {
ivars->tick--;
return false;
}
}
return false;
}
bool
CFReaderDH_Entry_Is_Dir_IMP(CFReaderDirHandle *self) {
CFReaderDirHandleIVARS *const ivars = CFReaderDH_IVARS(self);
if (ivars->elems) {
String *name = (String*)Vec_Fetch(ivars->elems, (size_t)ivars->tick);
if (name) {
return CFReader_Local_Is_Directory(ivars->cf_reader, name);
}
}
return false;
}