How to manage file (replace, delete...) in Qt? - c++

I've a text file with its backup copy: backup copy has the same name with only a "2" as last character of the extension (example: Original: Myfile.txt - Backup: Myfile.txt2).
Sometimes I need to replace the original one with the backup; I do the following:
QFile BackupFile("Myfile.txt2"); // backup copy
QString nameFile = BackupFile.fileName();// name of backup copy of file
nameFile.chop(1); // remove the last letter of file name, so nameFile now is the same of Original file
QFile originalFile(nameFile); // Original copy
originalFile.remove(); // delete the original file
BackupFile.rename(nameFile); // rename the backup file as original
BackupFile.close(); // close the file
This works, but it seems too complex. I'd like something easier.
Do you have any suggestion?

I think this code can be simple method. However, you should add code for error case, such as 'check whether backup file exist.', etc..
auto ReplaceWithBackup = []( QString& backupName ) -> bool
{
QString originName = backupName;
originName.chop( 1 );
if ( QFile::exists( originName ) )
{
QFile::remove( originName );
}
return QFile::rename( backupName, originName );
};
if ( ReplaceWithBackup( "Myfile.txt2") == false )
{
// error
}

If the files are in the same directory, you can use QDir::rename. Otherwise reading one file and writing in the other is required. Here is my version of the first case.
// Generate some test data
{
QFile bf( "Myfile.txt2" );
bf.open(QIODevice::WriteOnly);
bf.write("Backup data");
QFile( "Myfile.txt" ).open(QIODevice::WriteOnly);
}
//Assume you know which back-up file to restore
QString backupFn("Myfile.txt2");
//Actual code
QString origFn = backupFn.mid( 0, backupFn.size()-1 ); //"guess" the original file name.
QFile::remove( origFn ); //Use static version to delete file by name (No QFile instance required)
QDir().rename(backupFn,origFn);
However, each line requires many checks and validations e.g. is the provided backup-file name a valid backup name, did remove/rename succeded, etc, etc.

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.

how do i change a file extension on qt

I have a piece of code to download a file from server. However, due to server constraint, I can not put .exe file at server. So I rename my XXX.exe file to XXX.alt(just a random extension) and put it on server.
Now my code can download XXX.alt, but how can I change the file name from XXX.alt back to XXX.exe when in QT environment?
Use QFileInfo to get the path without the last extension then append the new extension.
QFileInfo info(fileName);
QString strNewName = info.path() + "/" + info.completeBaseName() + ".exe";
Just use rename function from 'stdio.h'.
char oldname[] ="XXX.alt";
char newname[] ="XXX.exe";
result= rename( oldname , newname );
if ( result == 0 )
puts ( "File successfully renamed" );
else
perror( "Error renaming file" );
One solution is to find the last '.', and replace the substring from that position to the end with the substring you want.
Exactly how to do it, there are many ways using both std::string and QString, as both support finding characters in the string (and doing a search from the end to the beginning as well), and replace substrings.
Prefer to use baseName()
QFileInfo info(fileName);
QString strNewName = info.path() + info.baseName() + ".exe";
QString QFileInfo::completeBaseName () const
Returns file name with shortest extension removed (file.tar.gz -> file.tar)
QString QFileInfo::baseName () const
Returns file name with longest extension removed (file.tar.gz -> file)

Add unique suffix to file name

Sometimes I need to ensure I'm not overwriting an existing file when saving some data, and I'd like to use a function that appends a suffix similar to how a browser does it - if dir/file.txt exists, it becomes dir/file (1).txt.
This is an implementation I've made, that uses Qt functions:
// Adds a unique suffix to a file name so no existing file has the same file
// name. Can be used to avoid overwriting existing files. Works for both
// files/directories, and both relative/absolute paths. The suffix is in the
// form - "path/to/file.tar.gz", "path/to/file (1).tar.gz",
// "path/to/file (2).tar.gz", etc.
QString addUniqueSuffix(const QString &fileName)
{
// If the file doesn't exist return the same name.
if (!QFile::exists(fileName)) {
return fileName;
}
QFileInfo fileInfo(fileName);
QString ret;
// Split the file into 2 parts - dot+extension, and everything else. For
// example, "path/file.tar.gz" becomes "path/file"+".tar.gz", while
// "path/file" (note lack of extension) becomes "path/file"+"".
QString secondPart = fileInfo.completeSuffix();
QString firstPart;
if (!secondPart.isEmpty()) {
secondPart = "." + secondPart;
firstPart = fileName.left(fileName.size() - secondPart.size());
} else {
firstPart = fileName;
}
// Try with an ever-increasing number suffix, until we've reached a file
// that does not yet exist.
for (int ii = 1; ; ii++) {
// Construct the new file name by adding the unique number between the
// first and second part.
ret = QString("%1 (%2)%3").arg(firstPart).arg(ii).arg(secondPart);
// If no file exists with the new name, return it.
if (!QFile::exists(ret)) {
return ret;
}
}
}
QTemporaryFile can do it for non-temporary files, despite its name:
QTemporaryFile file("./foobarXXXXXX.txt");
file.open();
// now the file should have been renamed to something like ./foobarQSlkDJ.txt
file.setAutoRemove(false);
// now the file will not be removed when QTemporaryFile is deleted
A better solution is to use GUID
Or you can generate a hash based on bytes collected from within a file, either randomly or based on some data property that is fairly unique from file to file.

RichTextBox SaveFile()

How can I pass a string as a path into here
void SaveLogFile()
{
logTxt->SaveFile(String::Concat
(System::Environment::GetFolderPath
(System::Environment::SpecialFolder::Personal),
"\\Testdoc.rtf"), RichTextBoxStreamType::RichNoOleObjs);
}
I can't figure out how to set a non SpecialFolder
From MSDN:
void SaveMyFile()
{
// Create a SaveFileDialog to request a path and file name to save to.
SaveFileDialog^ saveFile1 = gcnew SaveFileDialog;
// Initialize the SaveFileDialog to specify the RTF extention for the file.
saveFile1->DefaultExt = "*.rtf";
saveFile1->Filter = "RTF Files|*.rtf";
// Determine whether the user selected a file name from the saveFileDialog.
if ( saveFile1->ShowDialog() == System::Windows::Forms::DialogResult::OK &&
saveFile1->FileName->Length > 0 )
{
// Save the contents of the RichTextBox into the file.
richTextBox1->SaveFile( saveFile1->FileName );
}
}
See how the System::String^ is created here. Do it this way, too...