I just copied all my cds to my computer with a program called "Sound Juicer". It works fine, it creates for each artist a folder and it it for each album another folder. And of course in these folders the mp3 files.
The problem is I want the Tracknumber, the Artist and then the Tracktitle as name for my songs. What Sound Juicer does is adding d1t in front of the file which stands for "Disk 1 Title".
I'm a programmer so I used this problem to practice a little bit. This works :
void MainWindow::rename( const QString & text )
{
static int _files = 0;
QDir dir( text );
QFileInfoList a = dir.entryInfoList( QDir::Files | QDir::Dirs );
for( int i = 2; i < a.size(); i++ )
{
static QDir tmp;
if( a.at( i ).isDir() )
rename( a.at( i ).absoluteFilePath() );
if( a.at( i ).fileName().startsWith( "d1t" ) || a.at( i ).fileName().startsWith( "d2t" ) )
{
QString newFile = a.at( i ).fileName().remove(0,3);
tmp = a.at( i ).dir();
if( !tmp.rename( a.at( i ).fileName(), newFile ) )
qDebug() << "Failed";
_files++;
}
}
}
It checks a directory, selects the first file or directory and checks what it is. If it is a directory it calls itself (recursion) and starts again until he finds some files or no more directories exist. If a file is found, it renames it and adds 1 to the file counter.
However, it only renamed all files in the first 2 or 3 directories. After that it caused a SIGSEGV. Does anyone knows whats wrong?
Example of my directories :
1 Directory ("Sum 41") -> 1 Subdirectory ("All Killer No Filler") ->
Files "d1t01. Sum 41 - Introduction to Destruction.mp3" etc. ...
2 Subdirectory ("Blah Blah") -> Files ...
2 Directory ("Shinedown") -> 1 Subdirectory ("Sound of Madness") ->
Files d1t01. Shinedown - Devour.mp3 etc...
3 Directory ("Guns N’ Roses") -> Subdirectory ("Blah Blah") -> files ...
Subdirectory ("Blah ") -> files ...
With static you try to tell the compiler you want only one instance in the whole program, yet you put it in a for loop. That is not really clean. In this situation static is useless. Static can also cause problems due to the static initialization chaos problem. So if you dont really need static initialization, drop it and make it a local or a class variable.
Rewriting your method, to be able to read it better, i saw that you call the method recursively and that could cause an infinite loop.
Another problem is your files variable, what is the purpose of it?
void MainWindow::rename( const QString & text ) {
int files = 0;
QDir dir(text); // does QDir also accept wrong paths?
QFileInfoList list = dir.entryInfoList(QDir::Files|QDir::Dirs); // does it return a list in all cases?
foreach (QFileInfo entry, list) {
if (entry.isDir()) {
//is everything always ok when doing this?
// POTENTIAL infinite loop
rename(entry.absoluteFilePath());
}
else {
QString fileName = entry.fileName();
if(fileName.startsWith("d1t") || fileName.startsWith("d2t")) {
if (!entry.dir().rename(fileName, fileName.remove(0,3))) qDebug() << "Failed";
files++;
}
}
}
}
I have with the help of fonZ and Frank Osterfeld somehow fixed it. I finally created a GUI to select just a single path. However, I have done it. This piece of code looks up all directories without causing an infinite loop or an overflow (so far). The problem was something with QDir's and QFileInfo's functions to get the path. I played a while with it and this came out :
void MainWindow::rename( const QString& path )
{
//If invalid path return
if( path.isEmpty() || ( !QDir( path ).exists() ) )
return;
//entryInfoList( QDir::NoDotAndDotDot ) doesn't work.
QFileInfoList fileList = QDir( path ).entryInfoList();
foreach (QFileInfo entry, fileList ) {
//Eliminating wrong paths
if( entry.isDir() ){
if( entry.filePath().endsWith(".") )
continue;
//Start function again with new directory
rename( entry.filePath() );
}
else{
QString fileName = entry.fileName();
if( fileName.startsWith( "d1t" ) || fileName.startsWith( "d2t" ) ){
//Remove those characters
fileName.remove( 0, 3 );
//If renaming is successful, increment the successful files
//If not, increment the failed files and print an error
if( entry.dir().rename( entry.fileName(), fileName ) )
files++;
else{
addError( "Could not rename " + entry.fileName() + " to " + fileName );
filesFailed++;
}
}
}
}
}
Related
I need to get a list of folders in the directory, but only the folders. No files are needed. Only folders. I use filters to determine if this is a folder, but they do not work and all files and folders are output.
string root = "D:\\*";
cout << "Scan " << root << endl;
std::wstring widestr = std::wstring(root.begin(), root.end());
const wchar_t* widecstr = widestr.c_str();
WIN32_FIND_DATAW wfd;
HANDLE const hFind = FindFirstFileW(widecstr, &wfd);
In this way, I check that it is a folder.
if (INVALID_HANDLE_VALUE != hFind)
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
How to solve a problem?
There are two ways to do this: The hard way, and the easy way.
The hard way is based on FindFirstFile and FindNextFile, filtering out directories as needed. You will find a bazillion samples that outline this approach, both on Stack Overflow as well as the rest of the internet.
The easy way: Use the standard directory_iterator class (or recursive_directory_iterator, if you need to recurse into subdirectories). The solution is as simple as1:
for ( const auto& entry : directory_iterator( path( L"abc" ) ) ) {
if ( is_directory( entry.path() ) ) {
// Do something with the entry
visit( entry.path() );
}
}
You will have to include the <filesystem> header file, introduced in C++17.
Note: Using the latest version of Visual Studio 2017 (15.3.5), this is not yet in namespace std. You will have to reference namespace std::experimental::filesystem instead.
1 Note in particular, that there is no need to filter out the . and .. pseudo-directories; those aren't returned by the directory iterators.
This function collects folders into given vector. If you set recursive to true, it will be scanning folders inside folders inside folders etc.
// TODO: proper error handling.
void GetFolders( std::vector<std::wstring>& result, const wchar_t* path, bool recursive )
{
HANDLE hFind;
WIN32_FIND_DATA data;
std::wstring folder( path );
folder += L"\\";
std::wstring mask( folder );
mask += L"*.*";
hFind=FindFirstFile(mask.c_str(),&data);
if(hFind!=INVALID_HANDLE_VALUE)
{
do
{
std::wstring name( folder );
name += data.cFileName;
if ( ( data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
// I see you don't want FILE_ATTRIBUTE_REPARSE_POINT
&& !( data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) )
{
// Skip . and .. pseudo folders.
if ( wcscmp( data.cFileName, L"." ) != 0 && wcscmp( data.cFileName, L".." ) != 0 )
{
result.push_back( name );
if ( recursive )
// TODO: It would be wise to check for cycles!
GetFolders( result, name.c_str(), recursive );
}
}
} while(FindNextFile(hFind,&data));
}
FindClose(hFind);
}
Modified from https://stackoverflow.com/a/46511952/8666197
I am trying to open a file from a specific location and it seems to find the path properly, but I can't figure out why it always skips the while loop.
QString utm_file_loc = "C:\\Example\\test\\UTM_Zone.config";
QFile fileutm(utm_file_loc);
QTextStream utm_in(&fileutm);
QString value;
while(!utm_in.atEnd())
{
QString line = utm_in.readLine();
line.replace(" ", "");
if( (line.indexOf("#") <0 || 1 < line.indexOf("#")) &&
(line.contains("UTM_ZONE=")) )
{
value = line.mid(line.indexOf("=")+1);
break;
}
}
The config file is 1 line and contains UTM_ZONE = 17
I thought it might have to do with it being 1 line and so it always thinks it's at the end, but I tried adding more lines before and after to the file and it still skips the loop.
Between the line where you make the File object and the line where you pass it into the QTextStream, you need to open the file:
if ( fileutm.open(QIODevice::ReadOnly) )
{
//Create you QTextStream and use it here...
}
else
{
//Report error opening file here....
}
I'am working with the FileSystemWatcher in C++/CLI. I'am having trouble with moving or copying a non empty folder: When copy a folder with one .txt file in it to the watching folder, then multiple createdand changed events are raised, that's fine, but when I move the same folder, only one single create event for the folder is raised. The problem is, that I need to know witch files are in it, so my idea was to just create a loop in the changed event that recursively searches trough the folder. This works for moving, but when I copy the folder, every event is raised twice.
I can't find an algorithm, so that only one create event for folders and files is raised.
Thanks for your help.
Code:
System::Void SnowDrive::Cloud::FileWatcher_Changed(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string ServerInode,
FileName = c.marshal_as<std::string> (e -> Name),
FilePath = c.marshal_as<std::string> (e -> FullPath),
FtpPath = ToFtpPath (FilePath.substr (0, FilePath.find_last_of ("\\")));
if (FileName.find_last_of ("\\") != string::npos)
FileName = FileName.substr (FileName.find_last_of ("\\") + 1);
for (unsigned int i = 0; i < IgnoreFileList.size (); i++)
{
if (IgnoreFileList[i] == FilePath)
{
IgnoreFileList.erase (IgnoreFileList.begin () + i);
return;
}
}
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
// do something
}
else
{
if (sender -> ToString () != " ")
return;
DIR * Dir;
dirent * FindData;
if((Dir = opendir(FilePath.c_str ())) == NULL)
return;
while ((FindData = readdir(Dir)) != NULL)
{
FileName = FindData -> d_name;
if (FileName == string (".") || FileName == string (".."))
continue;
FileWatcher_Changed (gcnew String (" "), gcnew IO::FileSystemEventArgs (IO::WatcherChangeTypes::Created, gcnew String (FilePath.c_str ()), gcnew String (FileName.c_str ())));
}
}
}
System::Void SnowDrive::Cloud::FileWatcher_Created(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string FilePath = c.marshal_as<std::string> (e -> FullPath);
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
}
FileWatcher_Changed (gcnew String (" "), e);
}
I don't know of a way to get only one Changed event like you want. I do, however, know how to get events for the moved files. You'll want to attach to the Renamed event.
Folder copy with files: One Changed event for the folder, and one per file.
Folder move with files: One Changed event for the folder, one Renamed event per file.
I have a directory with files and folders that I would like to zip. I'm using the qt-project quazip for it. So I thought I write a function that packs all content of a directory including the filestructure.
How can I create the folder in the zip-file? I tried it with QuaZipNewInfo but couldn't make it work.
For example I want to zip the tmp-folder with this content:
tmp/1.txt
tmp/folder1/2.txt
tmp/folder1/3.txt
tmp/folder2/4.txt
tmp/folder2/folder3/5.txt
What I get after extracting the file with a common archive-tool (Archive Utility) is this:
tmp/1.txt
tmp/2.txt
tmp/3.txt
tmp/4.txt
tmp/5.txt
This is what I have so far:
void Exporter::zipFilelist(QFileInfoList& files, QuaZipFile& outFile, QFile& inFile, QFile& inFileTmp)
{
char c;
foreach(QFileInfo file, files) {
if(file.isDir() && file.fileName() != "." && file.fileName() != "..") {
QFileInfoList infoList = QDir(file.filePath()).entryInfoList();
zipFilelist(infoList, outFile, inFile, inFileTmp);
}
if(file.isFile()) {
inFileTmp.setFileName(file.fileName());
inFile.setFileName(file.filePath());
if(!inFile.open(QIODevice::ReadOnly)) {
qDebug() << "testCreate(): inFile.open(): " << inFile.errorString().toLocal8Bit().constData();
}
QuaZipNewInfo info(inFileTmp.fileName(), inFile.fileName());
if(!outFile.open(QIODevice::WriteOnly, info)) {
qDebug() << "testCreate(): outFile.open(): " << outFile.getZipError();
}
while(inFile.getChar(&c)&&outFile.putChar(c)) ;
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.putChar(): %d"<< outFile.getZipError();
}
outFile.close();
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.close(): %d"<< outFile.getZipError();
}
inFile.close();
}
}
}
And this is how I call the function:
QFileInfoList files = QDir(sourceFolder).entryInfoList();
QFile inFile;
QFile inFileTmp;
QuaZipFile outFile(&zip);
zipFilelist(files, outFile, inFile, inFileTmp);
I don't get any error. When I want to unzip the file it doesn't extract the folders (because I probably don't pack them into the zip!?). So I get all files of all subfolders unziped into one folder.
It seems that in your function you were recursively getting the files in the folders, but not the folders themselves. Try creating a folder to zip the files into when you recurse into looking for the files in the subdirectory.
You may want to look into this answer:
https://stackoverflow.com/a/2598649/1819900
How about the utilities provided by QuaZip?
http://quazip.sourceforge.net/classJlCompress.html
When creating the QuaZipNewInfo object, specify the path and file name to your file as you want to store it in the zip as the first argument, and the path and file name to your file on disk as the second argument. Example:
Adding C:/test/myFile.txt as test/myFile.txt in zip:
QuaZipNewInfo("test/myFile.txt", "C:/test/myFile.txt")
In order to create a folder in your zip file, you need to create an empty file with a name ending with "/". The answer does not include the listing of files/folders but focuses on creating folders in zip file.
QDir sourceRootDir("/path/to/source/folder");
QStringList sourceFilesList; // list of path relative to source root folder
sourceFilesList << "relativePath.txt" << "folder" << "folder/relativePath";
QualZip zip("path/to/zip.zip");
if(!zip.open(QuaZip::mdCreate)){
return false;
}
QuaZipFile outZipFile(&zip);
// Copy file and folder to zip file
foreach (const QString &sourceFilePath, sourceFilesList) {
QFileInfo sourceFI(sourceRootDir.absoluteFilePath(sourceFilePath));
// FOLDER (this is the part that interests you!!!)
if(sourceFI.isFolder()){
QString sourceFolderPath = sourceFilePath;
if(!sourceFolderPath.endsWith("/")){
sourceFolderPath.append("/");
}
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFolderPath, sourceFI.absoluteFilePath()))){
return false;
}
outZipFile.close();
// FILE
} else if(sourceFI.isFile()){
QFile inFile(sourceFI.absoluteFilePath());
if(!inFile.open(QIODevice::ReadOnly)){
zip.close();
return false;
}
// Note: since relative, source=dst
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFilePath, sourceFI.absoluteFilePath()))){
inFile.close();
zip.close();
return false;
}
// Copy
qDebug() << " copy start";
QByteArray buffer;
int chunksize = 256; // Whatever chunk size you like
buffer = inFile.read(chunksize);
while(!buffer.isEmpty()){
qDebug() << " copy " << buffer.count();
outZipFile.write(buffer);
buffer = inFile.read(chunksize);
}
outZipFile.close();
inFile.close();
} else {
// Probably simlink, ignore
}
}
zip.close();
return true;
What is the cleanest way to recursively search for files using C++ and MFC?
EDIT: Do any of these solutions offer the ability to use multiple filters through one pass? I guess with CFileFind I could filter on *.* and then write custom code to further filter into different file types. Does anything offer built-in multiple filters (ie. *.exe,*.dll)?
EDIT2: Just realized an obvious assumption that I was making that makes my previous EDIT invalid. If I am trying to do a recursive search with CFileFind, I have to use *.* as my wildcard because otherwise subdirectories won't be matched and no recursion will take place. So filtering on different file-extentions will have to be handled separately regardless.
Using CFileFind.
Take a look at this example from MSDN:
void Recurse(LPCTSTR pstr)
{
CFileFind finder;
// build a string with wildcards
CString strWildcard(pstr);
strWildcard += _T("\\*.*");
// start working for files
BOOL bWorking = finder.FindFile(strWildcard);
while (bWorking)
{
bWorking = finder.FindNextFile();
// skip . and .. files; otherwise, we'd
// recur infinitely!
if (finder.IsDots())
continue;
// if it's a directory, recursively search it
if (finder.IsDirectory())
{
CString str = finder.GetFilePath();
cout << (LPCTSTR) str << endl;
Recurse(str);
}
}
finder.Close();
}
Use Boost's Filesystem implementation!
The recursive example is even on the filesystem homepage:
bool find_file( const path & dir_path, // in this directory,
const std::string & file_name, // search for this name,
path & path_found ) // placing path here if found
{
if ( !exists( dir_path ) ) return false;
directory_iterator end_itr; // default construction yields past-the-end
for ( directory_iterator itr( dir_path );
itr != end_itr;
++itr )
{
if ( is_directory(itr->status()) )
{
if ( find_file( itr->path(), file_name, path_found ) ) return true;
}
else if ( itr->leaf() == file_name ) // see below
{
path_found = itr->path();
return true;
}
}
return false;
}
I know it is not your question, but it is also easy to to without recursion by using a CStringArray
void FindFiles(CString srcFolder)
{
CStringArray dirs;
dirs.Add(srcFolder + "\\*.*");
while(dirs.GetSize() > 0) {
CString dir = dirs.GetAt(0);
dirs.RemoveAt(0);
CFileFind ff;
BOOL good = ff.FindFile(dir);
while(good) {
good = ff.FindNextFile();
if(!ff.IsDots()) {
if(!ff.IsDirectory()) {
//process file
} else {
//new directory (and not . or ..)
dirs.InsertAt(0,nd + "\\*.*");
}
}
}
ff.Close();
}
}
Check out the recls library - stands for recursive ls - which is a recursive search library that works on UNIX and Windows. It's a C library with adaptations to different language, including C++. From memory, you can use it something like the following:
using recls::search_sequence;
CString dir = "C:\\mydir";
CString patterns = "*.doc;abc*.xls";
CStringArray paths;
search_sequence files(dir, patterns, recls::RECURSIVE);
for(search_sequence::const_iterator b = files.begin(); b != files.end(); b++) {
paths.Add((*b).c_str());
}
It'll find all .doc files, and all .xls files beginning with abc in C:\mydir or any of its subdirectories.
I haven't compiled this, but it should be pretty close to the mark.
CString strNextFileName , strSaveLog= "C:\\mydir";
Find.FindFile(strSaveLog);
BOOL l = Find.FindNextFile();
if(!l)
MessageBox("");
strNextFileName = Find.GetFileName();
Its not working. Find.FindNextFile() returning false even the files are present in the same directory``