Add files to the existing archive in XZip, C++, Windows - c++

I am creating a windows service that periodically copies the contents of a folder to an archive. I've searched for archiving libraries and found suggestions to use XZip.
Now I can create a new archive using code like this:
HZIP arch = CreateZip((void*)Archive.c_str(), 0, ZIP_FILENAME);
if (arch == nullptr)
return addLogMessage("Unable to open archive.");
for (const auto& file : std::filesystem::directory_iterator(Directory)) {
const std::wstring filePath = file.path().wstring();
const std::wstring nameInArchive = file.path().filename().wstring();
if (ZipAdd(arch, (TCHAR*)nameInArchive.c_str(), (TCHAR*)filePath.c_str(), 0, ZIP_FILENAME) != ZR_OK) {
std::string what = "Error: Adding '" + std::string(filePath.begin(), filePath.end()) + "' to the archive.";
addLogMessage(what.c_str());
}
}
CloseZip(arch);
I also want to add files to an existing archive. When I run the code below, I get a ZR_ZMODE error from the ZipAdd function, which means tried to mix create / open zip archive:
if (std::filesystem::exists(Archive)) {
HZIP arch = OpenZip((void*)Archive.c_str(), 0, ZIP_FILENAME);
if (arch == nullptr)
return addLogMessage("Unable to re-open existing archive.");
for (const auto& file : std::filesystem::directory_iterator(Directory)) {
const std::wstring filePath = file.path().wstring();
const std::wstring nameInArchive = file.path().filename().wstring();
if (ZipAdd(arch, (TCHAR*)nameInArchive.c_str(), (TCHAR*)filePath.c_str(), 0, ZIP_FILENAME) != ZR_OK) {
std::string what = "Error: Adding '" + std::string(filePath.begin(), filePath.end()) + "' to the archive.";
addLogMessage(what.c_str());
}
}
CloseZip(arch);
}
I assume that in XZip library, I'm not able to add files to the existing archive. Is it true?
EDIT: I've solved the problem with different library, libzip. Now I can add new files to the old archive, and everything works fine. I've posted source code and compiling/linking instruction here.
I am still interested, if there is an option for adding files to the existing archive in XZip library, so I will not close the question for now.

Related

Append files to an existing zip file with Poco::Zip

After successfully compress the folder, here is my situation :
If append = true and overWrite = false I have to check whether if the target zip file exists or not if existed I will check the existed zip file which files it doesn't contain and append new file from the source folder to it.
My question is:
How can I open the zip file and put it to the compress object? or which others library in Poco should I use to open zip stream? I'm trying to use std::ifstream but Poco::zip::Compress doesn't seem to receive an std::ifstream
I surely have to modify the Poco source code itself to match with my requirement. Thanks in advance.
void ZipFile(string source, string target, List extensions, bool append, bool overWrite)
{
Poco::File tempFile(source);
if (tempFile.exists())
{
if (Poco::File(target).exists() && append && !overWrite) {
fs::path targetPath = fs::path(target);
std::ifstream targetFileStream(targetPath.string(), std::ios::binary);
std::ofstream outStream(target, ios::binary);
CompressEx compress(outStream, false, false);
if (tempFile.isDirectory())
{
Poco::Path sourceDir(source);
sourceDir.makeDirectory();
compress.addRecursive(sourceDir, Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL, false);
}
else if (tempFile.isFile())
{
Poco::Path path(tempFile.path());
compress.addFile(path, path.getFileName(), Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL);
}
compress.close(); // MUST be done to finalize the Zip file
outStream.close();
}
}
No need to modify the Poco source code. Poco allows you to get the contents of an archive and add files to it.
First, open the target archive to check which files are already in there:
Poco::ZipArchive archive(targetFileStream);
Then collect all files you want to add, that are not in the archive, yet:
std::vector<fs::path> files;
if (fs::is_directory(source)) {
for(auto &entry : fs::recursive_directory_iterator())
// if entry is file and not in zip
if (fs::is_regular_file(entry)
&& archive.findHeader(fs::relative(entry.path, source)) == archive.headerEnd()) {
files.push_back(entry.path);
}
} else if (fs::is_regular_file(entry)
&& archive.findHeader(source) == archive.headerEnd()) {
files.push_back(source);
}
Finally, add the files to your zip
Poco::Zip::ZipManipulator manipulator(target, false);
for(auto &file : files)
manipulator.addFile(fs::relative(file, source), file,
Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL);
I had no opportunity to test this. So try it out and see what needs to be done to make it work.

Rename a non-empty directory in an archive using libarchive

I'm trying to rename the entries of an archive using the libarchive library.
In particular I'm using the function archive_entry_set_pathname.
Files and empty directories are correctly renamed, but unfortunately this is not working if a directory is not empty: instead of being renamed, a new empty directory with the new name is created as sibling of the target directory, which has the old name.
Relevant code snippet:
...
while (archive_read_next_header(inputArchive, &entry) == ARCHIVE_OK) {
if (file == QFile::decodeName(archive_entry_pathname(entry))) {
// FIXME: not working with non-empty directories
archive_entry_set_pathname(entry, QFile::encodeName(newPath));
}
int header_response;
if ((header_response = archive_write_header(outputArchive, entry)) == ARCHIVE_OK) {
... // write the (new) outputArchive on disk
}
}
What's wrong with non-empty directories?
In an archive, the files are stored with their full path names relative to the root of the archive. Your code only matches the directory entry, you also need to match all entries below that directory and rename them. I'm no Qt expert and I haven't tried this code, but you will get the idea.
QStringLiteral oldPath("foo/");
QStringLiteral newPath("bar/");
while (archive_read_next_header(inputArchive, &entry) == ARCHIVE_OK) {
QString arEntryPath = QFile::decodeName(archive_entry_pathname(entry));
if(arEntryPath.startsWith(oldPath) {
arEntryPath.replace(0, oldPath.length(), newPath);
archive_entry_set_pathname(entry, QFile::encodeName(arEntryPath));
}
int header_response;
if ((header_response = archive_write_header(outputArchive, entry)) == ARCHIVE_OK) {
... // write the (new) outputArchive on disk
}
}

Accessing files in resources folder in mac osx app bundle

I would like to access files which are inside Resources in app bundle. Unfortunately i cannot use QT resorces, as i'm using CascadeClassifier from opencv. My current paths are
const std::string FACE_CLASIFIER_PATH = "/Resources/haarcascade_frontalface_default.xml";
const std::string EYES_CLASIFIER_PATH = "/Resources/haarcascade_mcs_eyepair_big.xml";
I also tried
const std::string FACE_CLASIFIER_PATH = "../Resources/haarcascade_frontalface_default.xml";
const std::string EYES_CLASIFIER_PATH = "../Resources/haarcascade_mcs_eyepair_big.xml";
But nether of them work. As for config both files are present inside MyApp.app/Contents/Resources, i include them using qmake
mac {
APP_XML_FILES.files = ../haarcascade_frontalface_default.xml ../haarcascade_mcs_eyepair_big.xml
APP_XML_FILES.path = Contents/Resources
QMAKE_BUNDLE_DATA += APP_XML_FILES
}
I would appreciate any help with this issue
You don't say what you want to do with the files, though that could be due to my lack of knowledge of opencv. However you can use the Core Foundation classes to get paths to files in the resources folder: -
CFURLRef appUrlRef;
appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("somefile"), NULL, NULL);
// do something with the file
//...
// Ensure you release the reference
CFRelease(appUrlRef);
With a CFURLRef, you can use Apple's documentation to get what you need from it.
For example, if you want a file path: -
CFStringRef filePathRef = CFURLCopyPath(appUrlRef);
// Always release items retrieved with a function that has "create or "copy" in its name
CFRelease(filePathRef);
From the file path, we can get a char* to the path: -
const char* filePath = CFStringGetCStringPtr(filePathRef, kCFStringEncodingUTF8);
So, putting it all together, if you want to get a char* path to haarcascade_mcs_eyepair_big.xml: -
CFURLRef appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("haarcascade_mcs_eyepair_big.xml"), NULL, NULL);
CFStringRef filePathRef = CFURLCopyPath(appUrlRef);
const char* filePath = CFStringGetCStringPtr(filePathRef, kCFStringEncodingUTF8);
// Release references
CFRelease(filePathRef);
CFRelease(appUrlRef);

C++ iterating through files and directories

I'm working on a C++ program that will automatically backup my work to my FTP server. So far I am able to upload a single file, by specifying a file name using this
CString strFilePath = szFile ;
int iPos = strFilePath.ReverseFind('\\');
CString strFileName = strFilePath.Right((strFilePath.GetLength()- iPos-1) );
CString strDirPath = m_szFolderDroppedIn ;
strDirPath = strDirPath.Mid(0,strDirPath.GetLength() - 1);
int iPost = strDirPath.ReverseFind('\\');
CString strDirName = strDirPath.Right((strDirPath.GetLength()- iPost -1) );
bool curdir = ftpclient.SetServerDirectory((char*)strDirName.GetBuffer(strDirName.GetLength()));
//Upload to Server
int uploadret = ftpclient.PutFile(szFile,(char*)strFileName.GetBuffer(strFileName.GetLength()),0,true,dwLastError);
m_lsDroppedFiles.RemoveAll();
break;
}
Now I want to be able to iterate through a directory (Which contains subdirectories) and recursively call. I'm having a problem getting a hold of the files in the directory.
Any help or code snippet...
Since you are using MFC, you can use the CFileFind class. Example code is given in MSDN. Alternatively, you can use boost.filesystem for the same.
#Swapnil: If you use boost::filesystem, there is a recursive_directory_iterator

Using taglib in a Qt application

I'd like to get the length of a media file in a qt application i'm building and so i decided to use taglib. This is the methos that is meant to read the length
void loadMetaData(QString file) {
QByteArray fileName = QFile::encodeName( file );
const char * encodedName = fileName.constData();
TagLib::FileRef fileref = TagLib::FileRef( encodedName );
if (fileref.isNull())
{
qDebug() << "Null";
}
else
{
qDebug() << "Not Null";
}
}
Problem is fileref is always null for some reason and i can't figure out why......
Use the getter audioProperties() on your FileRef object. The returned pointer contains the length of the file in seconds.
TagLib# is able to work with some Theora files. I used it in a project but found it wouldn't work with many Theora videos (I don't think any converted using libtheora 1.1 worked).
TagLib.File file = TagLib.File.Create(#"c:\video.ogv");
string height = file.Properties.VideoHeight;
This is for the .NET, not C++ though.