Cocoa File Dialog: Remove sandbox-ufriendly file system watcher
QFileSystemWatcher causes sandboxing errors since its backend uses POSIX API in a relatively liberal way. Also, Cocoa already acts as a file system watcher, and calls -[QNSOpenSavePanelDelegate panel:shouldShowFilename:] on each file. From a logical point of view, caching the directory content can be replaced by testing the current file against the filter setting. We expect Cocoa to cache results, and by using NSFileManager things should remain relatively fast. Task-number: QTBUG-34107 Change-Id: Ia872b9b1244f7b390d173a498011379b9309b3c6 Reviewed-by: Cyril Oblikov <munknex@gmail.com> Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
This commit is contained in:
parent
7a3c82077f
commit
c934ea341e
@ -61,12 +61,12 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <qabstracteventdispatcher.h>
|
#include <qabstracteventdispatcher.h>
|
||||||
#include "qcocoaautoreleasepool.h"
|
#include "qcocoaautoreleasepool.h"
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
|
||||||
#import <AppKit/NSSavePanel.h>
|
#import <AppKit/NSSavePanel.h>
|
||||||
|
#import <CoreFoundation/CFNumber.h>
|
||||||
|
|
||||||
QT_FORWARD_DECLARE_CLASS(QString)
|
QT_FORWARD_DECLARE_CLASS(QString)
|
||||||
QT_FORWARD_DECLARE_CLASS(QStringList)
|
QT_FORWARD_DECLARE_CLASS(QStringList)
|
||||||
@ -74,30 +74,6 @@ QT_FORWARD_DECLARE_CLASS(QFileInfo)
|
|||||||
QT_FORWARD_DECLARE_CLASS(QWindow)
|
QT_FORWARD_DECLARE_CLASS(QWindow)
|
||||||
QT_USE_NAMESPACE
|
QT_USE_NAMESPACE
|
||||||
|
|
||||||
class CachedEntries: public QObject {
|
|
||||||
public:
|
|
||||||
CachedEntries(QDir::Filters filters) : mFilters(filters) {
|
|
||||||
QObject::connect(&mFSWatcher, &QFileSystemWatcher::directoryChanged, this, &CachedEntries::updateDirCache);
|
|
||||||
}
|
|
||||||
QString directory() const {
|
|
||||||
const QStringList &dirs = mFSWatcher.directories();
|
|
||||||
return (dirs.count() ? dirs[0] : QString());
|
|
||||||
}
|
|
||||||
QStringList entries() const {
|
|
||||||
return mQDirFilterEntryList;
|
|
||||||
}
|
|
||||||
void updateDirCache(const QString &path) {
|
|
||||||
mFSWatcher.removePaths(mFSWatcher.directories());
|
|
||||||
mFSWatcher.addPath(path);
|
|
||||||
mQDirFilterEntryList = QDir(path).entryList(mFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QFileSystemWatcher mFSWatcher;
|
|
||||||
QStringList mQDirFilterEntryList;
|
|
||||||
QDir::Filters mFilters;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
|
typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
|
||||||
|
|
||||||
@class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate);
|
@class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate);
|
||||||
@ -117,7 +93,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
|
|||||||
int mReturnCode;
|
int mReturnCode;
|
||||||
|
|
||||||
SharedPointerFileDialogOptions mOptions;
|
SharedPointerFileDialogOptions mOptions;
|
||||||
CachedEntries *mCachedEntries;
|
|
||||||
QString *mCurrentSelection;
|
QString *mCurrentSelection;
|
||||||
QStringList *mNameFilterDropDownList;
|
QStringList *mNameFilterDropDownList;
|
||||||
QStringList *mSelectedNameFilter;
|
QStringList *mSelectedNameFilter;
|
||||||
@ -164,7 +139,6 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate);
|
|||||||
[mSavePanel setDelegate:self];
|
[mSavePanel setDelegate:self];
|
||||||
mReturnCode = -1;
|
mReturnCode = -1;
|
||||||
mHelper = helper;
|
mHelper = helper;
|
||||||
mCachedEntries = new CachedEntries(mOptions->filter());
|
|
||||||
mNameFilterDropDownList = new QStringList(mOptions->nameFilters());
|
mNameFilterDropDownList = new QStringList(mOptions->nameFilters());
|
||||||
QString selectedVisualNameFilter = mOptions->initiallySelectedNameFilter();
|
QString selectedVisualNameFilter = mOptions->initiallySelectedNameFilter();
|
||||||
mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
|
mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
|
||||||
@ -197,7 +171,6 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate);
|
|||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
delete mCachedEntries;
|
|
||||||
delete mNameFilterDropDownList;
|
delete mNameFilterDropDownList;
|
||||||
delete mSelectedNameFilter;
|
delete mSelectedNameFilter;
|
||||||
delete mCurrentSelection;
|
delete mCurrentSelection;
|
||||||
@ -308,6 +281,22 @@ static QString strippedText(QString s)
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isHiddenFile:(NSString *)filename isDir:(BOOL)isDir
|
||||||
|
{
|
||||||
|
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filename, kCFURLPOSIXPathStyle, isDir);
|
||||||
|
CFBooleanRef isHidden;
|
||||||
|
Boolean errorOrHidden = false;
|
||||||
|
if (!CFURLCopyResourcePropertyForKey(url, kCFURLIsHiddenKey, &isHidden, NULL)) {
|
||||||
|
errorOrHidden = true;
|
||||||
|
} else {
|
||||||
|
if (CFBooleanGetValue(isHidden))
|
||||||
|
errorOrHidden = true;
|
||||||
|
CFRelease(isHidden);
|
||||||
|
}
|
||||||
|
CFRelease(url);
|
||||||
|
return errorOrHidden;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
|
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
|
||||||
{
|
{
|
||||||
Q_UNUSED(sender);
|
Q_UNUSED(sender);
|
||||||
@ -316,8 +305,13 @@ static QString strippedText(QString s)
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
// Always accept directories regardless of their names (unless it is a bundle):
|
// Always accept directories regardless of their names (unless it is a bundle):
|
||||||
BOOL isDir;
|
NSFileManager *fm = [NSFileManager defaultManager];
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDir] && isDir) {
|
NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil];
|
||||||
|
if (!fileAttrs)
|
||||||
|
return NO; // Error accessing the file means 'no'.
|
||||||
|
NSString *fileType = [fileAttrs fileType];
|
||||||
|
bool isDir = [fileType isEqualToString:NSFileTypeDirectory];
|
||||||
|
if (isDir) {
|
||||||
if ([mSavePanel treatsFilePackagesAsDirectories] == NO) {
|
if ([mSavePanel treatsFilePackagesAsDirectories] == NO) {
|
||||||
if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO)
|
if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO)
|
||||||
return YES;
|
return YES;
|
||||||
@ -325,24 +319,35 @@ static QString strippedText(QString s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString qtFileName = QCFString::toQString(filename);
|
QString qtFileName = QCFString::toQString(filename);
|
||||||
QFileInfo info(qtFileName.normalized(QString::NormalizationForm_C));
|
// No filter means accept everything
|
||||||
QString path = info.absolutePath();
|
bool nameMatches = mSelectedNameFilter->isEmpty();
|
||||||
if (mCachedEntries->directory() != path) {
|
// Check if the current file name filter accepts the file:
|
||||||
mCachedEntries->updateDirCache(path);
|
for (int i = 0; !nameMatches && i < mSelectedNameFilter->size(); ++i) {
|
||||||
|
if (QDir::match(mSelectedNameFilter->at(i), qtFileName))
|
||||||
|
nameMatches = true;
|
||||||
}
|
}
|
||||||
// Check if the QDir filter accepts the file:
|
if (!nameMatches)
|
||||||
if (!mCachedEntries->entries().contains(info.fileName()))
|
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
// No filter means accept everything
|
QDir::Filters filter = mOptions->filter();
|
||||||
if (mSelectedNameFilter->isEmpty())
|
if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir)
|
||||||
return YES;
|
|| (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular])
|
||||||
// Check if the current file name filter accepts the file:
|
|| ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink]))
|
||||||
for (int i=0; i<mSelectedNameFilter->size(); ++i) {
|
return NO;
|
||||||
if (QDir::match(mSelectedNameFilter->at(i), qtFileName))
|
|
||||||
return YES;
|
bool filterPermissions = ((filter & QDir::PermissionMask)
|
||||||
|
&& (filter & QDir::PermissionMask) != QDir::PermissionMask);
|
||||||
|
if (filterPermissions) {
|
||||||
|
if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename])
|
||||||
|
|| (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename])
|
||||||
|
|| (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename]))
|
||||||
|
return NO;
|
||||||
}
|
}
|
||||||
return NO;
|
if (!(filter & QDir::Hidden)
|
||||||
|
&& (qtFileName.startsWith(QLatin1Char('.')) || [self isHiddenFile:filename isDir:isDir]))
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
|
- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
|
||||||
|
Loading…
Reference in New Issue
Block a user