Files
Webcam/include/modules/archive/archive.cpp

441 lines
12 KiB
C++

/* -----------------------------------------------------------------------------
GSFramework
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */
#include <cstring>
#include "zlib.h"
#include "archive/archive.h"
#include "metafile/nml.h"
#include "filesystem/filesystem.h"
#include "thread/mutex.h"
#include "platform.h"
#include "log/log.h"
using namespace GS;
using namespace GS::NML;
//------------------------------------------------------------------------------
#define __CorrectOffsetPadding \
{\
if (offset_padding)\
{\
size_t error = handle->Tell() % offset_padding;\
if (error)\
handle->Seek((long)(offset_padding - error));\
}\
}
bool ArchiveIndex::Load(const char *uri)
{
index.Clear();
__LOG_V__ << "Loading archive index '" << uri << "'...\n";
File file;
if (!Parser::Load(uri, file))
return false;
// Grab index tag.
Tag *index_tag = file.GetTag("Index;");
if (!index_tag)
__ERR__(__LOG_E__ << "No archive index tag found in '" << uri << "'.\n", false)
// Pool for entries.
NMLTagForeach(t, *index_tag)
if (t->name == "Entry")
{
Tag *id_tag = t->GetTypedTag("ID;", Variant::VariantString),
*compressed_tag = t->GetTypedTag("CompLen", Variant::VariantInteger),
*length_tag = t->GetTypedTag("Len", Variant::VariantInteger),
*method_tag = t->GetTypedTag("Method", Variant::VariantString),
*offset_tag = t->GetTypedTag("Offset", Variant::VariantInteger);
if (id_tag && compressed_tag && length_tag && method_tag)
{
// Import the entry.
if (ArchiveEntry *entry = new ArchiveEntry)
{
entry->path = id_tag->GetString();
entry->length = length_tag->GetInteger();
entry->compressed_length = compressed_tag->GetInteger();
entry->offset = offset_tag->GetInteger();
String method(method_tag->GetString());
if (method == "Raw")
entry->method = ArchiveEntry::MethodRaw;
else if (method == "Zlib")
entry->method = ArchiveEntry::MethodZLibCompress;
index.Add(entry);
}
else
__LOG_E__ << "Failed to allocate new archive index entry.\n";
}
else
__LOG_E__ << "Incomplete index entry.\n";
}
else
__LOG_W__ << "Unexpected tag <" << t->name << "> in <Index>.\n";
__LOG_V__ << "Done, found " << index.GetCount() << " entries.\n";
return true;
}
bool ArchiveIndex::Save(const char *uri)
{
File file;
Tag *index_tag = file.AddRoot("Index");
ListForeachPtr(ArchiveEntry *, entry, index)
{
// Create entry.
Tag *entry_tag = index_tag->AddChild("Entry");
entry_tag->AddChild("ID", entry->path.c_str());
entry_tag->AddChild("CompLen", (int)entry->compressed_length);
entry_tag->AddChild("Len", (int)entry->length);
entry_tag->AddChild("Offset", (int)entry->offset);
switch (entry->method)
{
case ArchiveEntry::MethodRaw:
entry_tag->AddChild("Method", "Raw");
break;
case ArchiveEntry::MethodZLibCompress:
entry_tag->AddChild("Method", "Zlib");
break;
}
}
return Parser::Save(uri, file);
}
ArchiveEntry *ArchiveIndex::FindEntry(const char *alias) const
{
ListForeachPtr(ArchiveEntry *, e, index)
if (e->path == alias)
return e;
return NULL;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Archive::LoadIndex(const char *uri)
{ return index.Load(uri); }
bool Archive::SaveIndex(const char *uri)
{ return index.Save(uri); }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Archive::OpenRead(const char *uri, const char *idx)
{
Close();
if (!uri)
__ERR__(__LOG_E__ << "No archive to open.\n", false)
if (!(handle = Platform::Get().io->Open(uri)))
__ERR__(__LOG_E__ << "Failed to open archive '" << uri << "'.\n", false)
// Check archive header.
__CorrectOffsetPadding
uint magic_word = handle->Read <uint> ();
if (magic_word == 0x4E415244) // 'NARD' (padding support).
{
__CorrectOffsetPadding
offset_padding = handle->Read <int> ();
__CorrectOffsetPadding
size_padding = handle->Read <int> ();
revision = EnhancedLegacy;
}
else if (magic_word == 0x4E415243) // 'NARC' backward compatibility.
{
offset_padding = 0;
size_padding = 0;
revision = Legacy;
}
else
{
__LOG_E__ << "Invalid archive type '" << uri << "'.\n";
Close();
return false;
}
// Open or create index if none provided.
if (!idx)
{
size_t alen = handle->GetSize();
if (verbose)
__LOG__ << "No index provided, please wait while scanning archive...\n";
while (handle->Tell() < alen)
{
// Fetch alias.
char tmp[512];
__CorrectOffsetPadding
uint tsz = handle->Read <uint> ();
if (tsz > 511)
break;
else
{
handle->Read((void *)tmp, tsz);
tmp[tsz] = 0;
}
// Attributes.
__CorrectOffsetPadding
char cmp = handle->Read <uchar> () & 1;
__CorrectOffsetPadding
uint len = handle->Read <uint> (), clen = len;
if (cmp)
{
__CorrectOffsetPadding
clen = handle->Read <uint> ();
}
// Store in index.
if (ArchiveEntry *entry = new ArchiveEntry)
{
entry->path = tmp;
entry->method = cmp;
entry->length = len;
entry->compressed_length = clen;
entry->offset = handle->Tell();
index.index.Add(entry);
if (verbose)
{
__LOG__ << "New entry: '" << entry->path << "'\n";
__LOG__ << "[method = " << entry->method << ", len = " << (uint)entry->length << ", clen = " << (uint)entry->compressed_length << ", offset = " << (uint)entry->offset << "].\n";
}
}
else
__LOG_E__ << "Could not allocate new index entry.\n";
handle->Seek(clen);
}
if (verbose)
__LOG__ << "Done, index table built.\n\n";
}
else
{
LoadIndex(idx);
if (verbose)
ListForeachPtr(ArchiveEntry *, entry, index.index)
{
__LOG__ << "New entry: '" << entry->path << "'\n";
__LOG__ << "[method = " << entry->method << ", len = " << (uint)entry->length << ", clen = " << (uint)entry->compressed_length << ", offset = " << (uint)entry->offset << "].\n";
}
}
return true;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Archive::CreateNew(const char *uri)
{
Close();
if (!(handle = Platform::Get().io->Open(uri, IO::ModeWrite)))
__ERR__(__LOG_E__ << "Failed to open '" << uri << "' as output archive.\n", false)
// Output header.
__CorrectOffsetPadding
handle->Write <uint> (0x4E415244); // 'NARD'
__CorrectOffsetPadding
handle->Write <int> (offset_padding);
__CorrectOffsetPadding
handle->Write <int> (size_padding);
append_mode = true;
return true;
}
void Archive::Close()
{
if (append_mode)
{
// EOF marker.
handle->Write <uint> (0xffffffff);
// Size padding.
size_t size = handle->Tell();
size_t pad_count = size_padding ? size_padding - size % size_padding : 0;
for (size_t n = 0; n < pad_count; ++n)
handle->Write <uchar> (0xff); // End of archive marker (entry id length > 512).
}
// Close archive.
handle = NULL;
index.index.Clear();
if (append_mode)
__LOG__ << "Archive complete, closing.\n";
append_mode = false;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Archive::FileRead(const char *path, void *out)
{
if (append_mode)
__ERR__(__LOG_E__ << "Cannot load '" << path << "' (r/w mode error).\n", false)
ArchiveEntry *entry = Exists(path);
if (!entry)
__ERR__(__LOG_E__ << "Could not find '" << path << "'.\n", false)
Threading::MutexLock lock(access_mutex);
size_t h_cursor = handle->Tell();
switch (entry->method)
{
case ArchiveEntry::MethodRaw:
handle->Seek(entry->offset, IO::Base::SeekStart); // Note: Should be padded to the right offset already.
if (handle->Read(out, entry->length) != entry->length)
__LOG_E__ << "Failed to load raw source.\n";
break;
case ArchiveEntry::MethodZLibCompress:
{
handle->Seek(entry->offset, IO::Base::SeekStart); // Note: Should be padded to the right offset already.
Array <char> cp((uint)(entry->compressed_length + 16));
if (!cp)
__ERR__(__LOG_E__ << "Failed to allocated compressed memory support.\n", false)
if (handle->Read((void *)cp, entry->compressed_length) != entry->compressed_length)
__ERR__(__LOG_E__ << "Failed to load compressed source.\n", false)
uLong out_len = (uLong)entry->length;
uncompress((Bytef *)out, &out_len, (Bytef *)&cp[0], (uLong)entry->compressed_length);
}
break;
}
handle->Seek(h_cursor, IO::Base::SeekStart);
return true;
}
//-----------------------------------------------------------------
//------------------------------------------------------------------------------
ArchiveEntry *Archive::MemoryBlockWrite(const char *alias, const void *in, size_t len, int level)
{
if (!append_mode || !len)
__ERR__(__LOG_E__ << "Cannot write '" << alias << "' (r/w mode error).\n", NULL)
Threading::MutexLock lock(access_mutex);
// Ensure alias uniqueness.
if (ArchiveEntry *e = index.FindEntry(alias))
return e;
// Sync index.
ArchiveEntry *entry = new ArchiveEntry;
if (!entry)
__ERR__(__LOG_E__ << "Failed to allocate new index entry.\n", NULL)
entry->path = alias;
index.index.Add(entry);
// Compress and add to archive.
Array <char> out;
size_t clen;
// Output alias.
__CorrectOffsetPadding
handle->Write <uint> (std::strlen(alias));
__CorrectOffsetPadding
handle->Write((const void *)alias, std::strlen(alias));
if (verbose)
__LOG_H__ << "Adding '" << alias << "' to archive...\n";
// Output data, ZLIB needs destination to be at least source * 100.1% + 12 bytes.
bool add_raw = true;
if (level >= 0)
{
clen = len + (len / 1000 + 1) + 12;
if (out.Allocate((uint)clen) && (compress2((Bytef *)&out[0], (uLongf *)&clen, (const Bytef *)in, (uLong)len, level) == Z_OK))
{
// @TODO compression method as a bit flag here.
__CorrectOffsetPadding
handle->Write <uchar> (1);
__CorrectOffsetPadding
handle->Write <uint> (len);
__CorrectOffsetPadding
handle->Write <uint> (clen);
// Write compressed block.
__CorrectOffsetPadding
entry->method = ArchiveEntry::MethodZLibCompress;
entry->offset = handle->Tell();
entry->length = len;
entry->compressed_length = clen;
if (verbose)
__LOG__ << "Was " << (uint)(len / 1000) << " KB, is now " << (uint)(clen / 1000) << " KB (" << (uint)(clen * 100 / len) <<"%).\n";
handle->Write((const void *)out, clen);
add_raw = false;
}
else
if (verbose)
__LOG__ << "No compression achieved for '" << alias << "', adding uncompressed.\n";
}
if (add_raw)
{
__CorrectOffsetPadding
handle->Write <uchar> (0);
__CorrectOffsetPadding
handle->Write <uint> (len);
// Write raw block.
__CorrectOffsetPadding
entry->method = ArchiveEntry::MethodRaw;
entry->offset = handle->Tell();
entry->length = len;
entry->compressed_length = 0;
handle->Write((const void *)in, len);
}
return entry;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
ArchiveEntry *Archive::FileWrite(const char *path, const char *alias, int level)
{
Array <char> data;
if (!Platform::Get().io->FileLoad(path, data))
return NULL;
return MemoryBlockWrite(alias ? alias : path, &data[0], data.GetSize(), level);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
Archive::Archive()
{
append_mode = false;
offset_padding = 0;
size_padding = 0;
access_mutex = new Threading::Mutex;
}
Archive::~Archive()
{
Close();
}
//------------------------------------------------------------------------------