xlnt/source/zip_file.cpp
2014-07-29 18:01:54 -04:00

363 lines
8.1 KiB
C++

#include <array>
#include <fstream>
#include "common/zip_file.hpp"
#include "common/exceptions.hpp"
namespace xlnt {
zip_file::zip_file(const std::string &filename, file_mode mode, file_access access)
: current_state_(state::closed),
filename_(filename),
modified_(false),
// mode_(mode),
access_(access)
{
switch(mode)
{
case file_mode::open:
read_all();
break;
case file_mode::open_or_create:
if(file_exists(filename))
{
read_all();
}
else
{
flush(true);
}
break;
case file_mode::create:
flush(true);
break;
case file_mode::create_new:
if(file_exists(filename))
{
throw std::runtime_error("file exists");
}
flush(true);
break;
case file_mode::truncate:
if((int)access & (int)file_access::read)
{
throw std::runtime_error("cannot read from file opened with file_mode truncate");
}
flush(true);
break;
case file_mode::append:
read_all();
break;
}
}
zip_file::~zip_file()
{
change_state(state::closed);
}
std::string zip_file::get_file_contents(const std::string &filename) const
{
return files_.at(filename);
}
std::string dirname(const std::string &filename)
{
auto last_sep_index = filename.find_last_of('/');
if(last_sep_index != std::string::npos)
{
return filename.substr(0, last_sep_index + 1);
}
return "";
}
void zip_file::set_file_contents(const std::string &filename, const std::string &contents)
{
if(!has_file(filename) || files_[filename] != contents)
{
modified_ = true;
}
auto dir = dirname(filename);
while(dir != "")
{
auto matching_directory = std::find(directories_.begin(), directories_.end(), dir);
if(matching_directory == directories_.end())
{
directories_.push_back(dir);
}
dir = dirname(dir.substr(0, dir.length() - 1));
}
files_[filename] = contents;
}
void zip_file::delete_file(const std::string &filename)
{
files_.erase(filename);
}
bool zip_file::has_file(const std::string &filename)
{
return files_.find(filename) != files_.end();
}
void zip_file::flush(bool force_write)
{
if(modified_ || force_write)
{
write_all();
}
}
void zip_file::read_all()
{
if(!((int)access_ & (int)file_access::read))
{
throw std::runtime_error("don't have read access");
}
change_state(state::read);
auto num_files = zip_file_.m_total_files;
std::size_t i = 0;
for(;i < num_files; i++)
{
mz_zip_archive_file_stat file_info;
if(!mz_zip_reader_file_stat(&zip_file_, i, &file_info))
{
throw std::runtime_error("stat failed");
}
std::string current_filename(file_info.m_filename, file_info.m_filename + strlen(file_info.m_filename));
if(mz_zip_reader_is_file_a_directory(&zip_file_, i))
{
directories_.push_back(current_filename);
continue;
}
files_[current_filename] = read_from_zip(current_filename);
}
}
void zip_file::write_all()
{
if(!((int)access_ & (int)file_access::write))
{
throw std::runtime_error("don't have write access");
}
change_state(state::write, false);
for(auto directory : directories_)
{
write_directory_to_zip(directory, true);
}
for(auto file : files_)
{
write_to_zip(file.first, file.second, true);
}
modified_ = false;
}
std::string zip_file::read_from_zip(const std::string &filename)
{
if(!((int)access_ & (int)file_access::read))
{
throw std::runtime_error("don't have read access");
}
change_state(state::read);
auto num_files = (std::size_t)mz_zip_reader_get_num_files(&zip_file_);
std::size_t i = 0;
for(;i < num_files; i++)
{
mz_zip_archive_file_stat file_info;
if(!mz_zip_reader_file_stat(&zip_file_, i, &file_info))
{
throw std::runtime_error("stat failed");
}
std::string current_filename(file_info.m_filename, file_info.m_filename + strlen(file_info.m_filename));
if(filename == current_filename)
{
break;
}
}
if(i == num_files)
{
throw std::runtime_error("not found");
}
if(mz_zip_reader_is_file_a_directory(&zip_file_, i))
{
directories_.push_back(filename);
return "";
}
std::size_t uncomp_size = 0;
char archive_filename[260];
std::fill(archive_filename, archive_filename + 260, '\0');
std::copy(filename.begin(), filename.begin() + filename.length(), archive_filename);
char *data = (char *)mz_zip_reader_extract_file_to_heap(&zip_file_, archive_filename, &uncomp_size, 0);
if(data == nullptr)
{
throw std::runtime_error("extract failed");
}
std::string content(data, data + uncomp_size);
mz_free(data);
return content;
}
void zip_file::write_directory_to_zip(const std::string &name, bool append)
{
if(!((int)access_ & (int)file_access::write))
{
throw std::runtime_error("don't have write access");
}
change_state(state::write, append);
if(!mz_zip_writer_add_mem(&zip_file_, name.c_str(), nullptr, 0, MZ_BEST_COMPRESSION))
{
throw std::runtime_error("write directory failed");
}
}
void zip_file::write_to_zip(const std::string &filename, const std::string &content, bool append)
{
if(!((int)access_ & (int)file_access::write))
{
throw std::runtime_error("don't have write access");
}
change_state(state::write, append);
auto status = mz_zip_writer_add_mem(&zip_file_, filename.c_str(), content.c_str(), content.length(), MZ_BEST_COMPRESSION);
if(!status)
{
throw std::runtime_error("write failed");
}
}
void zip_file::change_state(state new_state, bool append)
{
if(new_state == current_state_ && append)
{
return;
}
switch(new_state)
{
case state::closed:
if(current_state_ == state::write)
{
stop_write();
}
else if(current_state_ == state::read)
{
stop_read();
}
break;
case state::read:
if(current_state_ == state::write)
{
stop_write();
}
start_read();
break;
case state::write:
if(current_state_ == state::read)
{
stop_read();
}
if(current_state_ != state::write)
{
start_write(append);
}
break;
default:
throw std::runtime_error("bad enum");
}
current_state_ = new_state;
}
bool zip_file::file_exists(const std::string& name)
{
std::ifstream f(name.c_str());
return f.good();
}
void zip_file::start_read()
{
if(!mz_zip_reader_init_file(&zip_file_, filename_.c_str(), 0))
{
throw invalid_file_exception(filename_);
}
}
void zip_file::stop_read()
{
if(!mz_zip_reader_end(&zip_file_))
{
throw std::runtime_error("");
}
}
void zip_file::start_write(bool append)
{
if(append && !file_exists(filename_))
{
throw std::runtime_error("can't append to non-existent file");
}
if(!mz_zip_writer_init_file(&zip_file_, filename_.c_str(), 0))
{
if(append)
{
throw std::runtime_error("couldn't append to zip file");
}
else
{
throw std::runtime_error("couldn't create zip file");
}
}
}
void zip_file::stop_write()
{
flush();
if(!mz_zip_writer_finalize_archive(&zip_file_))
{
throw std::runtime_error("");
}
if(!mz_zip_writer_end(&zip_file_))
{
throw std::runtime_error("");
}
}
}