Add unique suffix to file name - c++

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.

Related

How to modify the filename of the S3 object uploaded using the Kafka Connect S3 Connector?

I've been using the S3 connector for a couple of weeks now, and I want to change the way the connector names each file. I am using the HourlyBasedPartition, so the path to each file is already enough for me to find each file, and I want the filenames to be something generic for all the files, like just 'Data.json.gzip' (with the respective path from the partitioner).
For example, I want to go from this:
<prefix>/<topic>/<HourlyBasedPartition>/<topic>+<kafkaPartition>+<startOffset>.<format>
To this:
<prefix>/<topic>/<HourlyBasedPartition>/Data.<format>
The objective of this is to only make one call to S3 to download the files later, instead of having to look for the filename first and then download it.
Searching through the files from the folder called 'kafka-connect-s3', I found this file:
https://github.com/confluentinc/kafka-connect-storage-cloud/blob/master/kafka-connect-s3/src/main/java/io/confluent/connect/s3/TopicPartitionWriter.java which at the end has some of the following functions:
private RecordWriter getWriter(SinkRecord record, String encodedPartition)
throws ConnectException {
if (writers.containsKey(encodedPartition)) {
return writers.get(encodedPartition);
}
String commitFilename = getCommitFilename(encodedPartition);
log.debug(
"Creating new writer encodedPartition='{}' filename='{}'",
encodedPartition,
commitFilename
);
RecordWriter writer = writerProvider.getRecordWriter(connectorConfig, commitFilename);
writers.put(encodedPartition, writer);
return writer;
}
private String getCommitFilename(String encodedPartition) {
String commitFile;
if (commitFiles.containsKey(encodedPartition)) {
commitFile = commitFiles.get(encodedPartition);
} else {
long startOffset = startOffsets.get(encodedPartition);
String prefix = getDirectoryPrefix(encodedPartition);
commitFile = fileKeyToCommit(prefix, startOffset);
commitFiles.put(encodedPartition, commitFile);
}
return commitFile;
}
private String fileKey(String topicsPrefix, String keyPrefix, String name) {
String suffix = keyPrefix + dirDelim + name;
return StringUtils.isNotBlank(topicsPrefix)
? topicsPrefix + dirDelim + suffix
: suffix;
}
private String fileKeyToCommit(String dirPrefix, long startOffset) {
String name = tp.topic()
+ fileDelim
+ tp.partition()
+ fileDelim
+ String.format(zeroPadOffsetFormat, startOffset)
+ extension;
return fileKey(topicsDir, dirPrefix, name);
}
I don't know if this can be customised to what I want to do but seems to be somehow near/related to my intentions. Hope it helps.
(Submitted an issue to Github as well: https://github.com/confluentinc/kafka-connect-storage-cloud/issues/369)

Write to existing json file

I am using this code to add to my existing JSON file. However It completely overrides my JSON file and just puts one JSON object in it when I would just like to add another item to the list of items in my JSON file. How would I fix this?
Json::Value root;
root[h]["userM"] = m;
root[h]["userT"] = t;
root[h]["userF"] = f;
root[h]["userH"] = h;
root[h]["userD"] = d;
Json::StreamWriterBuilder builder;
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
std::ofstream outputFileStream("messages.json");
writer-> write(root, &outputFileStream);
My recommendation is
Load the file into a Json::Value
Add or change whatever fields you want
Overwrite the original file with the updated Json::Value
Doing this is going to be the least error-prone method, and it'll work quickly unless you have a very large Json file.
How to read in the entire file
This is pretty simple! We make the root, then just use the >> operator to read in the file.
Json::Value readFile(std::istream& file) {
Json::Value root;
Json::Reader reader;
bool parsingSuccessful = reader.parse( file, root );
if(not parsingSuccessful) {
// Handle error case
}
return root;
}
See this documentation here for more information

Drag & Drop Filename Visual (Managed) C++

I have a RichTextBox that I would like to allow a user to drag and drop a file from disk into. All that should appear in the textbox is the filename(s). This code currently adds "System.String[]" to the textbox instead of the filename. When I change the DataFormats::FileDrop to DataFormats::Text as this MSDN would seem to suggest, I get a NULL dereference error.
The RichTextBox name is rtbFile. In my constructor, I have:
this->rtbFile->AllowDrop = true;
I set up the events like this (within InitializeComponents):
this->rtbFile->DragEnter += gcnew System::Windows::Forms::DragEventHandler(this, &VanicheMain::rtbFile_DragEnter);
this->rtbFile->DragDrop += gcnew System::Windows::Forms::DragEventHandler(this, &VanicheMain::rtbFile_DragDrop);
The functions are defined as follows:
void rtbFile_DragEnter(System::Object ^sender, System::Windows::Forms::DragEventArgs ^ e) {
if (e->Data->GetDataPresent(DataFormats::FileDrop))
e->Effect = DragDropEffects::Copy;
else
e->Effect = DragDropEffects::None;
}
System::Void rtbFile_DragDrop(System::Object ^sender, System::Windows::Forms::DragEventArgs ^e){
int i = rtbFile->SelectionStart;;
String ^s = rtbFile->Text->Substring(i);
rtbFile->Text = rtbFile->Text->Substring(0, i);
String ^str = String::Concat(rtbFile->Text, e->Data->GetData(DataFormats::FileDrop)->ToString());
rtbFile->Text = String::Concat(str, s);
}
Dragging files always produces an array of strings. Each array element is the path to one of the files that are dragged. You'll need to write the extra code to cast the return value of GetData() to an array and iterate it, reading the content of each file. Similar to this:
array<String^>^ paths = safe_cast<array<String^>^>(e->Data->GetData(DataFormats::FileDrop));
for each (String^ path in paths) {
String^ ext = System::IO::Path::GetExtension(path)->ToLower();
if (ext == ".txt") rtbFile->AppendText(System::IO::File::ReadAllText(path));
}

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

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.

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