Rename a non-empty directory in an archive using libarchive - c++

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
}
}

Related

std::filesystem::copy() only copies files in folder

I am trying to copy a folder to another folder using std::filesystem::copy(), so far it only copies the files and folders within the folder I'm trying to move over, instead of the folder itself. Any ideas why?
I know this could be done manually by creating a new directory using std::filesystem::create_directory(), but it won't carry over the security info and permissions from the original folder.
EDIT:
path = C:\Users\Test\Folder
nPath = C:\Users\Test\Movehere
boolean CopyToPath(std::string path, std::string nPath) {
if (fs::exists(path) && fs::exists(nPath)) {
try {
//path = Folder To Copy, nPath = Destination
fs::copy(path, nPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive);
return true;
}
catch (fs::filesystem_error e) {
std::cout << e.what() << "\n";
return false;
}
}
else
return false;
}
This is expected behavior, as documented at https://en.cppreference.com/w/cpp/filesystem/copy:
Otherwise, if from is a directory and either options has copy_options::recursive or is copy_options::none,
If to does not exist, first executes create_directory(to, from) (creates the new directory with a copy of the old directory's attributes)
Then, whether to already existed or was just created, iterates over the files contained in from as if by for (const std::filesystem::directory_entry& x : std::filesystem::directory_iterator(from)) and for each directory entry, recursively calls copy(x.path(), to/x.path().filename(), options | in-recursive-copy), where in-recursive-copy is a special bit that has no other effect when set in options. (The sole purpose of setting this bit is to prevent recursive copying subdirectories if options is copy_options::none.)
In other words, copy() does not copy the source directory itself, only the contents of the directory.
If your goal is to make a copy of Folder itself inside of Movehere, ie C:\Users\Test\Movehere\Folder, then you will have to extract Folder from the source directory and append it to the target path, eg:
fs::path src = "C:\\Users\\Test\\Folder";
fs::path dest = "C:\\Users\\Test\\Movehere";
dest /= src.filename();
fs::create_directory(dest, src);
// only because CopyToPath() requires this due
// to its use of fs::exists(), instead of letting
// fs::copy() create it...
CopyToPath(src, dest);

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.

Poco::Zip set Extension List

After successfully compress the zip file, I want to add extensions list in order to only zip any files that have extensions within the Extensions List.
set <string> extensionsSet;
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress c(fos, true);
extensionsSet.insert("txt");
c.setStoreExtensions(extensionsSet);//set extensions List
set <string> a = c.getStoreExtensions();//a contains 1 string which is txt
Poco::File aFile(source);
if (aFile.exists())
{
if (aFile.isDirectory())
{
Poco::Path sourceDir(source);
sourceDir.makeDirectory();
c.addRecursive(sourceDir, Poco::Zip::ZipCommon::CompressionMethod::CM_DEFLATE,
Poco::Zip::ZipCommon::CL_NORMAL, false);
}
else if (aFile.isFile())
{
Poco::Path p(aFile.path());
c.addFile(p, p.getFileName(), Poco::Zip::ZipCommon::CompressionMethod::CM_AUTO,
Poco::Zip::ZipCommon::CL_NORMAL);
}
}
else {
_log.EndMethod();
throw new FileNotFoundException("File Not Found");
}
c.close(); // MUST be done to finalize the Zip file
fos.close();
Can anyone help me to find out what's wrong within my code, because I don't see any difference when I set the extensions List? I want to zip a file(e.g test.txt), following my code should "test.txt" be included in the zip file?

Windows: File rename and directory iteration clash

I'm using boost::filesystem to rename a file like this:
boost::filesystem::rename(tmpFileName, targetFile);
tmpFileName / targetFile are of type boost::filsystem::path.
While doing this, I iterate over the directory using this code in another thread:
directory_iterator end_itr;
for (directory_iterator itr(dirInfoPath); itr != end_itr; ++itr)
{
path currentPath = itr->path();
if (is_directory(itr->status()))
{
// skip directories
}
else
{
std::string file_name = currentPath.leaf();
if (!boost::algorithm::starts_with(file_name, "new")
&& !boost::algorithm::starts_with(file_name, "finished")
&& boost::algorithm::ends_with(file_name, ".info"))
{
// save found filename in some variable
return true;
}
}
}
When this code is executed, I get an exception while renaming:
boost::filesystem::rename: The process cannot access the file because it is being used by another process
Is it possible that the iteration and the rename operation clash, because they both access the directory inode, or do I have some other problem?
code you provided doesn't contain any file open operations, so it cannot lock the file. you iterate over directory and renaming file, right? so it's possible this file is really used by another application like file viewer or something else, it's quite typical error. or you have it opened in your app somewhere else

SharpLibZip: Add file without path

I'm using the following code, using the SharpZipLib library, to add files to a .zip file, but each file is being stored with its full path. I need to only store the file, in the 'root' of the .zip file.
string[] files = Directory.GetFiles(folderPath);
using (ZipFile zipFile = ZipFile.Create(zipFilePath))
{
zipFile.BeginUpdate();
foreach (string file in files)
{
zipFile.Add(file);
}
zipFile.CommitUpdate();
}
I can't find anything about an option for this in the supplied documentation. As this is a very popular library, I hope someone reading this may know something.
My solution was to set the NameTransform object property of the ZipFile to a ZipNameTransform with its TrimPrefix set to the directory of the file. This causes the directory part of the entry names, which are full file paths, to be removed.
public static void ZipFolderContents(string folderPath, string zipFilePath)
{
string[] files = Directory.GetFiles(folderPath);
using (ZipFile zipFile = ZipFile.Create(zipFilePath))
{
zipFile.NameTransform = new ZipNameTransform(folderPath);
foreach (string file in files)
{
zipFile.BeginUpdate();
zipFile.Add(file);
zipFile.CommitUpdate();
}
}
}
What's cool is the the NameTransform property is of type INameTransform, allowing customisation of the name transforms.
How about using System.IO.Path.GetFileName() combined with the entryName parameter of ZipFile.Add()?
string[] files = Directory.GetFiles(folderPath);
using (ZipFile zipFile = ZipFile.Create(zipFilePath))
{
zipFile.BeginUpdate();
foreach (string file in files)
{
zipFile.Add(file, System.IO.Path.GetFileName(file));
}
zipFile.CommitUpdate();
}
The MSDN entry for Directory.GetFiles() states that The returned file names are appended to the supplied path parameter. (http://msdn.microsoft.com/en-us/library/07wt70x2.aspx), so the strings you are passing to zipFile.Add() contain the path.
According to the SharpZipLib documentation, there is an overload of the Add method,
public void Add(string fileName, string entryName)
Parameters:
fileName(String) The name of the file to add.
entryName (String) The name to use for the ZipEntry on the Zip file created.
Try this approach:
string[] files = Directory.GetFiles(folderPath);
using (ZipFile zipFile = ZipFile.Create(zipFilePath))
{
zipFile.BeginUpdate();
foreach (string file in files)
{
zipFile.Add(file, Path.GetFileName(file));
}
zipFile.CommitUpdate();
}