/* 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_SNAPSHOT
#include "Lucy/Util/ToolSet.h"
#include "Lucy/Index/Snapshot.h"
#include "Clownfish/Boolean.h"
#include "Lucy/Index/Segment.h"
#include "Lucy/Store/Folder.h"
#include "Lucy/Util/StringHelper.h"
#include "Lucy/Util/IndexFileNames.h"
#include "Lucy/Util/Json.h"
static Vector*
S_clean_segment_contents(Vector *orig);
int32_t Snapshot_current_file_format = 2;
static int32_t Snapshot_current_file_subformat = 1;
Snapshot*
Snapshot_new() {
Snapshot *self = (Snapshot*)Class_Make_Obj(SNAPSHOT);
return Snapshot_init(self);
}
static void
S_zero_out(Snapshot *self) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
DECREF(ivars->entries);
DECREF(ivars->path);
ivars->entries = Hash_new(0);
ivars->path = NULL;
}
Snapshot*
Snapshot_init(Snapshot *self) {
S_zero_out(self);
return self;
}
void
Snapshot_Destroy_IMP(Snapshot *self) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
DECREF(ivars->entries);
DECREF(ivars->path);
SUPER_DESTROY(self, SNAPSHOT);
}
void
Snapshot_Add_Entry_IMP(Snapshot *self, String *entry) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
Hash_Store(ivars->entries, entry, (Obj*)CFISH_TRUE);
}
bool
Snapshot_Delete_Entry_IMP(Snapshot *self, String *entry) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
Obj *val = Hash_Delete(ivars->entries, entry);
if (val) {
DECREF(val);
return true;
}
else {
return false;
}
}
Vector*
Snapshot_List_IMP(Snapshot *self) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
return Hash_Keys(ivars->entries);
}
uint32_t
Snapshot_Num_Entries_IMP(Snapshot *self) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
return (uint32_t)Hash_Get_Size(ivars->entries);
}
void
Snapshot_Set_Path_IMP(Snapshot *self, String *path) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
String *temp = ivars->path;
ivars->path = path ? Str_Clone(path) : NULL;
DECREF(temp);
}
String*
Snapshot_Get_Path_IMP(Snapshot *self) {
return Snapshot_IVARS(self)->path;
}
Snapshot*
Snapshot_Read_File_IMP(Snapshot *self, Folder *folder, String *path) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
// Eliminate all prior data. Pick a snapshot file.
S_zero_out(self);
ivars->path = (path != NULL && Str_Get_Size(path) > 0)
? Str_Clone(path)
: IxFileNames_latest_snapshot(folder);
if (ivars->path) {
Hash *snap_data
= (Hash*)CERTIFY(Json_slurp_json(folder, ivars->path), HASH);
Obj *format_obj
= CERTIFY(Hash_Fetch_Utf8(snap_data, "format", 6), OBJ);
int32_t format = (int32_t)Json_obj_to_i64(format_obj);
Obj *subformat_obj = Hash_Fetch_Utf8(snap_data, "subformat", 9);
int32_t subformat = subformat_obj
? (int32_t)Json_obj_to_i64(subformat_obj)
: 0;
// Verify that we can read the index properly.
if (format > Snapshot_current_file_format) {
THROW(ERR, "Snapshot format too recent: %i32, %i32",
format, Snapshot_current_file_format);
}
// Build up list of entries.
Vector *list = (Vector*)INCREF(CERTIFY(
Hash_Fetch_Utf8(snap_data, "entries", 7),
VECTOR));
if (format == 1 || (format == 2 && subformat < 1)) {
Vector *cleaned = S_clean_segment_contents(list);
DECREF(list);
list = cleaned;
}
Hash_Clear(ivars->entries);
for (size_t i = 0, max = Vec_Get_Size(list); i < max; i++) {
String *entry
= (String*)CERTIFY(Vec_Fetch(list, i), STRING);
Hash_Store(ivars->entries, entry, (Obj*)CFISH_TRUE);
}
DECREF(list);
DECREF(snap_data);
}
return self;
}
static Vector*
S_clean_segment_contents(Vector *orig) {
// Since Snapshot format 2, no DataReader has depended on individual files
// within segment directories being listed. Filter these files because
// they cause a problem with FilePurger.
Vector *cleaned = Vec_new(Vec_Get_Size(orig));
for (size_t i = 0, max = Vec_Get_Size(orig); i < max; i++) {
String *name = (String*)Vec_Fetch(orig, i);
if (!Seg_valid_seg_name(name)) {
if (Str_Starts_With_Utf8(name, "seg_", 4)) {
continue; // Skip this file.
}
}
Vec_Push(cleaned, INCREF(name));
}
return cleaned;
}
void
Snapshot_Write_File_IMP(Snapshot *self, Folder *folder, String *path) {
SnapshotIVARS *const ivars = Snapshot_IVARS(self);
Hash *all_data = Hash_new(0);
Vector *list = Snapshot_List(self);
// Update path.
DECREF(ivars->path);
if (path != NULL && Str_Get_Size(path) != 0) {
ivars->path = Str_Clone(path);
}
else {
String *latest = IxFileNames_latest_snapshot(folder);
uint64_t gen = latest ? IxFileNames_extract_gen(latest) + 1 : 1;
char base36[StrHelp_MAX_BASE36_BYTES];
StrHelp_to_base36(gen, &base36);
ivars->path = Str_newf("snapshot_%s.json", &base36);
DECREF(latest);
}
// Don't overwrite.
if (Folder_Exists(folder, ivars->path)) {
THROW(ERR, "Snapshot file '%o' already exists", ivars->path);
}
// Sort, then store file names.
Vec_Sort(list);
Hash_Store_Utf8(all_data, "entries", 7, (Obj*)list);
// Create a JSON-izable data structure.
Hash_Store_Utf8(all_data, "format", 6,
(Obj*)Str_newf("%i32", (int32_t)Snapshot_current_file_format));
Hash_Store_Utf8(all_data, "subformat", 9,
(Obj*)Str_newf("%i32", (int32_t)Snapshot_current_file_subformat));
// Write out JSON-ized data to the new file.
Json_spew_json((Obj*)all_data, folder, ivars->path);
DECREF(all_data);
}