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.
Related
I know, I know - that question title is very much all over the place. However, I am not sure what could be an issue here that is causing what I am witnessing.
I have the following method in class Project that is being unit tested:
bool Project::DetermineID(std::string configFile, std::string& ID)
{
std::ifstream config;
config.open(configFile);
if (!config.is_open()) {
WARNING << "Failed to open the configuration file for processing ID at: " << configFile;
return false;
}
std::string line = "";
ID = "";
bool isConfigurationSection = false;
bool isConfiguration = false;
std::string tempID = "";
while (std::getline(config, line))
{
std::transform(line.begin(), line.end(), line.begin(), ::toupper); // transform the line to all capital letters
boost::trim(line);
if ((line.find("IDENTIFICATIONS") != std::string::npos) && (!isConfigurationSection)) {
// remove the "IDENTIFICATIONS" part from the current line we're working with
std::size_t idStartPos = line.find("IDENTIFICATIONS");
line = line.substr(idStartPos + strlen("IDENTIFICATIONS"), line.length() - idStartPos - strlen("IDENTIFICATIONS"));
boost::trim(line);
isConfigurationSection = true;
}
if ((line.find('{') != std::string::npos) && isConfigurationSection) {
std::size_t bracketPos = line.find('{');
// we are working within the ids configuration section
// determine if this is the first character of the line, or if there is an ID that precedes the {
if (bracketPos == 0) {
// is the first char
// remove the bracket and keep processing
line = line.substr(1, line.length() - 1);
boost::trim(line);
}
else {
// the text before { is a temp ID
tempID = line.substr(0, bracketPos - 1);
isConfiguration = true;
line = line.substr(bracketPos, line.length() - bracketPos);
boost::trim(line);
}
}
if ((line.find("PORT") != std::string::npos) && isConfiguration) {
std::size_t indexOfEqualSign = line.find('=');
if (indexOfEqualSign == std::string::npos) {
WARNING << "Unable to determine the port # assigned to " << tempID;
}
else {
std::string portString = "";
portString = line.substr(indexOfEqualSign + 1, line.length() - indexOfEqualSign - 1);
boost::trim(portString);
// confirm that the obtained port string is not an empty value
if (portString.empty()) {
WARNING << "Failed to obtain the \"Port\" value that is set to " << tempID;
}
else {
// attempt to convert the string to int
int workingPortNum = 0;
try {
workingPortNum = std::stoi(portString);
}
catch (...) {
WARNING << "Failed to convert the obtained \"Port\" value that is set to " << tempID;
}
if (workingPortNum != 0) {
// check if this port # is the same port # we are publishing data on
if (workingPortNum == this->port) {
ID = tempID;
break;
}
}
}
}
}
}
config.close();
if (ID.empty())
return false;
else
return true;
}
The goal of this method is to parse any text file for the ID portion, based on matching the port # that the application is publishing data to.
Format of the file is like this:
Idenntifications {
ID {
port = 1001
}
}
In a separate Visual Studio project that unit tests various methods, including this Project::DetermineID method.
#define STRINGIFY(x) #x
#define EXPAND(x) STRINGIFY(x)
TEST_CLASS(ProjectUnitTests) {
Project* parser;
std::string projectDirectory;
TEST_METHOD_INITIALIZE(ProjectUnitTestInitialization) {
projectDirectory = EXPAND(UNITTESTPRJ);
projectDirectory.erase(0, 1);
projectDirectory.erase(projectDirectory.size() - 2);
parser = Project::getClass(); // singleton method getter/initializer
}
// Other test methods are present and pass/fail accordingly
TEST_METHOD(DetermineID) {
std::string ID = "";
bool x = parser ->DetermineAdapterID(projectDirectory + "normal.cfg", ID);
Assert::IsTrue(x);
}
};
Now, when I run the tests, DetermineID fails and the stack trace states:
DetermineID
Source: Project Tests.cpp line 86
Duration: 2 sec
Message:
Assert failed
Stack Trace:
ProjectUnitTests::DetermineID() line 91
Now, in my test .cpp file, TEST_METHOD(DetermineID) { is present on line 86. But that method's } is located on line 91, as the stack trace indicates.
And, when debugging, the unit test passes, because the return of x in the TEST_METHOD is true.
Only when running the test individually or running all tests does that test method fail.
Some notes that may be relevant:
This is a single-threaded application with no tasks scheduled (no race condition to worry about supposedly)
There is another method in the Project class that also processes a file with an std::ifstream same as this method does
That method has its own test method that has been written and passes without any problems
The test method also access the "normal.cfg" file
Yes, this->port has an assigned value
Thus, my questions are:
Why does the stack trace reference the closing bracket for the test method instead of the single Assert within the method that is supposedly failing?
How to get the unit test to pass when it is ran? (Since it currently only plasses during debugging where I can confirm that x is true).
If the issue is a race condition where perhaps the other test method is accessing the "normal.cfg" file, why does the test method fail even when the method is individually ran?
Any support/assistance here is very much appreciated. Thank you!
I'm building a Compress/Decompress functions and currently, I only able to compress 1 single file into a zip file. Each time I add a new Directory or add Recursively a Directory it always give me Exception.
Here is my ZipFile function:
Poco::DateTime(Timestamp);
set <string> extensionsSet;
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress c(fos, true);
for (int i = 0; i < extensions.Size(); i++) {
string ext = std::dynamic_pointer_cast<String>(extensions.operator[](i))->GetPrimitive();
extensionsSet.insert(ext);
}
c.setStoreExtensions(extensionsSet);//set extensions List
Poco::File aFile(source);
if (aFile.exists())
{
Poco::Path p(aFile.path());
if (aFile.isDirectory())
{
Poco::Path sourceDir(source);
Poco::Path targetDir(target);
c.addDirectory(targetDir, Poco::DateTime(Timestamp));// give exception
targetDir.makeDirectory();
c.addRecursive(sourceDir);
}
else if (aFile.isFile())
{
c.addFile(p, p.getFileName());
}
}
else {
_log.EndMethod();
throw new FileNotFoundException("File Not Found");
}
c.close(); // MUST be done to finalize the Zip file
fos.close();
Here is where my exception, the underneath block of code is within addDirectory methods of Poco:
if (!ZipCommon::isValidPath(fileStr))
throw ZipException("Illegal entry name " + fileStr + " containing parent directory reference");
There is a member in "Poco::Path" called "absolute".
You can set absolute to false by constructor
otherwise the ZipException is thrown because this function returns "false":
bool ZipCommon::isValidPath(const std::string& path)
{
try
{
if (Path(path, Path::PATH_UNIX).isAbsolute() || Path(path, Path::PATH_WINDOWS).isAbsolute())
return false;
}
...
}
Here is a example:
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress compress(fos, true);
std::vector<std::string> dirs; // parent dirs in zip file
Poco::Path myPath(false); // set path to non-absolute
// add parent directories
for (auto& dir : dirs)
{
myPath.pushDirectory(dir);
}
//add file
myPath.setFileName("FileName.txt");
//add file to archive
compress.addFile(theFile, relativePath);
//close archive and get zipArchiveObject
ZipArchive zipArchive(compress.close());
I want to know how to figure out file path is from internal or external storage.
I want to delete a file. Before deleting it i want to check whether it is from internal memory or external.
if file is from internal storage then i can simply delete it like this
file.delete();
But if file is from external storage (sdcard). Then i would first check permission then delete it through storage access framework.
I'm currently doing like this.
File selectedFile = Constant.allMemoryVideoList.get(fPosition).getFile().getAbsoluteFile();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (???????????? check if file is not from internal storage ???????????) {
List<UriPermission> permissions = getContentResolver().getPersistedUriPermissions();
if (permissions != null && permissions.size() > 0) {
sdCardUri = permissions.get(0).getUri();
deleteFileWithSAF();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Please select external storage directory (e.g SDCard)")
.setMessage("Due to change in android security policy it is not possible to delete or rename file in external storage without granting permission")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// call document tree dialog
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);
}
})
.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
}
} else {
deleteFile();
}
} else {
deleteFile();
}
deleteFileWithSAF()
private void deleteFileWithSAF() {
//First we get `DocumentFile` from the `TreeUri` which in our case is `sdCardUri`.
DocumentFile documentFile = DocumentFile.fromTreeUri(this, sdCardUri);
//Then we split file path into array of strings.
//ex: parts:{"", "storage", "extSdCard", "MyFolder", "MyFolder", "myImage.jpg"}
// There is a reason for having two similar names "MyFolder" in
//my exmple file path to show you similarity in names in a path will not
//distract our hiarchy search that is provided below.
String[] parts = (selectedFile.getPath()).split("\\/");
// findFile method will search documentFile for the first file
// with the expected `DisplayName`
// We skip first three items because we are already on it.(sdCardUri = /storage/extSdCard)
for (int i = 3; i < parts.length; i++) {
if (documentFile != null) {
documentFile = documentFile.findFile(parts[i]);
}
}
if (documentFile == null) {
// File not found on tree search
// User selected a wrong directory as the sd-card
// Here must inform user about how to get the correct sd-card
// and invoke file chooser dialog again.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Please select root of external storage directory (click SELECT button at bottom)")
.setMessage("Due to change in android security policy it is not possible to delete or rename file in external storage without granting permission")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// call for document tree dialog
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);
}
})
.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
} else {
// File found on sd-card and it is a correct sd-card directory
// save this path as a root for sd-card on your database(SQLite, XML, txt,...)
// Now do whatever you like to do with documentFile.
// Here I do deletion to provide an example.
if (documentFile.delete()) {// if delete file succeed
// Remove information related to your media from ContentResolver,
// which documentFile.delete() didn't do the trick for me.
// Must do it otherwise you will end up with showing an empty
// ImageView if you are getting your URLs from MediaStore.
getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(selectedFile)));
// Methods.removeMedia(this,selectedFile.getPath());
if (deleteSingleFileCall){
Constant.allMemoryVideoList.remove(videoPosition);
adapter.notifyItemRemoved(videoPosition);
deleteSingleFileCall = false;
}
/*update the playback record to
* getFileName() contain file.getName()*/
for (int i = 0; i < Constant.filesPlaybackHistory.size(); i++) {
if ((selectedFile.getName()).equals(Constant.filesPlaybackHistory.get(i).getFileName())) {
Constant.filesPlaybackHistory.remove(i);
break;
}
}
//save the playback history
Paper.book().write("playbackHistory", Constant.filesPlaybackHistory);
}
}
}
This is how i load files of both internal and external storage.
StorageUtil is library https://github.com/hendrawd/StorageUtil
String[] allPath = StorageUtil.getStorageDirectories(this);
private File directory;
for (String path: allPath){
directory = new File(path);
Methods.load_Directory_Files(directory);
}
All Loaded files in following arraylist.
//all the directory that contains files
public static ArrayList<File> directoryList = null;
//list of all files (internal and external)
public static ArrayList<FilesInfo> allMemoryVideoList = new ArrayList<>();
FilesInfo: Contain all info about file like thumbnail, duration, directory, new or played before, if played then last playback position etc
LoadDirectoryFiles()
public static void load_Directory_Files(File directory) {
//Get all file in storage
File[] fileList = directory.listFiles();
//check storage is empty or not
if(fileList != null && fileList.length > 0)
{
for (int i=0; i<fileList.length; i++)
{
boolean restricted_directory = false;
//check file is directory or other file
if(fileList[i].isDirectory())
{
for (String path : Constant.removePath){
if (path.equals(fileList[i].getPath())) {
restricted_directory = true;
break;
}
}
if (!restricted_directory)
load_Directory_Files(fileList[i]);
}
else
{
String name = fileList[i].getName().toLowerCase();
for (String ext : Constant.videoExtensions){
//Check the type of file
if(name.endsWith(ext))
{
//first getVideoDuration
String videoDuration = Methods.getVideoDuration(fileList[i]);
long playbackPosition;
long percentage = C.TIME_UNSET;
FilesInfo.fileState state;
/*First check video already played or not. If not then state is NEW
* else load playback position and calculate percentage of it and assign it*/
//check it if already exist or not if yes then start from there else start from start position
int existIndex = -1;
for (int j = 0; j < Constant.filesPlaybackHistory.size(); j++) {
String fListName = fileList[i].getName();
String fPlaybackHisName = Constant.filesPlaybackHistory.get(j).getFileName();
if (fListName.equals(fPlaybackHisName)) {
existIndex = j;
break;
}
}
try {
if (existIndex != -1) {
//if true that means file is not new
state = FilesInfo.fileState.NOT_NEW;
//set playbackPercentage not playbackPosition
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(fileList[i].getPath());
String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
retriever.release();
int duration = Integer.parseInt(time);
playbackPosition = Constant.filesPlaybackHistory.get(existIndex).getPlaybackPosition();
if (duration > 0)
percentage = 1000L * playbackPosition / duration;
else
percentage = C.TIME_UNSET;
}
else
state = FilesInfo.fileState.NEW;
//playbackPosition have value in percentage
Constant.allMemoryVideoList.add(new FilesInfo(fileList[i],
directory,videoDuration, state, percentage, storageType));
//directory portion
currentDirectory = directory.getPath();
unique_directory = true;
for(int j=0; j<directoryList.size(); j++)
{
if((directoryList.get(j).toString()).equals(currentDirectory)){
unique_directory = false;
}
}
if(unique_directory){
directoryList.add(directory);
}
//When we found extension from videoExtension array we will break it.
break;
}catch (Exception e){
e.printStackTrace();
Constant.allMemoryVideoList.add(new FilesInfo(fileList[i],
directory,videoDuration, FilesInfo.fileState.NOT_NEW, C.TIME_UNSET, storageType));
}
}
}
}
}
}
Constant.directoryList = directoryList;
}
Image So reader could easily understand what is going on.
I have a .txt file in QT project, where I store settings parameters for my app. If I read it that way
void MainWindow::ReadApplicationSettings()
{
QFile cfgFile("config.txt");
if(!cfgFile.exists())
{
qDebug() << "Cannot find config file.";
}
else
{
ParseConfigFile(cfgFile);
SetCfg();
}
cfgFile.close();
}
And Parse it:
void MainWindow::ParseConfigFile(QFile &cfgFile)
{
QString line;
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&cfgFile);
while (!stream.atEnd())
{
line = stream.readLine();
std::istringstream iss(line.toStdString());
std::string id, eq, val;
bool error = false;
if (!(iss >> id))
{
error = true;
}
else if (id[0] == '#')
{
continue;
}
else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
{
error = true;
}
if (error) { throw std::runtime_error("Parse error"); }
cfgMap[id] = std::stoi(val);
}
}
}
The file exists and when the parsing begin the content of the file is empty
The result of: line = stream.readLine(); is "".
If I add the file like a resource file and opened this way:
QFile cfgFile(":config.txt");
It's working correct, but the problem is that the config file is compiled and when you have to change some value, the project must be rebuild to take effect
I tried to compose the path like that QDir::currentPath + "/config.txt", but not working as well.
Another option is the undocumented feature "file_copies" in Qt 5.6 that you can use like this:
CONFIG += file_copies
configfiles.path = $$OUT_PWD
configfiles.files = $$PWD/config.txt
COPIES += configfiles
Found here: https://stackoverflow.com/a/54162789/6842395
If you don't like this method, that post has some more options to choose.
By the way, loooking at your ParseConfigFile() method seems that your config.txt is a collection of lines with the format: key = value, very similar to a classic INI file. Maybe you could use QSettings third constructor like this:
QSettings settings("config.txt", QSettings::IniFormat);
You can use QMAKE_EXTRA_TARGET in your profile like:
copyfile.commands += $${QMAKE_COPY} $$system_path($$PWD/config.txt) $$system_path($$DESTDIR/)
first.depends = $(first) copyfile
QMAKE_EXTRA_TARGETS += first copyfile
But ensure your $$DESTDIR is correct.
In a program that I am writing, there is a function that takes all of the filenames from a specific directory and puts them into a vector so they can be used later in the program.
However, this also grabs any hidden files that happen to be in the folder. I've tried to just have the program delete any files starting with a '.', but that has not worked on the folder that I was testing (I still get ".." listed as a file).
Here is the section of the code:
while (handle != INVALID_HANDLE_VALUE)
{
filenameList.push_back(search_data.cFileName);
if (FindNextFile(handle, &search_data) == FALSE)
break;
}
//removes a lot of hidden files from the file list
for (int i = 0; i < filenameList.size(); i++)
{
string hiddenCheck = filenameList[i];
if (hiddenCheck[0] == '.')
{
filenameList.erase(filenameList.begin() + i);
i = 0;
}
You need to look at the file attributes while you are enumerating the files:
HANDLE handle = FindFirstFile(..., &search_data);
if (handle != NULL)
{
do
{
// if not a directory, and not hidden...
if ((search_data.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN)) == 0)
{
// add it to the list...
filenameList.push_back(search_data.cFileName);
}
}
while (FindNextFile(handle, &search_data));
FindClose(handle);
}