This commit is contained in:
Phil Smith 2017-10-05 15:21:50 -07:00
Родитель 56712c5d65
Коммит ae88fe63e8
15 изменённых файлов: 494 добавлений и 45 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -107,6 +107,9 @@ Temporary Items
*.userosscache
*.sln.docstates
# Intermediate test files/data
test/unpack
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

Просмотреть файл

@ -144,6 +144,7 @@
<ClInclude Include="..\..\lib\zlib\zlib.h" />
<ClInclude Include="..\..\src\inc\Base64Stream.hpp" />
<ClInclude Include="..\..\src\inc\CRC32Stream.hpp" />
<ClInclude Include="..\..\src\inc\DirectoryObject.hpp" />
<ClInclude Include="..\..\src\inc\Exceptions.hpp" />
<ClInclude Include="..\..\src\inc\FileStream.hpp" />
<ClInclude Include="..\..\src\inc\OffsetStream.hpp" />
@ -159,7 +160,9 @@
<None Include="xPlatAppx.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\xPlatAppx\PAL\FileSystem\Win32\DirectoryObject.cpp" />
<ClCompile Include="..\..\src\xPlatAppx\xPlatAppx.cpp" />
<ClCompile Include="..\..\src\xPlatAppx\ZipFileStream.cpp" />
<ClCompile Include="..\..\src\xPlatAppx\ZipObject.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

Просмотреть файл

@ -54,6 +54,9 @@
<ClInclude Include="..\..\src\inc\ZipObject.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\inc\DirectoryObject.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="xPlatAppx.def">
@ -67,5 +70,11 @@
<ClCompile Include="..\..\src\xPlatAppx\ZipObject.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\xPlatAppx\PAL\FileSystem\Win32\DirectoryObject.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\xPlatAppx\ZipFileStream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "Exceptions.hpp"
#include "StreamBase.hpp"
#include "StorageObject.hpp"
namespace xPlat {
class DirectoryObject : public StorageObject
{
public:
DirectoryObject(std::string root) : m_root(root) {}
// StorageObject methods
virtual std::vector<std::string> GetFileNames() override;
virtual std::shared_ptr<StreamBase> GetFile(std::string& fileName) override;
virtual void RemoveFile(std::string& fileName) override;
virtual std::shared_ptr<StreamBase> OpenFile(std::string& fileName, FileStream::Mode mode) override;
virtual void CommitChanges() override;
protected:
std::map<std::string, std::shared_ptr<StreamBase>> m_streams;
std::string m_root;
};//class ZipObject
}

Просмотреть файл

@ -9,7 +9,7 @@ namespace xPlat {
class ExceptionBase : public std::exception
{
public:
enum Facility : uint32_t {
enum SubFacility : uint32_t {
NONE = 0x0000,
FILE = 0x1000,
ZIP = 0x2000,
@ -18,18 +18,19 @@ namespace xPlat {
};
ExceptionBase() {}
ExceptionBase(Facility facility) : facility(facility) {}
ExceptionBase(SubFacility subFacility) : subFacility(subFacility) {}
ExceptionBase(uint32_t headerOveride, SubFacility subFacility) : header(headerOveride), subFacility(subFacility) {}
void SetLastError(uint32_t error)
{
code = header + facility + (error & Facility::MAX);
code = header + subFacility + (error & SubFacility::MAX);
}
uint32_t Code() { return code; }
protected:
Facility facility = Facility::NONE;
uint32_t header = 0x8BAD0000; // facility 2989
SubFacility subFacility = SubFacility::NONE;
uint32_t header = 0x8BAD0000; // SubFacility 2989
uint32_t code = 0xFFFFFFFF; // by default, something very bad happened...
};

Просмотреть файл

@ -12,7 +12,7 @@ namespace xPlat {
{
public:
FileException(std::string message, uint32_t error = 0) :
ExceptionBase(ExceptionBase::Facility::FILE),
ExceptionBase(ExceptionBase::SubFacility::FILE),
reason(message)
{
SetLastError(error);
@ -58,9 +58,9 @@ namespace xPlat {
offset = Ftell();
}
virtual std::size_t Read(std::size_t size, const std::uint8_t* bytes) override
virtual std::uint64_t Read(std::uint64_t size, const std::uint8_t* bytes) override
{
std::size_t bytesRead = std::fread(
std::uint64_t bytesRead = std::fread(
static_cast<void*>(const_cast<std::uint8_t*>(bytes)), 1, size, file
);
if (bytesRead < size && !Feof())

38
src/inc/StorageObject.hpp Normal file
Просмотреть файл

@ -0,0 +1,38 @@
#pragma once
#include "Exceptions.hpp"
#include "StreamBase.hpp"
#include "FileStream.hpp"
#include <string>
#include <vector>
#include <memory>
namespace xPlat {
// Interface over a namespace of collected file objects
class StorageObject
{
public:
virtual ~StorageObject() {}
// Obtains a vector of UTF-8 formatted string names contained in the storage object
virtual std::vector<std::string> GetFileNames() = 0;
// Obtains a pointer to a stream representing the file that exists in the storage object
virtual std::shared_ptr<StreamBase> GetFile(std::string& fileName) = 0;
// Remvoes a file by name from the storage object. If the file does not exist, the operation is a no-op
virtual void RemoveFile(std::string& fileName) = 0;
// Opens a stream to a file by name in the storage object. If the file does not exist and mode is read,
// or read + update, then nullptr is returned. If the file is opened with write and it does not exist,
// then the file is created and an empty stream to the file is handed back to the caller.
virtual std::shared_ptr<StreamBase> OpenFile(std::string& fileName, FileStream::Mode mode) = 0;
// Some storage objects may operate under cache semantics and therefore require an explicit commit.
// Clients should explicitly call CommitChanges after all write operations into the object are complete.
// An implementation of this interface MAY be a no-op.
virtual void CommitChanges() = 0;
};
}

Просмотреть файл

@ -14,19 +14,19 @@ namespace xPlat {
// This way, derived classes only have to implement what they actually need, and everything else is not implemented.
virtual void Write(std::size_t size, const std::uint8_t* bytes) { throw NotImplementedException(); }
virtual std::size_t Read(std::size_t size, const std::uint8_t* bytes) { throw NotImplementedException(); }
virtual std::uint64_t Read(std::uint64_t size, const std::uint8_t* bytes) { throw NotImplementedException(); }
virtual void Seek(std::uint64_t offset, Reference where) { throw NotImplementedException(); }
virtual int Ferror() { throw NotImplementedException(); }
virtual bool Feof() { throw NotImplementedException(); }
virtual std::uint64_t Ftell() { throw NotImplementedException(); }
virtual void CopyTo(StreamBase& to)
virtual void CopyTo(StreamBase* to)
{
std::uint8_t buffer[1024]; // 1k at a time ought to be sufficient
std::size_t bytes = Read(sizeof(buffer), buffer);
while (bytes != 0)
{
to.Write(bytes, buffer);
to->Write(bytes, buffer);
bytes = Read(sizeof(buffer), buffer);
}
}

Просмотреть файл

@ -1,6 +1,7 @@
#include "Exceptions.hpp"
#include "StreamBase.hpp"
#include "StreamBase.hpp"
#include <string>
@ -16,16 +17,25 @@ namespace xPlat {
std::uint32_t offset,
std::uint32_t compressedSize,
std::uint32_t uncompressedSize,
bool isCompressed
bool isCompressed,
StreamBase* stream
) :
m_fileName(std::move(fileName)),
m_offset(offset),
m_compressedSize(compressedSize),
m_uncompressedSize(uncompressedSize),
m_isCompressed(isCompressed)
m_isCompressed(isCompressed),
m_stream(stream)
{
}
virtual void Write(std::size_t size, const std::uint8_t* bytes) override;
virtual std::uint64_t Read(std::uint64_t size, const std::uint8_t* bytes) override;
virtual void Seek(std::uint64_t offset, Reference where) override;
virtual int Ferror() override;
virtual bool Feof() override;
virtual std::uint64_t Ftell() override;
protected:
std::string m_fileName;
@ -36,5 +46,6 @@ namespace xPlat {
bool m_isCompressed = false;
std::uint64_t m_relativePosition = 0;
StreamBase* m_stream = nullptr;
};
}

Просмотреть файл

@ -2,8 +2,7 @@
#include "Exceptions.hpp"
#include "StreamBase.hpp"
#include "ObjectBase.hpp"
#include "ZipFileStream.hpp"
#include "StorageObject.hpp"
#include <vector>
#include <map>
@ -26,7 +25,7 @@ namespace xPlat {
InvalidLocalFileHeader = 8,
};
ZipException(std::string message, Error error) : reason(message), ExceptionBase(ExceptionBase::Facility::ZIP)
ZipException(std::string message, Error error) : reason(message), ExceptionBase(ExceptionBase::SubFacility::ZIP)
{
SetLastError(static_cast<std::uint32_t>(error));
}
@ -38,15 +37,20 @@ namespace xPlat {
class LocalFileHeader;
// This represents a raw stream over a.zip file.
class ZipObject
class ZipObject : public StorageObject
{
public:
ZipObject(StreamBase* stream);
std::vector<std::string> GetFileNames();
// StorageObject methods
virtual std::vector<std::string> GetFileNames() override;
virtual std::shared_ptr<StreamBase> GetFile(std::string& fileName) override;
virtual void RemoveFile(std::string& fileName) override;
virtual std::shared_ptr<StreamBase> OpenFile(std::string& fileName, FileStream::Mode mode) override;
virtual void CommitChanges() override;
protected:
std::map<std::string, std::shared_ptr<ZipFileStream>> m_streams;
std::map<std::string, std::shared_ptr<StreamBase>> m_streams;
std::map<std::string, std::shared_ptr<CentralDirectoryFileHeader>> m_centralDirectory;
// TODO: change to uint64_t when adding full zip64 support

Просмотреть файл

@ -0,0 +1 @@
// TODO: Implement FTS wrapper

Просмотреть файл

@ -0,0 +1,236 @@
#include "Exceptions.hpp"
#include "DirectoryObject.hpp"
#include "FileStream.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <sstream>
#include <locale>
#include <codecvt>
// UNICODE MUST be defined before you include Windows.h if you want the non-ascii versions of APIs (and you do)
#define UNICODE
#include <windows.h>
namespace xPlat {
class Win32Exception : public ExceptionBase
{
public:
Win32Exception(DWORD error) : ExceptionBase(0x8007, SubFacility::NONE)
{
SetLastError(static_cast<std::uint32_t>(error));
}
};
std::wstring utf8_to_utf16(std::string& utf8string)
{
/*
from: https://connect.microsoft.com/VisualStudio/feedback/details/1403302/unresolved-external-when-using-codecvt-utf8
Posted by Microsoft on 2/16/2016 at 11:49 AM
<snip>
A workaround is to replace 'char32_t' with 'unsigned int'. In VS2013, char32_t was a typedef of 'unsigned int'. In VS2015, char32_t is a distinct type of it's own. Switching your use of 'char32_t' to 'unsigned int' will get you the old behavior from earlier versions and won't trigger a missing export error.
There is also a similar error to this one with 'char16_t' that can be worked around using 'unsigned short'.
*/
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<unsigned short>, unsigned short>{}.from_bytes(utf8string.data());
std::wstring result(converted.begin(), converted.end());
return result;
}
std::string utf16_to_utf8(std::wstring& utf16string)
{
auto converted = std::wstring_convert<std::codecvt_utf8<wchar_t>>{}.to_bytes(utf16string.data());
std::string result(converted.begin(), converted.end());
return result;
}
enum class WalkOptions : std::uint16_t
{
Files = 1, // Enumerate files within the specified directory
Directories = 2, // Enumerate directories
Recursive = 4 // Enumerate recursively
};
inline constexpr WalkOptions operator &(WalkOptions a, WalkOptions b)
{
return static_cast<WalkOptions>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
}
inline constexpr WalkOptions operator |(WalkOptions a, WalkOptions b)
{
return static_cast<WalkOptions>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
}
template <WalkOptions options, class Lambda>
void WalkDirectory(std::string& root, Lambda& visitor)
{
static std::string dot(".");
static std::string dotdot("..");
std::wstring utf16Name = utf8_to_utf16(root);
WIN32_FIND_DATA findFileData = {};
std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::FindClose)> find(
FindFirstFile(reinterpret_cast<LPCWSTR>(utf16Name.c_str()), &findFileData),
&FindClose);
if (INVALID_HANDLE_VALUE == find.get())
{
DWORD lastError = GetLastError();
if (lastError == ERROR_FILE_NOT_FOUND)
{
return;
}
throw Win32Exception(lastError);
}
do
{
utf16Name = std::wstring(findFileData.cFileName);
auto utf8Name = utf16_to_utf8(utf16Name);
if (((options & WalkOptions::Directories) == WalkOptions::Directories) &&
(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
)
{
std::string child = (root + "\\" + utf8Name);
if (!visitor(root, WalkOptions::Directories, std::move(utf8Name)))
{
break;
}
if ((options & WalkOptions::Recursive) == WalkOptions::Recursive)
{
WalkDirectory<options>(child, visitor);
}
}
else if ((options & WalkOptions::Files) == WalkOptions::Files)
{
if (dot != utf8Name && dotdot != utf8Name)
{
if (!visitor(root, WalkOptions::Files, std::move(utf8Name)))
{
break;
}
}
}
}
while (FindNextFile(find.get(), &findFileData));
DWORD lastError = GetLastError();
if ((lastError != ERROR_NO_MORE_FILES) && (lastError != ERROR_SUCCESS))
{
throw Win32Exception(lastError);
}
}
std::vector<std::string> DirectoryObject::GetFileNames()
{
std::vector<std::string> result;
WalkDirectory<WalkOptions::Files | WalkOptions::Directories | WalkOptions::Recursive>(m_root, [&](
std::string,
WalkOptions option,
std::string&& name)
{
if (option == WalkOptions::Files)
{
result.push_back(std::move(name));
}
return true;
});
return result;
}
std::shared_ptr<StreamBase> DirectoryObject::GetFile(std::string& fileName)
{
// first check cache
auto index = m_streams.find(fileName);
if (index != m_streams.cend())
{
return index->second;
}
// create stream and populate cache
std::string path = m_root + "\\" + fileName;
auto result = m_streams[fileName] = std::make_unique<FileStream>(std::move(fileName), FileStream::Mode::READ);
return result;
}
void DirectoryObject::RemoveFile(std::string& fileName)
{
throw NotImplementedException();
}
std::shared_ptr<StreamBase> DirectoryObject::OpenFile(std::string& fileName, FileStream::Mode mode)
{
static const std::string slash("\\");
std::vector<std::string> directories;
auto PopFirst = [&directories]()
{
auto result = directories.at(0);
std::vector<std::string>(directories.begin() + 1, directories.end()).swap(directories);
return result;
};
auto PopBack = [&directories]()
{
auto result = directories.at(directories.size() - 1);
directories.pop_back();
return result;
};
// Build a list of directory names to ensure exist
std::istringstream stream(fileName);
std::string directory;
while (getline(stream, directory, '/'))
{
directories.push_back(std::move(directory));
}
auto name = PopBack(); // remove the actual file name from the list of directories, but keep just the name
// Enforce that directory structure exists before creating file at specified location.
bool found = false;
std::string path = m_root;
while (directories.size() != 0)
{
WalkDirectory<WalkOptions::Directories>(path + slash + directories.front(), [&](
std::string,
WalkOptions option,
std::string&& name)
{
found = false;
if (directories.front() == name)
{
found = true;
path = path + slash + PopFirst();
return false;
}
return true;
});
if (!found)
{
std::wstring utf16Name = utf8_to_utf16(path + slash + directories.front());
if (!CreateDirectory(utf16Name.c_str(), nullptr))
{
throw Win32Exception(GetLastError());
}
path = path + slash + PopFirst();
}
}
name = path + slash + name;
auto result = m_streams[fileName] = std::make_unique<FileStream>(std::move(name), mode);
return result;
}
void DirectoryObject::CommitChanges()
{
m_streams.clear();
}
}
// Don't pollute other compilation units with any of our #defs...
#undef UNICODE

Просмотреть файл

@ -0,0 +1,58 @@
#include "ZipFileStream.hpp"
#include "StreamBase.hpp"
#include <limits>
#include <algorithm>
namespace xPlat {
void ZipFileStream::Write(std::size_t size, const std::uint8_t* bytes)
{
throw NotImplementedException();
}
std::uint64_t ZipFileStream::Read(std::uint64_t size, const std::uint8_t* bytes)
{
m_stream->Seek(m_offset + m_relativePosition, StreamBase::Reference::START);
std::uint64_t amountToRead = std::min(size, (m_compressedSize - m_relativePosition));
std::uint64_t bytesRead = m_stream->Read(amountToRead, bytes);
m_relativePosition += bytesRead;
return bytesRead;
}
void ZipFileStream::Seek(std::uint64_t offset, Reference where)
{
std::uint64_t newPos = 0;
switch (where)
{
case Reference::CURRENT:
newPos = m_offset + m_relativePosition + offset;
break;
case Reference::START:
newPos = m_offset + offset;
break;
case Reference::END:
newPos = m_offset + m_compressedSize + offset;
break;
}
m_stream->Seek(newPos, Reference::START);
m_relativePosition = m_stream->Ftell() - m_offset;
}
int ZipFileStream::Ferror()
{
return 0;
}
bool ZipFileStream::Feof()
{
return m_relativePosition >= m_compressedSize;
}
std::uint64_t ZipFileStream::Ftell()
{
return m_relativePosition;
}
} /* xPlat */

Просмотреть файл

@ -2,6 +2,7 @@
#include "StreamBase.hpp"
#include "ObjectBase.hpp"
#include "ZipObject.hpp"
#include "ZipFileStream.hpp"
#include <memory>
#include <string>
@ -754,6 +755,36 @@ namespace xPlat {
};//class EndOfCentralDirectoryRecord
std::vector<std::string> ZipObject::GetFileNames()
{
std::vector<std::string> result;
std::for_each(m_streams.begin(), m_streams.end(), [&](auto it)
{
result.push_back(it.first);
});
return result;
}
std::shared_ptr<StreamBase> ZipObject::GetFile(std::string& fileName)
{
return m_streams[fileName];
}
void ZipObject::RemoveFile(std::string& fileName)
{
throw NotImplementedException();
}
std::shared_ptr<StreamBase> ZipObject::OpenFile(std::string& fileName, FileStream::Mode mode)
{
throw NotImplementedException();
}
void ZipObject::CommitChanges()
{
throw NotImplementedException();
}
ZipObject::ZipObject(StreamBase* stream)
{
// Confirm that the file IS the correct format
@ -801,23 +832,13 @@ namespace xPlat {
centralFileHeader.second->GetRelativeOffsetOfLocalHeader() + localFileHeader->Size(),
localFileHeader->GetCompressedSize(),
localFileHeader->GetUncompressedSize(),
localFileHeader->GetCompressionType() == CompressionType::Deflate
localFileHeader->GetCompressionType() == CompressionType::Deflate,
stream
);
m_streams.insert(std::make_pair(
centralFileHeader.second->GetFileName(),
zipFileStream));
}
}
std::vector<std::string> ZipObject::GetFileNames()
{
std::vector<std::string> result;
std::for_each(m_streams.begin(), m_streams.end(), [&](auto it)
{
result.push_back(it.first);
});
return result;
}
} // ZipObject::ZipObject
} // namespace xPlat

Просмотреть файл

@ -3,6 +3,7 @@
#include "StreamBase.hpp"
#include "FileStream.hpp"
#include "ZipObject.hpp"
#include "DirectoryObject.hpp"
#include <string>
#include <memory>
@ -49,21 +50,53 @@ unsigned int ResultOf(char* source, char* destination, Lambda lambda)
return result;
}
XPLATAPPX_API unsigned int UnpackAppx(char* source, char* destination)
XPLATAPPX_API unsigned int UnpackAppx(char* from, char* to)
{
return ResultOf(source, destination, [&]() {
std::string appxFileName(source);
auto rawFile = std::make_unique<xPlat::FileStream>(
std::move(appxFileName),
xPlat::FileStream::Mode::READ);
return ResultOf(from, to, [&]() {
std::string source(from);
std::string target(to);
xPlat::DirectoryObject directory(std::move(target));
auto rawFile = std::make_unique<xPlat::FileStream>(std::move(source), xPlat::FileStream::Mode::READ);
{
xPlat::ZipObject zip(rawFile.get());
auto fileNames = zip.GetFileNames();
for (auto fileName : fileNames)
{
auto sourceFile = zip.GetFile(fileName);
auto targetFile = directory.OpenFile(fileName, xPlat::FileStream::Mode::WRITE);
sourceFile->CopyTo(targetFile.get());
}
}
});
}
XPLATAPPX_API unsigned int PackAppx (char* source, char* destination)
XPLATAPPX_API unsigned int PackAppx (char* from, char* to)
{
return ResultOf(source, destination, []() {
// TODO: implement here
return ResultOf(from, to, [&]() {
std::string source(from);
std::string target(to);
xPlat::DirectoryObject directory(std::move(source));
auto rawFile = std::make_unique<xPlat::FileStream>(std::move(target), xPlat::FileStream::Mode::WRITE);
{
xPlat::ZipObject zip(rawFile.get());
auto fileNames = directory.GetFileNames();
for (auto fileName : fileNames)
{
auto sourceFile = directory.GetFile(fileName);
auto targetFile = zip.OpenFile(fileName, xPlat::FileStream::Mode::WRITE);
sourceFile->CopyTo(targetFile.get());
}
zip.CommitChanges();
}
});
}