diff --git a/tdecore/CMakeLists.txt b/tdecore/CMakeLists.txt index 22c3bbc88..7051d4843 100644 --- a/tdecore/CMakeLists.txt +++ b/tdecore/CMakeLists.txt @@ -72,7 +72,7 @@ install( FILES kcalendarsystem.h kcalendarsystemfactory.h kmacroexpander.h kmanagerselection.h kmountpoint.h kuser.h klockfile.h kidna.h ktempdir.h kshell.h fixx11h.h kxerrorhandler.h - tdelibs_export.h kde_file.h ktimezones.h + tdelibs_export.h kde_file.h ktimezones.h tdestringmatcher.h ${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h DESTINATION ${INCLUDE_INSTALL_DIR} ) @@ -135,6 +135,7 @@ set( ${target}_SRCS kprotocolinfo_tdecore.cpp kprotocolinfofactory.cpp kxerrorhandler.cpp kuser.cpp tdeconfigskeleton.cpp tdeconfigdialogmanager.cpp klockfile.cpp kqiodevicegzip_p.cpp ktimezones.cpp ksimpledirwatch.cpp + tdestringmatcher.cpp ) tde_add_library( ${target} SHARED AUTOMOC diff --git a/tdecore/README.tdestringmatcher b/tdecore/README.tdestringmatcher new file mode 100644 index 000000000..b5312d17d --- /dev/null +++ b/tdecore/README.tdestringmatcher @@ -0,0 +1,63 @@ +The TDEStringMatcher class provides string matching against a list of +one or more TQRegExp objects. The matching functions currently provided +are: + + matchAny(): strings match if they match any TQRegExp object in list. + matchAll(): strings match only if the match all TQRegExp objects in list. + +The list of TQRegExp objects is constructed using a specially formatted string +that is passed by applications via the generatePatternList() function. This +string is referred to as the "patternString" and is formatted as follows: + + * The first character of the patternString defines the character that + is used to split the remainder of the string into match specification + strings. It is recommended (but not required) that this be a character + that does not occur in a match pattern (see below). + + * Each match specification string consists of an initial character that + designates the match specification type followed by the corresponding + match specification itself. + + * Match specification strings starting with 'o' are followed by zero or + or more letters, each of which are interpreted as setting or unsetting + a match option. Match options remain in effect until explicitly overridden + by subsequent match options. The match option letters that are currently + recognized and processed are: + + 'w' - Match patterns are to be interpreted as "wildcards" + 'r' - Match patterns are to be interpreted as "regexes" (TQRegExp default) + 'c' - Matching will be case-sensitive (TQRegExp default) + 'c' - Matching will be case-INsensitive + 'm' - Regex matching will be "minimal" versus "greedy" + 'g' - Regex matching will be "greedy" (TQRegExp default) + + * Match specification strings starting with 'p' are followed by one or + more characters that constitute a match pattern. Each match pattern + together with the match options in effect are used to construct + a single TQRegExp object to be used for string matching. This object + will be validated before acceptance. + + * Match specification strings starting with any other character are ignored. + +Some examples of patternString: + + * |ow|p.*|p*~ + Dotfiles and kwrite backup files will be matched via wildcards + + * /or/p[.][0-9]+$/ow/p.* + Files with a version number suffix will be matched via regex + and dotfiles will be matched via wildcard + +Current and potential use of the TDEStringMatcher class include: + + * Expansion of definition of "hidden" files, addressing issue # 270. + + * Creation of a subclass that provides string parsing functions. + +Miscellaneous implementation notes: + + * It is the responsibility of applications that use this class to provide + patternString editing (perhaps via UI), storage, and retrieval. + + * Regex patterns use TQRegExp::search for matching but wildcard patterns + must use TQRegExp::exactMatch in order for matching to work correctly. diff --git a/tdecore/tdeglobal.cpp b/tdecore/tdeglobal.cpp index 8ef5f59db..6eca24169 100644 --- a/tdecore/tdeglobal.cpp +++ b/tdecore/tdeglobal.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #ifdef __TDE_HAVE_TDEHWLIB #include @@ -131,6 +132,20 @@ KCharsets *TDEGlobal::charsets() return _charsets; } +TDEStringMatcher *TDEGlobal::hiddenFileMatcher() +{ + if( _hiddenFileMatcher == 0 ) { + TSMTRACE << "TDEGlobal::hiddenFileMatcher(): Global HFM initialization STARTED" << endl; + _hiddenFileMatcher = new TDEStringMatcher(); + TDEGlobal::config()->setGroup( "General" ); + TQString settings = TDEGlobal::config()->readEntry( "globalHiddenFileSpec", "/oW/.*" ); + TSMTRACE << "TDEGlobal::hiddenFileMatcher(): using retrieved patternString = '" << settings << "'" << endl; + _hiddenFileMatcher->generatePatternList( settings ); + } + + return _hiddenFileMatcher; +} + void TDEGlobal::setActiveInstance(TDEInstance *i) { _activeInstance = i; @@ -223,6 +238,8 @@ TDEInstance *TDEGlobal::_instance = 0; TDEInstance *TDEGlobal::_activeInstance = 0; TDELocale *TDEGlobal::_locale = 0; KCharsets *TDEGlobal::_charsets = 0; +TDEStringMatcher *TDEGlobal::_hiddenFileMatcher = 0; + KStaticDeleterList *TDEGlobal::_staticDeleters = 0; #ifdef WIN32 diff --git a/tdecore/tdeglobal.h b/tdecore/tdeglobal.h index 1269c36a9..7ec57f027 100644 --- a/tdecore/tdeglobal.h +++ b/tdecore/tdeglobal.h @@ -30,6 +30,7 @@ class TDEHardwareDevices; class TDEGlobalNetworkManager; #endif class TDELocale; +class TDEStringMatcher; class TDEStandardDirs; class KStaticDeleterBase; class KStaticDeleterList; @@ -107,6 +108,12 @@ public: */ static KCharsets *charsets(); + /** + * The global hidden file matcher. + * @return the global hidden file matcher + */ + static TDEStringMatcher *hiddenFileMatcher(); + /** * Creates a static TQString. * @@ -174,6 +181,7 @@ public: static TDEInstance *_instance; static TDELocale *_locale; static KCharsets *_charsets; + static TDEStringMatcher *_hiddenFileMatcher; static KStaticDeleterList *_staticDeleters; /** diff --git a/tdecore/tdestringmatcher.cpp b/tdecore/tdestringmatcher.cpp new file mode 100644 index 000000000..74e0cd7af --- /dev/null +++ b/tdecore/tdestringmatcher.cpp @@ -0,0 +1,169 @@ +#include "tdestringmatcher.h" + +#include +#include + +class TDEStringMatcher::TDEStringMatcherPrivate { +public: + TQString patternString; +}; // FIXME: This may be too small to warrant a private class :\ + +TDEStringMatcher::TDEStringMatcher() +{ + p = new TDEStringMatcherPrivate; + TSMTRACE << "TDEStringMatcher::TDEStringMatcher: New instance created: " << this << endl; +} + +TDEStringMatcher::~TDEStringMatcher() +{ + patternList.setAutoDelete( true ); + patternList.clear(); + delete p; + TSMTRACE << "TDEStringMatcher::TDEStringMatcher: Instance destroyed: " << this << endl; +} + +TQString TDEStringMatcher::getPatternString() +{ + return p->patternString; +} + +bool TDEStringMatcher::generatePatternList( TQString newPatternString ) +{ + if ( newPatternString == p->patternString ) + return true; + TSMTRACE << "TDEStringMatcher::generatePatternList: Proposed pattern string: <" << newPatternString << ">" << endl; + if ( newPatternString.length() < 2 ) { + TSMTRACE << " Input string too short to be interpreted, patterns will be cleared" << endl; + patternList.clear(); + p->patternString = "" ; +#ifdef TSMSIGNALS + emit patternsChanged(); +#endif // TSMSIGNALS + return true; + } + TQChar patternStringDivider = newPatternString[0]; + TSMTRACE << " patternStringDivider = '" << patternStringDivider << "'" << endl; + TQStringList specList = TQStringList::split( patternStringDivider, newPatternString.mid(1), true ); + + TQRegExp rxWork; + TQPtrList rxPatternList; + + for ( const TQString &specification : specList ) { + TSMTRACE << " Processing specification string: '" << specification << "'" << endl; + TQChar specificationType = specification[0].lower(); + switch ( specificationType ) { + case 'o' : { + TQString optionString = specification.mid(1).lower(); + TSMTRACE << " Processing match option string: '" << optionString << "'" << endl; + for ( int i = 0 ; i < optionString.length() ; i++ ) { + TQChar optionChar = optionString[i]; + TSMTRACE << " Option character: '" << optionChar << "'" << endl; + switch ( optionChar ) { + case 'w' : rxWork.setWildcard( true ); break; + case 'r' : rxWork.setWildcard( false ); break; + case 'c' : rxWork.setCaseSensitive( true ); break; + case 'i' : rxWork.setCaseSensitive( false ); break; + case 'm' : rxWork.setMinimal( true ); break; + case 'g' : rxWork.setMinimal( false ); break; + default: break; + } + } + TSMTRACE << " Wildcard/CaseSensitive settings: " << rxWork.wildcard() << "/" << rxWork.caseSensitive() << endl; + + } + break; + + case 'p' : { + TQString pattern = specification.mid(1); + TSMTRACE << " Processing match pattern: '" << pattern << "'" << endl; + if ( pattern.isEmpty() ) { + TSMTRACE << " Empty patterns are not allowed" << endl; + rxPatternList.clear(); + return false; + } + rxWork.setPattern( pattern ); + if (! rxWork.isValid() ) { + TSMTRACE << " Invalid pattern" << endl; + rxPatternList.clear(); + return false; + } + TQRegExp *rxPattern = new TQRegExp( rxWork ); + rxPatternList.append( rxPattern ); + } + break; + + default : + TSMTRACE << " Ignoring unknown specification type '" << specificationType << "'" << endl; + //-Relax, don't overreact: rxPatternList.clear(); + //-Relax, don't overreact: return false; + break; + } + } + + // patternList.clear(); // no need to do this? + patternList.setAutoDelete( true ); + patternList = rxPatternList; + p->patternString = newPatternString; + // rxPatternList.clear(); // no need to do this? + + TSMTRACE << " Final patternString: '" << p->patternString << "'" << endl; + TSMTRACE << " Number of regex match patterns in list: '" << patternList.count() << "'" << endl; + +#ifdef TSMSIGNALS + TSMTRACE << " Notifying slots of pattern change" << endl; + emit patternsChanged(); + TSMTRACE << " All slots have been notified" << endl; +#endif // TSMSIGNALS + TSMTRACE << "TDEStringMatcher::generatePatternList: Patterns were successfully regenerated" << endl << endl; + return true; +} + +bool TDEStringMatcher::matchAny( const TQString& stringToMatch ) +{ + //-Debug: TSMTRACE << "Attempting to match string '" << stringToMatch << "' against stored patterns" << endl; + for ( const TQRegExp *rxPattern : patternList ) { + if ( + ( rxPattern->wildcard() && rxPattern->exactMatch( stringToMatch ) ) || + ( ! rxPattern->wildcard() && rxPattern->search( stringToMatch ) >= 0) + ) + { + //-Debug: TSMTRACE << "String matched pattern: '" << rxPattern->pattern() << "'" << endl; + return true; + } + } + + if ( patternList.isEmpty() ) { + //-Debug: TSMTRACE << "Match failed on empty pattern list!" << endl; + return false; + } + else { + //-Debug: TSMTRACE << "Match failed, no pattern matched!" << endl; + return false; + } +} + +bool TDEStringMatcher::matchAll( const TQString& stringToMatch ) +{ + //-Debug: TSMTRACE << "Attempting to match string '" << stringToMatch << "' against ALL stored patterns" << endl; + for ( const TQRegExp *rxPattern : patternList ) { + if ( ! + ( rxPattern->wildcard() && rxPattern->exactMatch( stringToMatch ) ) || + ( ! rxPattern->wildcard() && rxPattern->search( stringToMatch ) >= 0) + ) + { + //-Debug: TSMTRACE << "String failed to match pattern: '" << rxPattern->pattern() << "'" << endl; + return false; + } + } + + if ( patternList.isEmpty() ) { + //-Debug: TSMTRACE << "Match failed on empty pattern list!" << endl; + return false; + } + else { + //-Debug: TSMTRACE << "Match succeeded, all patterns matched!" << endl; + return true; + } +} + +#include "tdestringmatcher.moc" diff --git a/tdecore/tdestringmatcher.h b/tdecore/tdestringmatcher.h new file mode 100644 index 000000000..f34492e2c --- /dev/null +++ b/tdecore/tdestringmatcher.h @@ -0,0 +1,60 @@ +#ifndef TDESTRINGMATCHER_H +#define TDESTRINGMATCHER_H + +#include "tdelibs_export.h" + +#include +#include +#include + +#define TSMTRACE kdDebug() << " " +#define TSMSIGNALS + +/** + * + * Generic string matcher class. + */ +class TDECORE_EXPORT TDEStringMatcher : public TQObject +{ +Q_OBJECT +public: + + TDEStringMatcher(); + ~TDEStringMatcher(); + + /** + Use @param newPatternString to generate @property patternList. Refer to + file README.tdestringmatcher for more information on how the input + string should be formatted. + */ + bool generatePatternList( TQString newPatternString ); + + /** + Return pattern string from which @property patternList was created. + String is stored in @property TDEStringMatcherPrivate::patternString. + */ + TQString getPatternString(); + + /** + Methods that determine whether or not @param stringToMatch match + any/all of the TQRegExp objects contained in @property patternList. + */ + bool matchAny( const TQString& stringToMatch ); + bool matchAll( const TQString& stringToMatch ); + +signals: + + void patternsChanged(); + +protected: + + TQPtrList patternList; + +private: + + class TDEStringMatcherPrivate; + TDEStringMatcherPrivate *p; + +}; + +#endif diff --git a/tdeio/tdeio/tdefileitem.cpp b/tdeio/tdeio/tdefileitem.cpp index 577a8a0d7..2953eff38 100644 --- a/tdeio/tdeio/tdefileitem.cpp +++ b/tdeio/tdeio/tdefileitem.cpp @@ -204,6 +204,9 @@ void KFileItem::init( bool _determineMimeTypeOnDemand ) // otherwise, determineMimeType will be able to do better. m_bMimeTypeKnown = (!_determineMimeTypeOnDemand) || accurate; } + + // Initialize hidden file matching apparatus + setHiddenFileMatcher( TDEGlobal::hiddenFileMatcher() ); } void KFileItem::readUDSEntry( bool _urlIsDirectory ) @@ -830,15 +833,61 @@ bool KFileItem::isWritable() const return true; } -bool KFileItem::isHidden() const +void KFileItem::resetHiddenFileMatcher() { - if ( m_hidden != Auto ) - return m_hidden == Hidden; + setHiddenFileMatcher( TDEGlobal::hiddenFileMatcher() ); +} + +void KFileItem::setHiddenFileMatcher( TDEStringMatcher *hiddenFileMatcher ) +{ + TSMTRACE << "KFileItem::setHiddenFileMatcher(...) called for " << m_url.fileName() << " [" << hiddenFileMatcher->getPatternString() << "]" <