├── doc ├── CMakeLists.txt ├── screenshot.png └── en │ ├── feedback-mail.png │ ├── kdirstat-main.png │ ├── kdirstat-config-cleanups.png │ ├── kdirstat-config-tree-colors.png │ └── CMakeLists.txt ├── .gitignore ├── AUTHORS ├── icons ├── 16-apps-k4dirstat.png ├── 32-apps-k4dirstat.png ├── 48-apps-k4dirstat.png ├── sc-apps-k4dirstat.svgz └── CMakeLists.txt ├── src ├── Messages.sh ├── k4dirstatui.qrc ├── kcleanup_dummy.cpp ├── settings.kcfgc ├── k4dirstat.desktop ├── k4dirstat.kcfg ├── CMakeLists.txt ├── kcleanupcollection_p.h ├── kstdcleanup.h ├── kexcluderules.cpp ├── kactivitytracker.h ├── kcleanupcollection.h ├── kexcluderules.h ├── main.cpp ├── kcleanupcollection.cpp ├── kstdcleanup.cpp ├── kdirtreecache.h ├── k4dirstatui.rc ├── kfileinfo.cpp ├── k4dirstat.h ├── ktreemaptile.h ├── kdirtree.cpp ├── kdirinfo.h ├── kcleanup.h ├── kcleanup.cpp ├── kdirinfo.cpp ├── ktreemapview.h ├── kdirtree.h ├── kdirreadjob.h ├── kdirtreecache.cpp └── kdirreadjob.cpp ├── .tx └── config ├── README.md ├── CREDITS ├── po ├── create-pot └── CMakeLists.txt ├── k4dirstat.1 ├── CMakeLists.txt └── COPYING /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(en) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gmo 2 | Makefile 3 | CMakeCache.txt 4 | moc_*.cpp 5 | CMakeFiles 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Stefan Hundhammer 2 | Joshua Hodosh 3 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/doc/screenshot.png -------------------------------------------------------------------------------- /doc/en/feedback-mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/doc/en/feedback-mail.png -------------------------------------------------------------------------------- /doc/en/kdirstat-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/doc/en/kdirstat-main.png -------------------------------------------------------------------------------- /icons/16-apps-k4dirstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/icons/16-apps-k4dirstat.png -------------------------------------------------------------------------------- /icons/32-apps-k4dirstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/icons/32-apps-k4dirstat.png -------------------------------------------------------------------------------- /icons/48-apps-k4dirstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/icons/48-apps-k4dirstat.png -------------------------------------------------------------------------------- /icons/sc-apps-k4dirstat.svgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/icons/sc-apps-k4dirstat.svgz -------------------------------------------------------------------------------- /doc/en/kdirstat-config-cleanups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/doc/en/kdirstat-config-cleanups.png -------------------------------------------------------------------------------- /doc/en/kdirstat-config-tree-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromerobert/k4dirstat/HEAD/doc/en/kdirstat-config-tree-colors.png -------------------------------------------------------------------------------- /src/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | $EXTRACTRC `find . -name \*.rc` >> rc.cpp 3 | $XGETTEXT *.cpp -o $podir/k4dirstat.pot 4 | rm -f *.cpp 5 | -------------------------------------------------------------------------------- /src/k4dirstatui.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | k4dirstatui.rc 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/en/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########### install files ############### 2 | # 3 | 4 | kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR k4dirstat) 5 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [k4dirstat.k4dirstat] 5 | file_filter = po/.po 6 | source_file = po/k4dirstat.pot 7 | source_lang = en 8 | type = PO 9 | -------------------------------------------------------------------------------- /src/kcleanup_dummy.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "kcleanup.h" 3 | 4 | using namespace KDirStat; 5 | 6 | KCleanup : KCleanup(QString id, QString command, QString title, 7 | KActionCollection *parent) 8 | -------------------------------------------------------------------------------- /src/settings.kcfgc: -------------------------------------------------------------------------------- 1 | # Code generation options for kconfig_compiler 2 | File=k4dirstat.kcfg 3 | ClassName=Settings 4 | Singleton=true 5 | Mutators=col_background,col_foreground 6 | # will create the necessary code for setting those variables 7 | -------------------------------------------------------------------------------- /icons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(ECMInstallIcons) 2 | ecm_install_icons(ICONS 3 | 16-apps-k4dirstat.png 4 | 32-apps-k4dirstat.png 5 | 48-apps-k4dirstat.png 6 | sc-apps-k4dirstat.svgz 7 | DESTINATION share/icons) 8 | -------------------------------------------------------------------------------- /src/k4dirstat.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=K4DirStat 3 | Exec=k4dirstat %i -qwindowtitle %c %u 4 | Icon=k4dirstat 5 | Type=Application 6 | X-DocPath=k4dirstat/index.html 7 | GenericName=Directory Statistics 8 | GenericName[de]=Verzeichnisstatistik 9 | GenericName[hu]=könyvtárstatisztika 10 | Comment=Directory statistics and disk usage 11 | Comment[de]=Verzeichnisstatistik und Platzverbrauch 12 | Comment[hu]=Könyvtárstatisztikák és szabad hely 13 | Terminal=false 14 | Categories=Utility; 15 | Keywords=disk usage, file system, cleanup; 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ![Screenshot of the k4dirstat UI, with the 'tree view' at the top and the 'treemap view' at the bottom](doc/screenshot.png) 4 | 5 | See the [wiki](https://github.com/jeromerobert/k4dirstat/wiki/Overview). 6 | 7 | # Building 8 | 9 | First install [Qt 5](http://www.qt.io) and [KDE Framework 5](https://www.kde.org) headers. On Debian and its derivatives it can be done with: 10 | 11 | ``` 12 | apt-get install extra-cmake-modules qtbase5-dev libkf5coreaddons-dev \ 13 | libkf5i18n-dev libkf5xmlgui-dev libkf5doctools-dev libkf5kio-dev 14 | ``` 15 | 16 | Then run [cmake](http://www.cmake.org): 17 | 18 | cmake -DCMAKE_INSTALL_PREFIX=/path/where/to/install 19 | make install 20 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | We would like to thank the following people: 2 | 3 | - Ben Shneiderman for his ingenious idea of treemaps as an alternative and 4 | truly intuitive way of visualizing trees. 5 | 6 | - All the people at the TU Eindhoven who worked on SequoiaView that gave us the 7 | inspiration for including treemaps in KDirStat and numerous papers describing 8 | the algorithms behind it. 9 | 10 | - Toyohiro Asukai for Asian support patches. 11 | 12 | - Alexander Rawass for implementing the 13 | first version of treemaps for KDirStat (this version has been completely 14 | replaced beginning May 2002). 15 | 16 | - Christoph Thielecke for a nice patch that 17 | provided GUI support for exclude rules (Nov 2008). 18 | -------------------------------------------------------------------------------- /src/k4dirstat.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | black 11 | 12 | 13 | 14 | yellow 15 | 16 | 17 | 18 | 2 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt5_add_resources(UI_RCC k4dirstatui.qrc) 2 | set(k4dirstat_SRCS 3 | k4dirstat.cpp 4 | ${UI_RCC} 5 | main.cpp 6 | ktreemapview.cpp 7 | kcleanupcollection.cpp 8 | kfileinfo.cpp 9 | kdirtreeview.cpp 10 | ktreemaptile.cpp 11 | kstdcleanup.cpp 12 | kcleanup.cpp 13 | kdirtree.cpp 14 | kexcluderules.cpp 15 | kdirreadjob.cpp 16 | kdirinfo.cpp 17 | kdirtreecache.cpp 18 | kdirstatsettings.cpp 19 | ) 20 | 21 | kconfig_add_kcfg_files(k4dirstat_SRCS settings.kcfgc ) 22 | 23 | add_executable(k4dirstat ${k4dirstat_SRCS}) 24 | 25 | target_link_libraries(k4dirstat KF5::XmlGui KF5::KIOCore 26 | KF5::KIOWidgets KF5::I18n KF5::IconThemes 27 | ${QT_QTGUI_LIBS} 28 | ${ZLIB_LIBRARIES}) 29 | 30 | install(TARGETS k4dirstat ${INSTALL_TARGETS_DEFAULT_ARGS} ) 31 | 32 | 33 | ########### install files ############### 34 | 35 | install( FILES k4dirstat.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) 36 | install( FILES k4dirstat.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) 37 | -------------------------------------------------------------------------------- /po/create-pot: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | # 3 | # Script to generate .pot file from C++ sources and XML GUI description file 4 | 5 | PROGNAME=k4dirstat 6 | SRCDIR=../src 7 | 8 | extractrc --tag=cstring $SRCDIR/*ui.rc >ui_rc.cpp 9 | 10 | 11 | # search for sourcecode-files 12 | SRCFILES=$(find $SRCDIR . -type f -name "*.h" \ 13 | -o -name "*.c" \ 14 | -o -name "*.cc" \ 15 | -o -name "*.cpp") 16 | 17 | echo "$SRCFILES" 18 | 19 | xgettext \ 20 | --add-comments \ 21 | --add-location \ 22 | --keyword=i18n \ 23 | --keyword=i18n:1,2 \ 24 | --keyword=I18N_NOOP \ 25 | --keyword=_ \ 26 | --keyword=_:1,2 \ 27 | --keyword=__ \ 28 | --keyword=N_ \ 29 | --foreign-user \ 30 | --default-domain=$PROGNAME \ 31 | --output=$PROGNAME.pot \ 32 | $SRCFILES ui_rc.cpp 33 | 34 | catalogs=`find . -name '*.po'` 35 | for ct in $catalogs; do 36 | echo $ct 37 | msgmerge -o $ct.net $ct $PROGNAME.pot 38 | mv $ct.net $ct 39 | done 40 | 41 | 42 | rm ui_rc.cpp 43 | -------------------------------------------------------------------------------- /src/kcleanupcollection_p.h: -------------------------------------------------------------------------------- 1 | #include "kcleanup.h" 2 | #include "kfileinfo.h" 3 | #include 4 | 5 | namespace KDirStat { 6 | class CleanupAction : public QAction { 7 | Q_OBJECT 8 | 9 | public: 10 | CleanupAction(KCleanup cleanup, QObject * parent) : QAction(parent), cleanup_(cleanup) { 11 | connect(this, SIGNAL(triggered()), this, SLOT(slotTriggered())); 12 | setEnabled(false); 13 | refresh(); 14 | } 15 | KCleanup &cleanup() { return cleanup_; } 16 | /** Refresh the action after it's cleanup changed */ 17 | void refresh() { setText(cleanup_.title()); } 18 | 19 | signals: 20 | /** 21 | * Emitted after the action is executed. 22 | * 23 | * Please note that there intentionally is no reference as to which 24 | * object the action was executed upon since this object very likely 25 | * doesn't exist any more. 26 | **/ 27 | void executed(); 28 | public slots: 29 | void selectionChanged(KDirTree *); 30 | private slots: 31 | void slotTriggered() { 32 | cleanup_.execute(tree_); 33 | emit executed(); 34 | } 35 | 36 | private: 37 | KCleanup cleanup_; 38 | KDirTree * tree_; 39 | }; 40 | } // namespace KDirStat 41 | -------------------------------------------------------------------------------- /po/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FIND_PROGRAM(GETTEXT_MSGFMT_EXECUTABLE msgfmt) 2 | 3 | IF(NOT GETTEXT_MSGFMT_EXECUTABLE) 4 | MESSAGE( 5 | "------ 6 | NOTE: msgfmt not found. Translations will *not* be installed 7 | ------") 8 | ELSE(NOT GETTEXT_MSGFMT_EXECUTABLE) 9 | 10 | SET(catalogname k4dirstat) 11 | ADD_CUSTOM_TARGET(translations ALL) 12 | 13 | FILE(GLOB PO_FILES *.po) 14 | 15 | FOREACH(_poFile ${PO_FILES}) 16 | GET_FILENAME_COMPONENT(_poFileName ${_poFile} NAME) 17 | STRING(REGEX REPLACE "^${catalogname}_?" "" _langCode ${_poFileName} ) 18 | STRING(REGEX REPLACE "\\.po$" "" _langCode ${_langCode} ) 19 | 20 | IF( _langCode ) 21 | GET_FILENAME_COMPONENT(_lang ${_poFile} NAME_WE) 22 | SET(_gmoFile ${CMAKE_CURRENT_BINARY_DIR}/${_lang}.gmo) 23 | 24 | ADD_CUSTOM_COMMAND(TARGET translations 25 | COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} --check -o ${_gmoFile} ${_poFile} 26 | DEPENDS ${_poFile}) 27 | INSTALL(FILES ${_gmoFile} DESTINATION ${LOCALE_INSTALL_DIR}/${_langCode}/LC_MESSAGES/ RENAME ${catalogname}.mo) 28 | ENDIF( _langCode ) 29 | ENDFOREACH(_poFile ${PO_FILES}) 30 | 31 | ENDIF(NOT GETTEXT_MSGFMT_EXECUTABLE) 32 | -------------------------------------------------------------------------------- /src/kstdcleanup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * License: LGPL - See file COPYING.LIB for details. 4 | * Author: Stefan Hundhammer 5 | * Joshua Hodosh 6 | */ 7 | 8 | #include "kcleanup.h" 9 | 10 | // Forward declarations 11 | class KActionCollection; 12 | 13 | namespace KDirStat { 14 | /** 15 | * Predefined standard @ref KCleanup actions to be performed on 16 | * @ref KDirTree items. 17 | * 18 | * This class is not meant to be ever instantiated - use the static methods 19 | * only. 20 | * 21 | * For details about what each individual method does, refer to the help 22 | * file. Use the old (KDirStat 0.86) help file in case the current help 23 | * file isn't available yet. 24 | * 25 | * @short KDirStat standard cleanup actions 26 | **/ 27 | 28 | class KStdCleanup { 29 | public: 30 | static KCleanup *openInKonqueror(QString &icon, QKeySequence &shortcut); 31 | static KCleanup *openInTerminal(QString &icon, QKeySequence &shortcut); 32 | static KCleanup *compressSubtree(QString &icon, QKeySequence &shortcut); 33 | static KCleanup *makeClean(QString &icon, QKeySequence &shortcut); 34 | static KCleanup *deleteTrash(QString &icon, QKeySequence &shortcut); 35 | static KCleanup *moveToTrashBin(QString &icon, QKeySequence &shortcut); 36 | static KCleanup *hardDelete(QString &icon, QKeySequence &shortcut); 37 | 38 | private: 39 | /** 40 | * Prevent instances of this class - private constructor / destructor. 41 | **/ 42 | KStdCleanup() {} 43 | ~KStdCleanup() {} 44 | }; 45 | 46 | class TrashBinCleanup : public KCleanup { 47 | public: 48 | TrashBinCleanup(); 49 | public slots: 50 | void execute(KDirTree * tree) override; 51 | }; 52 | 53 | } // namespace KDirStat 54 | 55 | -------------------------------------------------------------------------------- /src/kexcluderules.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "kexcluderules.h" 8 | #include 9 | 10 | #define VERBOSE_EXCLUDE_MATCHES 1 11 | 12 | using namespace KDirStat; 13 | 14 | KExcludeRule::KExcludeRule(const QRegExp ®exp) 15 | : _regexp(regexp), _enabled(true) { 16 | // NOP 17 | } 18 | 19 | KExcludeRule::~KExcludeRule() { 20 | // NOP 21 | } 22 | 23 | bool KExcludeRule::match(const QString &text) { 24 | if (text.isEmpty() || !_enabled) 25 | return false; 26 | 27 | return _regexp.exactMatch(text); 28 | } 29 | 30 | KExcludeRules::~KExcludeRules() { 31 | foreach (KExcludeRule *rule, _rules) 32 | delete rule; 33 | } 34 | 35 | KExcludeRules *KExcludeRules::excludeRules() { 36 | static KExcludeRules *singleton = 0; 37 | 38 | if (!singleton) { 39 | singleton = new KExcludeRules(); 40 | } 41 | 42 | return singleton; 43 | } 44 | 45 | void KExcludeRules::add(KExcludeRule *rule) { 46 | if (rule) 47 | _rules.append(rule); 48 | } 49 | 50 | bool KExcludeRules::match(const QString &text) { 51 | if (text.isEmpty()) 52 | return false; 53 | 54 | foreach (KExcludeRule *rule, _rules) { 55 | if (rule->match(text)) { 56 | #if VERBOSE_EXCLUDE_MATCHES 57 | 58 | qDebug() << text << " matches exclude rule " << rule->regexp().pattern() 59 | << Qt::endl; 60 | 61 | #endif 62 | return true; 63 | } 64 | } 65 | 66 | return false; 67 | } 68 | 69 | const KExcludeRule *KExcludeRules::matchingRule(const QString &text) { 70 | if (text.isEmpty()) 71 | return NULL; 72 | 73 | foreach (KExcludeRule *rule, _rules) { 74 | if (rule->match(text)) 75 | return rule; 76 | } 77 | 78 | return 0; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /k4dirstat.1: -------------------------------------------------------------------------------- 1 | .TH "K4DIRSTAT" "1" "May 2015" "" "" 2 | . 3 | .SH "NAME" 4 | \fBk4dirstat\fR \- Graphical directory statistics 5 | . 6 | .SH "SYNOPSIS" 7 | \fBk4dirstat\fR [\fIOPTIONS\fR\.\.\.] [\fIpath or url\fR] 8 | . 9 | .SH "DESCRIPTION" 10 | Shows where all your disk space has gone and helps you clean it up\. The size of files and directory is shown using treemapping\. 11 | . 12 | .TP 13 | \fIpath or url\fR 14 | Directory or URL to open 15 | . 16 | .SH "OPTIONS" 17 | . 18 | .TP 19 | \fB\-v\fR, \fB\-version\fR 20 | Output version information and exit\. 21 | . 22 | .SS "Qt options" 23 | . 24 | .TP 25 | \fB\-platform\fR \fIplatformName[:options]\fR 26 | specifies the Qt Platform Abstraction (QPA) plugin\. Overridden by the \fBQT_QPA_PLATFORM\fR environment variable\. 27 | . 28 | .TP 29 | \fB\-platformpluginpath\fR \fIpath\fR 30 | specifies the path to platform plugins\. Overridden by the \fBQT_QPA_PLATFORM_PLUGIN_PATH\fR environment variable\. 31 | . 32 | .TP 33 | \fB\-platformtheme\fR \fIplatformTheme\fR 34 | specifies the platform theme\. Overridden by the \fBQT_QPA_PLATFORMTHEME\fR environment variable\. 35 | . 36 | .TP 37 | \fB\-plugin\fR \fIplugin\fR 38 | specifies additional plugins to load\. The argument may appear multiple times\. Overridden by the \fBQT_QPA_GENERIC_PLUGINS\fR environment variable\. 39 | . 40 | .TP 41 | \fB\-qwindowgeometry\fR \fIgeometry\fR 42 | specifies window geometry for the main window using the X11\-syntax\. For example: \fB\-qwindowgeometry 100x100+50+50\fR\. 43 | . 44 | .TP 45 | \fB\-qwindowicon\fR 46 | sets the default window icon\. 47 | . 48 | .TP 49 | \fB\-qwindowtitle\fR 50 | sets the title of the first window\. 51 | . 52 | .TP 53 | \fB\-display\fR \fIhostname\fR 54 | screen_number, switches displays on X11\. Overrides the \fBDISPLAY\fR environment variable\. 55 | . 56 | .TP 57 | \fB\-geometry\fR 58 | geometry, same as \fB\-qwindowgeometry\fR\. 59 | . 60 | .SH "AUTHOR" 61 | This man page was written by Jerome Robert. 62 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | project(k4dirstat) 3 | 4 | function(git_version output_variable default_version strip_string) 5 | option(K4DIRSTAT_GIT_VERSION "Get the version string from git describe" ON) 6 | if(K4DIRSTAT_GIT_VERSION) 7 | find_package(Git) 8 | if(GIT_FOUND) 9 | execute_process(COMMAND "${GIT_EXECUTABLE}" 10 | describe --dirty=-dirty --always --tags 11 | OUTPUT_VARIABLE _GIT_DESCRIBE ERROR_QUIET) 12 | if(_GIT_DESCRIBE) 13 | string(REPLACE ${strip_string} "" ${output_variable} ${_GIT_DESCRIBE}) 14 | string(STRIP ${${output_variable}} ${output_variable}) 15 | endif() 16 | endif() 17 | endif() 18 | if(NOT ${output_variable}) 19 | # It would be better use only git and add a make dist target for Linux distro 20 | # but this would require to upload dist tarball on Bitbucket and I prefere using 21 | # the Bitbucket download tag feature. 22 | set(${output_variable} ${default_version}) 23 | endif() 24 | message(STATUS "Version string is ${${output_variable}}") 25 | add_definitions(-D${output_variable}=${${output_variable}}) 26 | endfunction() 27 | 28 | include(CheckCCompilerFlag) 29 | check_c_compiler_flag(-Wl,--as-needed has_as_needed) 30 | if (has_as_needed) 31 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") 32 | endif() 33 | 34 | find_package(ECM REQUIRED NO_MODULE) 35 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 36 | 37 | include(KDEInstallDirs) 38 | include(KDECMakeSettings) 39 | include(KDECompilerSettings) 40 | include(FeatureSummary) 41 | 42 | find_package(Qt5 REQUIRED COMPONENTS Widgets) 43 | find_package(KF5 REQUIRED COMPONENTS CoreAddons I18n DocTools XmlGui KIO JobWidgets IconThemes) 44 | find_package(ZLIB) 45 | 46 | ADD_DEFINITIONS(-D_LARGE_FILES -D_FILE_OFFSET_BITS=64) 47 | 48 | add_definitions(-DQT_NO_URL_CAST_FROM_STRING) 49 | add_definitions(-DQT_USE_QSTRINGBUILDER) 50 | 51 | git_version(K4DIRSTAT_VERSION 3.4.3 k4dirstat-) 52 | add_subdirectory( doc ) 53 | add_subdirectory( src ) 54 | add_subdirectory( icons ) 55 | add_subdirectory( po ) 56 | 57 | install(FILES k4dirstat.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") 58 | -------------------------------------------------------------------------------- /src/kactivitytracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Summary: Utility object to track user activity 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | */ 7 | 8 | #include 9 | 10 | /** 11 | * Helper class to track user activity of any kind: When the user uses an 12 | * application's actions (menu items etc.), those actions notify this object of 13 | * that fact. Each action has an amount of "activity points" assigned 14 | * (i.e. what this action is "worth"). Those points are summed up here, and 15 | * when a certain number of points is reached, a signal is triggered. This 16 | * signal can be used for example to ask the user if he wouldn't like to rate 17 | * this program - or register it if this is a shareware program. 18 | * 19 | * @short User activity tracker 20 | **/ 21 | class KActivityTracker : public QObject { 22 | Q_OBJECT 23 | public: 24 | /** 25 | * Constructor. The ID is a name for the KConfig object to look in for 26 | * accumulated activity points so far. 'initialThreshold' is only used if 27 | * the application's @ref KConfig object doesn't contain a corresponding 28 | * entry yet. 29 | **/ 30 | KActivityTracker(QObject *parent, const QString &id, long initialThreshold); 31 | 32 | /** 33 | * Destructor. 34 | **/ 35 | virtual ~KActivityTracker(); 36 | 37 | /** 38 | * Returns the number of activity points accumulated so far. 39 | **/ 40 | long sum() const { return _sum; } 41 | 42 | /** 43 | * Sets the activity threshold, i.e. when a signal will be sent. 44 | **/ 45 | void setThreshold(long threshold); 46 | 47 | /** 48 | * Returns the current threshold. 49 | **/ 50 | long threshold() const { return _threshold; } 51 | 52 | /** 53 | * Check the sum of activity points accumulated so far against the current 54 | * threshold and emit a signal if appropriate. 55 | **/ 56 | void checkThreshold(); 57 | 58 | public slots: 59 | 60 | /** 61 | * Track an activity, i.e. add the specified amount of activity points to 62 | * the accumulated sum. 63 | **/ 64 | void trackActivity(int points); 65 | 66 | /** 67 | * Set the threshold to its double value. 68 | **/ 69 | void doubleThreshold() { setThreshold(2 * threshold()); } 70 | 71 | signals: 72 | 73 | /** 74 | * Emitted when the activity threshold is reached. 75 | * 76 | * You might want to set the threshold to a new value when this signal is 77 | * emitted. You can simply connect it to @ref doubleThreshold(). 78 | **/ 79 | void thresholdReached(void); 80 | 81 | protected: 82 | long _sum; 83 | long _threshold; 84 | long _lastSignal; 85 | QString _id; 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /src/kcleanupcollection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File name: kcleanupcollection.h 3 | * Summary: Support classes for KDirStat 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | * 8 | * Updated: 2010-02-01 9 | */ 10 | 11 | #ifndef KCleanupCollection_h 12 | #define KCleanupCollection_h 13 | 14 | #include "kcleanup.h" 15 | 16 | class KActionCollection; 17 | 18 | namespace KDirStat { 19 | class CleanupAction; 20 | /** 21 | * Set of @ref KCleanup actions to be performed for @ref KDirTree items, 22 | * consisting of a number of predefined and a number of user-defined 23 | * cleanups. The prime purpose of this is to make save/restore operations 24 | * with a number of cleanups easier. Thus, it provides a copy constructor, 25 | * an assignment operator and various methods to directly access individual 26 | * cleanups. 27 | * 28 | * @short KDirStat cleanup action collection 29 | **/ 30 | 31 | class KCleanupCollection : public QObject { 32 | Q_OBJECT 33 | 34 | public: 35 | /** 36 | * Constructor. 37 | * 38 | * Most applications will want to pass KMainWindow::actionCollection() 39 | * for 'actionCollection' so the menus and toolbars can be created 40 | * using the XML UI description ('kdirstatui.rc' for KDirStat). 41 | * 42 | * All @ref KCleanup actions ever added to this collection will get 43 | * this as their parent. 44 | **/ 45 | KCleanupCollection(KActionCollection &actionCollection); 46 | 47 | /** 48 | * Destructor 49 | **/ 50 | virtual ~KCleanupCollection(); 51 | 52 | /** 53 | * Add the standard cleanups to this collection. 54 | **/ 55 | void addStdCleanups(); 56 | 57 | /** 58 | * Add 'number' user-defined cleanups to this collection. 59 | **/ 60 | void addUserCleanups(int number); 61 | 62 | QList cleanupsCopy(); 63 | void setCleanups(QList &cleanups); 64 | void revertToDefault(int nbUserCleanups); 65 | 66 | private: 67 | /** 68 | * Add one single cleanup to this collection. The collection assumes 69 | * ownerwhip of this cleanup - don't delete it! 70 | **/ 71 | CleanupAction *add(KCleanup *cleanup); 72 | void add(KCleanup *(*factory)(QString &icon, QKeySequence &shortcut)); 73 | 74 | public slots: 75 | 76 | /** 77 | * Read collection for all cleanups. 78 | **/ 79 | void readConfig(); 80 | 81 | /** 82 | * Save configuration for all cleanups. 83 | **/ 84 | void saveConfig(); 85 | 86 | signals: 87 | /** 88 | * Emitted at user activity, i.e. when the user executes a cleanup. 89 | * This is intended for use together with a @ref KActivityTracker. 90 | **/ 91 | void userActivity(int points); 92 | void selectionChanged(KDirTree *); 93 | 94 | protected slots: 95 | 96 | /** 97 | * Connected to each cleanup's @ref executed() signal to track user 98 | * activity. 99 | **/ 100 | void cleanupExecuted(); 101 | 102 | private: 103 | // Data members 104 | 105 | KActionCollection &_actionCollection; 106 | QList cleanupActions; 107 | int _nextUserCleanupNo; 108 | }; 109 | } // namespace KDirStat 110 | 111 | #endif // ifndef KCleanupCollection_h 112 | -------------------------------------------------------------------------------- /src/kexcluderules.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace KDirStat { 14 | /** 15 | * One single exclude rule to check text (file names) against. 16 | * It can be enabled or disabled. Only enabled rules can ever match; a 17 | * disabled exclude rule will never exclude anything. 18 | **/ 19 | class KExcludeRule { 20 | public: 21 | /** 22 | * Constructor. 23 | **/ 24 | KExcludeRule(const QRegExp ®exp); 25 | 26 | /** 27 | * Destructor. 28 | **/ 29 | virtual ~KExcludeRule(); 30 | 31 | /** 32 | * Check a string (usually a file name) against this exclude rule. 33 | * Returns 'true' if the string matches, i.e. if the object this string 34 | * belongs to should be excluded. 35 | * 36 | * Only enabled exclude rules will ever match; as long as it is 37 | * disabled, this will always return 'false'. 38 | **/ 39 | bool match(const QString &text); 40 | 41 | /** 42 | * Returns this rule's regular expression. 43 | **/ 44 | QRegExp regexp() const { return _regexp; } 45 | 46 | /** 47 | * Change this rule's regular expression. 48 | **/ 49 | void setRegexp(const QRegExp ®exp) { _regexp = regexp; } 50 | 51 | /** 52 | * Check if this rule is enabled. 53 | **/ 54 | bool isEnabled() const { return _enabled; } 55 | 56 | /** 57 | * Enable or disable this rule. 58 | * New rules are always enabled by default. 59 | **/ 60 | void enable(bool enable = true) { _enabled = enable; } 61 | 62 | private: 63 | QRegExp _regexp; 64 | bool _enabled; 65 | }; 66 | 67 | /** 68 | * Container for multiple exclude rules. 69 | * 70 | * Normal usage: 71 | * 72 | * KExcludeRules::excludeRules()->add( new KExcludeRule( ... ) ); 73 | * ... 74 | * if ( KExcludeRules::excludeRules()->match( filename ) ) 75 | * { 76 | * // exclude this file 77 | * } 78 | **/ 79 | class KExcludeRules { 80 | public: 81 | /** 82 | * Constructor. 83 | * 84 | * Most applications will want to use excludeRules() instead to create 85 | * and use a singleton object of this class. 86 | **/ 87 | KExcludeRules() {} 88 | 89 | /** 90 | * Destructor. 91 | **/ 92 | ~KExcludeRules(); 93 | 94 | /** 95 | * Return the singleton object of this class. 96 | * This will create one if there is none yet. 97 | **/ 98 | static KExcludeRules *excludeRules(); 99 | 100 | /** 101 | * Add an exclude rule to this rule set. 102 | * This transfers ownership of that rule to this rule set; 103 | * it will be destroyed with 'delete' after use. 104 | **/ 105 | void add(KExcludeRule *rule); 106 | 107 | /** 108 | * Check a string against the exclude rules. 109 | * This will return 'true' if the text matches any (enabled) rule. 110 | * 111 | * Note that this operation will move current(). 112 | **/ 113 | bool match(const QString &text); 114 | 115 | /** 116 | * Find the exclude rule that matches 'text'. 117 | * Return 0 if there is no match. 118 | * 119 | * This is intended to explain to the user which rule matched. 120 | **/ 121 | const KExcludeRule *matchingRule(const QString &text); 122 | 123 | /** 124 | * Clear (delete) all exclude rules. 125 | **/ 126 | void clear() { _rules.clear(); } 127 | 128 | const QList &rules() const { return _rules; } 129 | 130 | private: 131 | QList _rules; 132 | }; 133 | 134 | } // namespace KDirStat 135 | 136 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File name: main.cpp 3 | * Summary: Main program for KDirStat 4 | * License: GPL - See file COPYING for details. 5 | * 6 | * Author: Stefan Hundhammer 7 | * Joshua Hodosh 8 | * Parts auto-generated by KDevelop 9 | * 10 | * Updated: 2010-03-03 11 | */ 12 | 13 | #include "k4dirstat.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | static const char description[] = 22 | I18N_NOOP("k4dirstat - Directory statistics.\n" 23 | "\n" 24 | "Shows where all your disk space has gone\n" 25 | "and helps you clean it up."); 26 | 27 | #define STRINGIFY(x) #x 28 | #define EXPAND(x) STRINGIFY(x) 29 | static const char version[] = EXPAND(K4DIRSTAT_VERSION); 30 | 31 | int main(int argc, char **argv) { 32 | QApplication app(argc, argv); 33 | KLocalizedString::setApplicationDomain("k4dirstat"); 34 | KAboutData about("k4dirstat", i18n("k4dirstat"), version, i18n(description), 35 | KAboutLicense::GPL, 36 | "\u00A9 2015-2019 J\u00E9r\u00F4me Robert, \u00A9 2010 Joshua " 37 | "Hodosh, \u00A9 1999-2008 Stefan Hundhammer", 38 | "", "https://github.com/jeromerobert/k4dirstat", 39 | "https://github.com/jeromerobert/k4dirstat/issues"); 40 | 41 | about.addAuthor("J\u00E9r\u00F4me Robert", 42 | i18n("KF5 Port, current maintainer."), "", 43 | "https://github.com/jeromerobert/k4dirstat"); 44 | about.addAuthor("Stefan Hundhammer", i18n("Original kdirstat author."), 45 | "kdirstat@gmx.de", "http://kdirstat.sourceforge.net/"); 46 | about.addAuthor("Joshua Hodosh", i18n("Port to KDE4"), 47 | "kdirstat@grumpypenguin.org"); 48 | 49 | about.addCredit(i18n("SequoiaView Team"), 50 | i18n("for showing just how useful treemaps really can be."), 51 | 0, // e-mail 52 | "http://www.win.tue.nl/sequoiaview"); 53 | 54 | about.addCredit(i18n("Jarke J. van Wijk, Huub van de Wetering, Mark Bruls"), 55 | i18n("for their papers about treemaps."), 56 | "vanwijk@win.tue.nl", "http://www.win.tue.nl/~vanwijk/"); 57 | 58 | about.addCredit(i18n("Ben Shneiderman"), 59 | i18n("for his ingenious idea of treemaps -\n" 60 | "a truly intuitive way of visualizing tree contents."), 61 | "", // E-Mail 62 | "http://www.cs.umd.edu/hcil/treemaps/"); 63 | KAboutData::setApplicationData(about); 64 | app.setApplicationName("k4dirstat"); 65 | app.setApplicationVersion(version); 66 | app.setApplicationDisplayName(i18n("k4dirstat")); 67 | app.setWindowIcon(QIcon::fromTheme("k4dirstat")); 68 | QCommandLineParser parser; 69 | parser.addHelpOption(); 70 | parser.addVersionOption(); 71 | parser.addPositionalArgument("+[Dir/URL]", "Directory or URL to open"); 72 | parser.process(app); 73 | k4dirstat *kdirstat = new k4dirstat; 74 | 75 | // see if we are starting with session management 76 | if (app.isSessionRestored()) { 77 | kRestoreMainWindows(); 78 | } else { 79 | kdirstat->show(); 80 | // no session.. just start up normally 81 | QStringList args = parser.positionalArguments(); 82 | if (args.isEmpty()) { 83 | kdirstat->fileAskOpenDir(); 84 | } else { 85 | // Process command line arguments as URLs or paths to scan 86 | QUrl u = QUrl::fromUserInput(args[0], QDir::currentPath(), 87 | QUrl::AssumeLocalFile); 88 | kdirstat->openURL( 89 | u.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments)); 90 | } 91 | } 92 | 93 | return app.exec(); 94 | } 95 | -------------------------------------------------------------------------------- /src/kcleanupcollection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "kcleanupcollection.h" 8 | #include "kcleanupcollection_p.h" 9 | #include "kstdcleanup.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace KDirStat; 17 | 18 | void CleanupAction::selectionChanged(KDirTree * tree) { 19 | tree_ = tree; 20 | setEnabled(cleanup_.isEnabledFromSelection(tree)); 21 | } 22 | 23 | KCleanupCollection::KCleanupCollection(KActionCollection &actionCollection) 24 | : _actionCollection(actionCollection) { 25 | /** 26 | * All cleanups beloningt to this collection are stored in two separate Qt 27 | * collections, a QList and a QDict. Make _one_ of them manage the cleanup 28 | * objects, i.e. have them clear the KCleanup objects upon deleting. The 29 | * QList is the master collection, the QDict the slave. 30 | **/ 31 | 32 | _nextUserCleanupNo = 0; 33 | } 34 | 35 | KCleanupCollection::~KCleanupCollection() { 36 | // No need to delete the cleanups: _cleanupList takes care of that 37 | // (autoDelete!). 38 | } 39 | 40 | CleanupAction *KCleanupCollection::add(KCleanup *newCleanup) { 41 | CleanupAction *action = new CleanupAction(*newCleanup, this); 42 | _actionCollection.addAction(newCleanup->id(), action); 43 | delete newCleanup; 44 | cleanupActions.append(action); 45 | 46 | connect(this, SIGNAL(selectionChanged(KDirTree*)), action, 47 | SLOT(selectionChanged(KDirTree*))); 48 | 49 | connect(action, SIGNAL(executed()), this, SLOT(cleanupExecuted())); 50 | return action; 51 | } 52 | 53 | void KCleanupCollection::readConfig() { 54 | for (int i = 0; i < cleanupActions.count(); i++) { 55 | cleanupActions[i]->cleanup().readConfig(); 56 | cleanupActions[i]->refresh(); 57 | } 58 | } 59 | 60 | void KCleanupCollection::saveConfig() { 61 | for (int i = 0; i < cleanupActions.count(); i++) { 62 | cleanupActions[i]->cleanup().saveConfig(); 63 | } 64 | } 65 | 66 | void KCleanupCollection::add(KCleanup *(*factory)(QString &iconName, 67 | QKeySequence &shortcut)) { 68 | QString iconName; 69 | QKeySequence shortcut; 70 | QAction *action; 71 | action = add(factory(iconName, shortcut)); 72 | if (!shortcut.isEmpty()) 73 | _actionCollection.setDefaultShortcut(action, shortcut); 74 | if (!iconName.isEmpty()) 75 | action->setIcon(QIcon(new KIconEngine(iconName, KIconLoader::global()))); 76 | } 77 | 78 | void KCleanupCollection::addStdCleanups() { 79 | add(KStdCleanup::openInKonqueror); 80 | add(KStdCleanup::openInTerminal); 81 | add(KStdCleanup::compressSubtree); 82 | add(KStdCleanup::makeClean); 83 | add(KStdCleanup::deleteTrash); 84 | add(KStdCleanup::moveToTrashBin); 85 | add(KStdCleanup::hardDelete); 86 | } 87 | 88 | void KCleanupCollection::addUserCleanups(int number) { 89 | for (int i = 0; i < number; i++) { 90 | QString id = QString("cleanup_user_defined_%1").arg(_nextUserCleanupNo); 91 | QString title; 92 | 93 | if (_nextUserCleanupNo <= 9) 94 | // Provide a keyboard shortcut for cleanup #0..#9 95 | title = i18n("User Defined Cleanup #&%1", _nextUserCleanupNo); 96 | else 97 | // No keyboard shortcuts for cleanups #10.. - they would be duplicates 98 | title = i18n("User Defined Cleanup #%1", _nextUserCleanupNo); 99 | 100 | _nextUserCleanupNo++; 101 | 102 | KCleanup *cleanup = new KCleanup(id, "", title); 103 | Q_CHECK_PTR(cleanup); 104 | cleanup->setEnabled(false); 105 | 106 | CleanupAction *action = add(cleanup); 107 | if (i <= 9) { 108 | // Provide an application-wide keyboard accelerator for cleanup #0..#9 109 | _actionCollection.setDefaultShortcut(action, Qt::CTRL + Qt::Key_0 + i); 110 | } 111 | } 112 | } 113 | 114 | void KCleanupCollection::cleanupExecuted() { emit userActivity(10); } 115 | 116 | void KCleanupCollection::revertToDefault(int nbUserCleanups) { 117 | _nextUserCleanupNo = 0; 118 | for (int i = 0; i < cleanupActions.size(); i++) 119 | _actionCollection.removeAction(cleanupActions[i]); 120 | cleanupActions.clear(); 121 | addStdCleanups(); 122 | addUserCleanups(nbUserCleanups); 123 | } 124 | 125 | QList KCleanupCollection::cleanupsCopy() { 126 | QList toReturn; 127 | toReturn.reserve(cleanupActions.size()); 128 | for (int i = 0; i < cleanupActions.size(); i++) 129 | toReturn.append(cleanupActions[i]->cleanup()); 130 | return toReturn; 131 | } 132 | 133 | void KCleanupCollection::setCleanups(QList &cleanups) { 134 | Q_ASSERT(cleanups.size() == cleanupActions.size()); 135 | for (int i = 0; i < cleanups.size(); i++) { 136 | cleanupActions[i]->cleanup() = cleanups[i]; 137 | cleanupActions[i]->refresh(); 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /src/kstdcleanup.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "k4dirstat.h" 8 | #include "kcleanup.h" 9 | #include "kstdcleanup.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #if KIO_VERSION >= QT_VERSION_CHECK(5, 100, 0) 17 | #include 18 | #endif 19 | 20 | using namespace KDirStat; 21 | 22 | KCleanup *KStdCleanup::openInKonqueror(QString &icon, QKeySequence &shortcut) { 23 | KCleanup *cleanup = new KCleanup("cleanup_open_in_konqueror", "xdg-open %p", 24 | i18n("Open a file &browser")); 25 | Q_CHECK_PTR(cleanup); 26 | cleanup->setWorksForDir(true); 27 | cleanup->setWorksForFile(true); 28 | cleanup->setWorksForDotEntry(true); 29 | cleanup->setWorksLocalOnly(false); 30 | cleanup->setRefreshPolicy(KCleanup::noRefresh); 31 | icon = "system-file-manager"; 32 | shortcut = Qt::CTRL + Qt::Key_K; 33 | return cleanup; 34 | } 35 | 36 | KCleanup *KStdCleanup::openInTerminal(QString &icon, QKeySequence &shortcut) { 37 | KCleanup *cleanup = new KCleanup("cleanup_open_in_terminal", "konsole --workdir %p", 38 | i18n("Open in &Terminal")); 39 | Q_CHECK_PTR(cleanup); 40 | cleanup->setWorksForDir(true); 41 | cleanup->setWorksForFile(true); 42 | cleanup->setWorksForDotEntry(true); 43 | cleanup->setRefreshPolicy(KCleanup::noRefresh); 44 | icon = "utilities-terminal"; 45 | shortcut = Qt::CTRL + Qt::Key_T; 46 | 47 | return cleanup; 48 | } 49 | 50 | KCleanup *KStdCleanup::compressSubtree(QString &iconFile, QKeySequence &) { 51 | KCleanup *cleanup = new KCleanup("cleanup_compress_subtree", 52 | "cd ..; tar cjvf %n.tar.bz2 %n && rm -rf %n", 53 | i18n("&Compress")); 54 | Q_CHECK_PTR(cleanup); 55 | cleanup->setWorksForDir(true); 56 | cleanup->setWorksForFile(false); 57 | cleanup->setWorksForDotEntry(false); 58 | cleanup->setRefreshPolicy(KCleanup::refreshParent); 59 | iconFile = "utilities-file-archiver"; 60 | 61 | return cleanup; 62 | } 63 | 64 | KCleanup *KStdCleanup::makeClean(QString &, QKeySequence &) { 65 | KCleanup *cleanup = 66 | new KCleanup("cleanup_make_clean", "make clean", i18n("&make clean")); 67 | Q_CHECK_PTR(cleanup); 68 | cleanup->setWorksForDir(true); 69 | cleanup->setWorksForFile(false); 70 | cleanup->setWorksForDotEntry(true); 71 | cleanup->setRefreshPolicy(KCleanup::refreshThis); 72 | 73 | return cleanup; 74 | } 75 | 76 | KCleanup *KStdCleanup::deleteTrash(QString &, QKeySequence &) { 77 | KCleanup *cleanup = 78 | new KCleanup("cleanup_delete_trash", "rm -f *.o *~ *.bak *.auto core", 79 | i18n("Delete T&rash Files")); 80 | Q_CHECK_PTR(cleanup); 81 | cleanup->setWorksForDir(true); 82 | cleanup->setWorksForFile(false); 83 | cleanup->setWorksForDotEntry(true); 84 | cleanup->setRefreshPolicy(KCleanup::refreshThis); 85 | cleanup->setRecurse(true); 86 | 87 | return cleanup; 88 | } 89 | 90 | KCleanup *KStdCleanup::moveToTrashBin(QString &icon, QKeySequence &shortcut) { 91 | KCleanup *cleanup = new TrashBinCleanup(); 92 | Q_CHECK_PTR(cleanup); 93 | cleanup->setWorksForDir(true); 94 | cleanup->setWorksForFile(true); 95 | cleanup->setWorksForDotEntry(false); 96 | cleanup->setRefreshPolicy(KCleanup::assumeDeleted); 97 | /* The icon standard says the action should be "edit-trash" 98 | However, Oxygen doesn't have that icon, so I'm setting 99 | "user-trash" which will probably be the same in most 100 | icon sets. */ 101 | // cleanup->setIcon(KIcon( "edit-trash" )); 102 | icon = "user-trash"; 103 | shortcut = Qt::CTRL + Qt::Key_X; 104 | 105 | return cleanup; 106 | } 107 | 108 | KCleanup *KStdCleanup::hardDelete(QString &icon, QKeySequence &shortcut) { 109 | KCleanup *cleanup = new KCleanup("cleanup_hard_delete", "rm -rf %p", 110 | i18n("&Delete (no way to undelete!)")); 111 | Q_CHECK_PTR(cleanup); 112 | cleanup->setWorksForDir(true); 113 | cleanup->setWorksForFile(true); 114 | cleanup->setWorksForDotEntry(false); 115 | cleanup->setAskForConfirmation(true); 116 | cleanup->setRefreshPolicy(KCleanup::assumeDeleted); 117 | icon = "edit-delete"; 118 | shortcut = Qt::CTRL + Qt::Key_Delete; 119 | 120 | return cleanup; 121 | } 122 | 123 | TrashBinCleanup::TrashBinCleanup() 124 | : KCleanup("cleanup_move_to_trash_bin", "", 125 | i18n("Delete (to Trash &Bin)")) {} 126 | 127 | static void konqOperationsDel(QWidget *m_mainWindow, const QList &urls) { 128 | #if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0) 129 | KIO::JobUiDelegate uiDelegate; 130 | uiDelegate.setWindow(m_mainWindow); 131 | if (uiDelegate.askDeleteConfirmation( 132 | urls, KIO::JobUiDelegate::Trash, 133 | KIO::JobUiDelegate::DefaultConfirmation)) { 134 | KIO::Job *job = KIO::trash(urls); 135 | KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, 136 | QUrl("trash:/"), job); 137 | KJobWidgets::setWindow(job, m_mainWindow); 138 | job->uiDelegate()->setAutoErrorHandlingEnabled( 139 | true); // or connect to the result signal 140 | } 141 | #else 142 | using Iface = KIO::AskUserActionInterface; 143 | auto * job = new KIO::DeleteOrTrashJob(urls, Iface::Trash, 144 | Iface::DefaultConfirmation, m_mainWindow); 145 | job->start(); 146 | #endif 147 | } 148 | 149 | void TrashBinCleanup::execute(KDirTree * tree) { 150 | for(auto it = tree->selection().begin(); it != tree->selection().end(); ++it) { 151 | if (worksFor(*it, tree)) { 152 | QUrl url; 153 | url.setPath((*it)->url()); 154 | QList urls; 155 | urls.append(url); 156 | konqOperationsDel(k4dirstat::instance(), urls); 157 | tree->deleteSubtree(*it); 158 | } 159 | } 160 | } 161 | 162 | -------------------------------------------------------------------------------- /src/kdirtreecache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Summary: KDirStat cache reader / writer 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include "kdirtree.h" 10 | #include 11 | #include 12 | 13 | #ifndef NOT_USED 14 | #define NOT_USED(PARAM) ((void)(PARAM)) 15 | #endif 16 | 17 | #define DEFAULT_CACHE_NAME ".kdirstat.cache.gz" 18 | #define MAX_CACHE_LINE_LEN 1024 19 | #define MAX_FIELDS_PER_LINE 32 20 | 21 | namespace KDirStat { 22 | class KCacheWriter { 23 | public: 24 | /** 25 | * Write 'tree' to file 'fileName' in gzip format (using zlib). 26 | * 27 | * Check CacheWriter::ok() to see if writing the cache file went OK. 28 | **/ 29 | KCacheWriter(const QString &fileName, KDirTree *tree); 30 | 31 | /** 32 | * Destructor 33 | **/ 34 | virtual ~KCacheWriter(); 35 | 36 | /** 37 | * Returns true if writing the cache file went OK. 38 | **/ 39 | bool ok() const { return _ok; } 40 | 41 | /** 42 | * Format a file size as string - with trailing "G", "M", "K" for 43 | * "Gigabytes", "Megabytes, "Kilobytes", respectively (provided there 44 | * is no fractional part - 27M is OK, 27.2M is not). 45 | **/ 46 | QString formatSize(KFileSize size); 47 | 48 | protected: 49 | /** 50 | * Write cache file in gzip format. 51 | * Returns 'true' if OK, 'false' upon error. 52 | **/ 53 | bool writeCache(const QString &fileName, KDirTree *tree); 54 | 55 | /** 56 | * Write 'item' recursively to cache file 'cache'. 57 | * Uses zlib to write gzip-compressed files. 58 | **/ 59 | void writeTree(gzFile cache, KFileInfo *item); 60 | 61 | /** 62 | * Write 'item' to cache file 'cache' without recursion. 63 | * Uses zlib to write gzip-compressed files. 64 | **/ 65 | void writeItem(gzFile cache, KFileInfo *item); 66 | 67 | // 68 | // Data members 69 | // 70 | 71 | bool _ok; 72 | }; 73 | 74 | class KCacheReader : public QObject { 75 | Q_OBJECT 76 | 77 | public: 78 | /** 79 | * Begin reading cache file 'fileName'. The cache file remains open 80 | * until this object is destroyed. 81 | **/ 82 | KCacheReader(const QString &fileName, KDirTree *tree, KDirInfo *parent = 0); 83 | 84 | /** 85 | * Destructor 86 | **/ 87 | virtual ~KCacheReader(); 88 | 89 | /** 90 | * Read at most maxLines from the cache file (check with eof() if the 91 | * end of file is reached yet) or the entire file (if maxLines is 0). 92 | * 93 | * Returns true if OK and there is more to read, false otherwise. 94 | **/ 95 | bool read(int maxLines = 0); 96 | 97 | /** 98 | * Returns true if the end of the cache file is reached (or if there 99 | * was an error). 100 | **/ 101 | bool eof(); 102 | 103 | /** 104 | * Returns true if writing the cache file went OK. 105 | **/ 106 | bool ok() const { return _ok; } 107 | 108 | /** 109 | * Resets the reader so all data lines of the cache can be read with 110 | * subsequent read() calls. 111 | **/ 112 | void rewind(); 113 | 114 | /** 115 | * Returns the absolute path of the first directory in this cache file 116 | * or an empty string if there is none. 117 | * 118 | * This method expects the cache file to be just opened without any 119 | * previous read() operations on the file. If this is not the case, 120 | * call rewind() immediately before firstDir(). 121 | * 122 | * After firstDir(), some records of the cache file will be read. 123 | * Make sure to call rewind() if you intend to read from this cache 124 | * file afterwards. 125 | **/ 126 | QString firstDir(); 127 | 128 | /** 129 | * Returns the tree associated with this reader. 130 | **/ 131 | KDirTree *tree() const { return _tree; } 132 | 133 | /** 134 | * Skip leading whitespace from a string. 135 | * Returns a pointer to the first character that is non-whitespace. 136 | **/ 137 | static char *skipWhiteSpace(char *cptr); 138 | 139 | /** 140 | * Find the next whitespace in a string. 141 | * 142 | * Returns a pointer to the next whitespace character 143 | * or a null pointer if there is no more whitespace in the string. 144 | **/ 145 | static char *findNextWhiteSpace(char *cptr); 146 | 147 | /** 148 | * Remove all trailing whitespace from a string - overwrite it with 0 149 | * bytes. 150 | * 151 | * Returns the new string length. 152 | **/ 153 | static void killTrailingWhiteSpace(char *cptr); 154 | 155 | signals: 156 | 157 | /** 158 | * Emitted when a child has been added. 159 | **/ 160 | void childAdded(KFileInfo *newChild); 161 | 162 | /** 163 | * Emitted when reading this cache is finished. 164 | **/ 165 | void finished(); 166 | 167 | /** 168 | * Emitted if there is a read error. 169 | **/ 170 | void error(); 171 | 172 | protected: 173 | /** 174 | * Check this cache's header (see if it is a KDirStat cache at all) 175 | **/ 176 | bool checkHeader(); 177 | 178 | /** 179 | * Use _fields to add one item to _tree. 180 | **/ 181 | void addItem(); 182 | 183 | /** 184 | * Read the next line that is not empty or a comment and store it in _line. 185 | * Returns true if OK, false if error. 186 | **/ 187 | bool readLine(); 188 | 189 | /** 190 | * split the current input line into fields separated by whitespace. 191 | **/ 192 | void splitLine(); 193 | 194 | /** 195 | * Returns the start of field no. 'no' in the current input line 196 | * after splitLine(). 197 | **/ 198 | char *field(int no); 199 | 200 | /** 201 | * Returns the number of fields in the current input line after splitLine(). 202 | **/ 203 | int fieldsCount() const { return _fieldsCount; } 204 | 205 | // 206 | // Data members 207 | // 208 | 209 | KDirTree *_tree; 210 | gzFile _cache; 211 | char _buffer[MAX_CACHE_LINE_LEN]; 212 | char *_line; 213 | int _lineNo; 214 | QString _fileName; 215 | char *_fields[MAX_FIELDS_PER_LINE]; 216 | int _fieldsCount; 217 | bool _ok; 218 | KDirInfo *_toplevel; 219 | KDirInfo *_lastDir; 220 | KDirInfo *_lastExcludedDir; 221 | QString _lastExcludedDirUrl; 222 | }; 223 | 224 | } // namespace KDirStat 225 | 226 | -------------------------------------------------------------------------------- /src/k4dirstatui.rc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | &File 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | &Actions 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | &Treemap 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | &Settings 72 | 73 | 74 | 75 | 76 | 77 | 78 | &Report 79 | 80 | 81 | 82 | 83 | &Help 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Main Toolbar 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /src/kfileinfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "kdirinfo.h" 8 | #include "kfileinfo.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | static const size_t LSTAT_BLOCK_SIZE = 512; 17 | 18 | using namespace KDirStat; 19 | 20 | KFileInfo::KFileInfo(KDirInfo *parent, const char *name) : _parent(parent) { 21 | // TODO: this contructor is only used by KDirInfo and should be moved there 22 | _isLocalFile = true; 23 | _name = name ? name : ""; 24 | _device = 0; 25 | _mode = 0; 26 | _links = 1; 27 | _size = 0; 28 | _blocks = 0; 29 | _mtime = 0; 30 | } 31 | 32 | KFileInfo::KFileInfo(const QString &filenameWithoutPath, struct stat *statInfo, 33 | KDirInfo *parent): _parent(parent) { 34 | Q_CHECK_PTR(statInfo); 35 | 36 | _isLocalFile = true; 37 | _name = filenameWithoutPath; 38 | 39 | _device = statInfo->st_dev; 40 | _mode = statInfo->st_mode; 41 | _links = statInfo->st_nlink; 42 | if(_links == 0) { 43 | // The file system probably does not support links 44 | _links = 1; 45 | } 46 | _mtime = statInfo->st_mtime; 47 | 48 | if (isSpecial()) { 49 | _size = 0; 50 | _blocks = 0; 51 | } else { 52 | _size = statInfo->st_size; 53 | _blocks = statInfo->st_blocks; 54 | } 55 | } 56 | 57 | KFileInfo::KFileInfo(const KFileItem *fileItem, KDirInfo *parent) 58 | : _parent(parent) { 59 | Q_CHECK_PTR(fileItem); 60 | 61 | _isLocalFile = fileItem->isLocalFile(); 62 | _name = parent ? fileItem->name() : fileItem->url().url(); 63 | _device = 0; 64 | _mode = fileItem->mode(); 65 | _links = 1; 66 | 67 | if (isSpecial()) { 68 | _size = 0; 69 | _blocks = 0; 70 | } else { 71 | _size = fileItem->size(); 72 | 73 | // Since KFileItem does not return any information about allocated disk 74 | // blocks, calculate that information artificially so callers don't 75 | // need to bother with special cases depending on how this object was 76 | // constructed. 77 | 78 | _blocks = _size / LSTAT_BLOCK_SIZE; 79 | 80 | if ((_size % LSTAT_BLOCK_SIZE) > 0) 81 | _blocks++; 82 | } 83 | 84 | _mtime = fileItem->time(KFileItem::ModificationTime).toTime_t(); 85 | } 86 | 87 | KFileInfo::KFileInfo(KDirInfo *parent, 88 | const QString &filenameWithoutPath, mode_t mode, 89 | KFileSize size, time_t mtime, KFileSize blocks, 90 | nlink_t links) 91 | : _parent(parent) { 92 | _name = filenameWithoutPath; 93 | _isLocalFile = true; 94 | _mode = mode; 95 | _size = size; 96 | _mtime = mtime; 97 | _links = links; 98 | 99 | if (blocks < 0) { 100 | _blocks = _size / LSTAT_BLOCK_SIZE; 101 | 102 | if ((_size % LSTAT_BLOCK_SIZE) > 0) 103 | _blocks++; 104 | } else { 105 | _blocks = blocks; 106 | } 107 | 108 | // qDebug() << "Created KFileInfo " << this << endl; 109 | } 110 | 111 | bool KFileInfo::isSparseFile() const { 112 | if(isSpecial() || !isFile()) 113 | return false; 114 | // Report difference between allocatedSize and byteSize() only if it's 115 | // significant. 116 | return std::abs(allocatedSize() - _size) > 4096; 117 | } 118 | 119 | KFileSize KFileInfo::allocatedSize() const { return blocks() * LSTAT_BLOCK_SIZE; } 120 | KFileSize KFileInfo::size() const { 121 | KFileSize sz = isSparseFile() ? allocatedSize() : _size; 122 | 123 | if (_links > 1) 124 | sz /= _links; 125 | 126 | return sz; 127 | } 128 | 129 | QString KFileInfo::url() const { 130 | if (_parent) { 131 | QString parentUrl = _parent->url(); 132 | 133 | if (isDotEntry()) // don't append "/." for dot entries 134 | return parentUrl; 135 | 136 | if (parentUrl == "/") // avoid duplicating slashes 137 | return parentUrl + _name; 138 | else 139 | return parentUrl + "/" + _name; 140 | } else 141 | return _name; 142 | } 143 | 144 | QString KFileInfo::debugUrl() const { 145 | return url() + (isDotEntry() ? "/" : ""); 146 | } 147 | 148 | QString KFileInfo::urlPart(int targetLevel) const { 149 | int level = treeLevel(); // Cache this - it's expensive! 150 | 151 | if (level < targetLevel) { 152 | qCritical() << Q_FUNC_INFO << "URL level " << targetLevel 153 | << " requested, this is level " << level << Qt::endl; 154 | return ""; 155 | } 156 | 157 | const KFileInfo *item = this; 158 | 159 | while (level > targetLevel) { 160 | level--; 161 | item = item->parent(); 162 | } 163 | 164 | return item->name(); 165 | } 166 | 167 | int KFileInfo::treeLevel() const { 168 | int level = 0; 169 | KFileInfo *parent = _parent; 170 | 171 | while (parent) { 172 | level++; 173 | parent = parent->parent(); 174 | } 175 | 176 | return level; 177 | 178 | if (_parent) 179 | return _parent->treeLevel() + 1; 180 | else 181 | return 0; 182 | } 183 | 184 | bool KFileInfo::isInSubtree(const KFileInfo *subtree) const { 185 | const KFileInfo *ancestor = this; 186 | 187 | while (ancestor) { 188 | if (ancestor == subtree) 189 | return true; 190 | 191 | ancestor = ancestor->parent(); 192 | } 193 | 194 | return false; 195 | } 196 | 197 | KFileInfo *KFileInfo::locate(QString url, bool findDotEntries) { 198 | if (!url.startsWith(_name)) 199 | return 0; 200 | else // URL starts with this node's name 201 | { 202 | url.remove(0, _name.length()); // Remove leading name of this node 203 | 204 | if (url.length() == 0) // Nothing left? 205 | return this; // Hey! That's us! 206 | 207 | if (url.startsWith("/")) // If the next thing a path delimiter, 208 | url.remove(0, 1); // remove that leading delimiter. 209 | else // No path delimiter at the beginning 210 | { 211 | if (_name.right(1) != "/" && // and this is not the root directory 212 | !isDotEntry()) // or a dot entry: 213 | return 0; // This can't be any of our children. 214 | } 215 | 216 | // Search all children 217 | 218 | for(size_t i = 0; i < numChildren(); i++) { 219 | KFileInfo *foundChild = this->child(i)->locate(url, findDotEntries); 220 | if (foundChild) 221 | return foundChild; 222 | } 223 | 224 | // Special case: The dot entry is requested. 225 | 226 | if (findDotEntries && dotEntry() && url == "") 227 | return dotEntry(); 228 | 229 | // Search the dot entry if there is one - but only if there is no more 230 | // path delimiter left in the URL. The dot entry contains files only, 231 | // and their names may not contain the path delimiter, nor can they 232 | // have children. This check is not strictly necessary, but it may 233 | // speed up things a bit if we don't search the non-directory children 234 | // if the rest of the URL consists of several pathname components. 235 | 236 | if (dotEntry() && url.indexOf('/') < 0) // No (more) "/" in this URL 237 | { 238 | return dotEntry()->locate(url, findDotEntries); 239 | } 240 | } 241 | 242 | return 0; 243 | } 244 | 245 | QUrl KDirStat::fixedUrl(const QString &dirtyUrl) { 246 | return QUrl::fromUserInput(dirtyUrl, QDir::currentPath(), 247 | QUrl::AssumeLocalFile); 248 | } 249 | 250 | QString KDirStat::formatSize(KFileSize lSize) { 251 | QString sizeString; 252 | QString unit; 253 | 254 | if (lSize < 1024) { 255 | sizeString.setNum((long)lSize); 256 | 257 | unit = i18n("Bytes"); 258 | } else { 259 | double size = lSize / 1024.0; // kB 260 | 261 | if (size < 1024.0) { 262 | sizeString = QString::number(size, 'f', 1); 263 | unit = i18n("kB"); 264 | } else { 265 | size /= 1024.0; // MB 266 | 267 | if (size < 1024.0) { 268 | sizeString = QString::number(size, 'f', 1); 269 | unit = i18n("MB"); 270 | } else { 271 | size /= 1024.0; // GB - we won't go any further... 272 | 273 | sizeString = QString::number(size, 'f', 2); 274 | unit = i18n("GB"); 275 | } 276 | } 277 | } 278 | 279 | if (!unit.isEmpty()) { 280 | sizeString += " " + unit; 281 | } 282 | 283 | return sizeString; 284 | } 285 | 286 | -------------------------------------------------------------------------------- /src/k4dirstat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File name: k4dirstat.h 3 | * Summary: The K4DirStat Application 4 | * License: GPL - See file COPYING for details. 5 | * 6 | * Author: Stefan Hundhammer 7 | * Joshua Hodosh 8 | * Parts auto-generated by KDevelop 9 | * 10 | * Updated: 2010-03-01 11 | */ 12 | 13 | #ifndef K4DIRSTAT_H 14 | #define K4DIRSTAT_H 15 | 16 | #include 17 | 18 | class QPrinter; 19 | class KUrl; 20 | class QLabel; 21 | 22 | class QSplitter; 23 | class KActivityTracker; 24 | class KFeedbackDialog; 25 | class KPacMan; 26 | class KAction; 27 | class KRecentFilesAction; 28 | class KToggleAction; 29 | 30 | namespace KDirStat { 31 | class KCleanupCollection; 32 | class KDirTreeView; 33 | class KDirTreeViewItem; 34 | class KDirTree; 35 | class KFileInfo; 36 | class KSettingsDialog; 37 | class KTreemapView; 38 | class KTreemapTile; 39 | } // namespace KDirStat 40 | 41 | using namespace KDirStat; 42 | 43 | /** 44 | * This class serves as the main window for k4dirstat. It handles the 45 | * menus, toolbars, and status bars. 46 | * 47 | * @short Main window class 48 | * @author %{AUTHOR} <%{EMAIL}> 49 | * @version %{VERSION} 50 | */ 51 | class k4dirstat : public KXmlGuiWindow { 52 | Q_OBJECT 53 | public: 54 | /** 55 | * Default Constructor 56 | */ 57 | k4dirstat(); 58 | 59 | static k4dirstat *instance() { return instance_; } 60 | 61 | /** 62 | * Default Destructor 63 | */ 64 | virtual ~k4dirstat(); 65 | 66 | /** 67 | * Open an URL specified by command line argument. 68 | **/ 69 | void openURL(const QUrl &url); 70 | 71 | /** 72 | * Return the main window's @ref KDirTreeView. 73 | **/ 74 | KDirTreeView *treeView() const { return _treeView; } 75 | 76 | /** 77 | * Returns the main window's @ref KTreemapView or 0 if there is none. 78 | * 79 | * Caution: Do not try to cache this value. The treemap view is destroyed 80 | * and re-created frequently! 81 | **/ 82 | KTreemapView *treemapView() const { return _treemapView; } 83 | 84 | public slots: 85 | /** 86 | * Open a directory tree. 87 | **/ 88 | void fileAskOpenDir(); 89 | 90 | /** 91 | * Open a (possibly remote) directory tree. 92 | **/ 93 | void fileAskOpenUrl(); 94 | 95 | /** 96 | * Refresh the entire directory tree, i.e. re-read everything from disk. 97 | **/ 98 | void refreshAll(); 99 | 100 | /** 101 | * Refresh the selected subtree, i.e. re-read it from disk. 102 | **/ 103 | void refreshSelected(); 104 | 105 | /** 106 | * Refresh the entire directory tree, i.e. re-read everything from disk. 107 | **/ 108 | void stopReading(); 109 | 110 | /** 111 | * Open a directory tree from the "recent" menu. 112 | **/ 113 | void fileOpenRecent(const QUrl &url); 114 | 115 | /** 116 | * asks for saving if the file is modified, then closes the current file 117 | * and window 118 | **/ 119 | void fileCloseDir(); 120 | 121 | /** 122 | * put the marked text/object into the clipboard 123 | **/ 124 | void editCopy(); 125 | 126 | /** 127 | * Notification that the view's selection has changed. 128 | * Enable/disable user actions as appropriate. 129 | **/ 130 | void selectionChanged(KDirTree*); 131 | 132 | /** 133 | * Ask user what application to open a file or directory with 134 | **/ 135 | void cleanupOpenWith(); 136 | 137 | /** 138 | * Toggle treemap view 139 | **/ 140 | void toggleTreemapView(); 141 | 142 | /** 143 | * Zoom in the treemap at the currently selected tile. 144 | **/ 145 | void treemapZoomIn(); 146 | 147 | /** 148 | * Zoom out the treemap after zooming in. 149 | **/ 150 | void treemapZoomOut(); 151 | 152 | /** 153 | * Select the parent of the currently selected treemap tile. 154 | **/ 155 | void treemapSelectParent(); 156 | 157 | /** 158 | * Rebuild the treemap. 159 | **/ 160 | void treemapRebuild(); 161 | 162 | /** 163 | * Invoke online help about treemaps. 164 | **/ 165 | void treemapHelp(); 166 | 167 | /** 168 | * Open settings dialog 169 | **/ 170 | void preferences(); 171 | 172 | /** 173 | * Changes the statusbar contents for the standard label permanently, used 174 | * to indicate current actions. 175 | * 176 | * @param text the text that is displayed in the statusbar 177 | **/ 178 | void statusMsg(const QString &text); 179 | 180 | /** 181 | * Opens a context menu for tree view items. 182 | **/ 183 | void contextMenu(const QPoint &pos); 184 | 185 | /** 186 | * Opens a context menu for treemap tiles. 187 | **/ 188 | void contextMenu(KTreemapTile *tile, const QPoint &pos); 189 | 190 | /** 191 | * Create a treemap view. This makes only sense after a directory tree is 192 | * completely read. 193 | **/ 194 | void createTreemapView(); 195 | 196 | /** 197 | * Create a treemap view after all events are processed. 198 | **/ 199 | void createTreemapViewDelayed(); 200 | 201 | /** 202 | * Delete an existing treemap view if there is one. 203 | **/ 204 | void deleteTreemapView(); 205 | 206 | /** 207 | * Sends a user feedback mail. 208 | **/ 209 | // void sendFeedbackMail(); 210 | 211 | /** 212 | * Read configuration for the main window. 213 | **/ 214 | void readMainWinConfig(); 215 | 216 | /** 217 | * Save the main window's configuration. 218 | **/ 219 | void saveMainWinConfig(); 220 | 221 | /** 222 | * Revert all cleanups to default values. 223 | **/ 224 | void revertCleanupsToDefaults(); 225 | 226 | /** 227 | * For the settings dialog only: Return the internal cleanup collection. 228 | **/ 229 | KCleanupCollection *cleanupCollection() { return _cleanupCollection; } 230 | 231 | /** 232 | * Returns true if the pacman animation in the tool bar is enabled, false 233 | * otherwise. 234 | **/ 235 | bool pacManEnabled() const { return _pacMan != 0; } 236 | 237 | /** 238 | * Ask user if he wouldn't like to rate this program. 239 | **/ 240 | // void askForFeedback(); 241 | 242 | /** 243 | * Notification that a feedback mail has been sent, thus don't remind 244 | * the user any more. 245 | **/ 246 | // void feedbackMailSent(); 247 | 248 | /** 249 | * Update enabled/disabled state of the user actions. 250 | **/ 251 | void updateActions(); 252 | 253 | /** 254 | * Open a file selection box to save the current directory tree to a 255 | * kdirstat cache file 256 | **/ 257 | void askWriteCache(); 258 | 259 | /** 260 | * Open a file selection box to read a directory tree from a kdirstat cache 261 | * file 262 | **/ 263 | void askReadCache(); 264 | 265 | private slots: 266 | void triggerSaveConfig(); 267 | 268 | signals: 269 | 270 | /** 271 | * Emitted when the configuration is to be read - other than at program 272 | * startup / object creation where each object is responsible for reading 273 | * its configuraton at an appropriate time. 274 | **/ 275 | void readConfig(); 276 | 277 | /** 278 | * Emitted when the configuration is to be saved. 279 | **/ 280 | void saveConfig(); 281 | 282 | // private slots: 283 | // void optionsPreferences(); 284 | 285 | protected: 286 | /** 287 | * Initialize @ref KCleanup actions. 288 | **/ 289 | void initCleanups(); 290 | 291 | /** 292 | * Set up status bar for the main window by initializing a status label. 293 | **/ 294 | void initStatusBar(); 295 | 296 | // Widgets 297 | 298 | QSplitter *_splitter; 299 | KDirTreeView *_treeView; 300 | KTreemapView *_treemapView; 301 | KPacMan *_pacMan; 302 | QWidget *_pacManDelimiter; 303 | QMenu *_treeViewContextMenu; 304 | QMenu *_treemapContextMenu; 305 | KDirStat::KSettingsDialog *_settingsDialog; 306 | KFeedbackDialog *_feedbackDialog; 307 | KActivityTracker *_activityTracker; 308 | QLabel * _freeSpaceLabel; 309 | 310 | QAction *_fileAskOpenDir; 311 | QAction *_fileAskOpenUrl; 312 | KRecentFilesAction *_fileOpenRecent; 313 | QAction *_fileCloseDir; 314 | QAction *_fileRefreshAll; 315 | QAction *_fileRefreshSelected; 316 | QAction *_fileReadExcludedDir; 317 | QAction *_fileContinueReadingAtMountPoint; 318 | QAction *_fileStopReading; 319 | QAction *_fileAskWriteCache; 320 | QAction *_fileAskReadCache; 321 | QAction *_fileQuit; 322 | QAction *_editCopy; 323 | QAction *_cleanupOpenWith; 324 | QAction *_treemapZoomIn; 325 | QAction *_treemapZoomOut; 326 | QAction *_treemapSelectParent; 327 | QAction *_treemapRebuild; 328 | 329 | QAction *_reportMailToOwner; 330 | QAction *_helpSendFeedbackMail; 331 | KToggleAction *_showTreemapView; 332 | 333 | KCleanupCollection *_cleanupCollection; 334 | 335 | int _treemapViewHeight; 336 | 337 | private: 338 | void setupActions(); 339 | static k4dirstat *instance_; 340 | 341 | private: 342 | QPrinter *m_printer; 343 | }; 344 | 345 | #endif // _K4DIRSTAT_H_ 346 | -------------------------------------------------------------------------------- /src/ktreemaptile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include 10 | 11 | namespace KDirStat { 12 | class KFileInfo; 13 | class KTreemapView; 14 | 15 | enum KOrientation { KTreemapHorizontal, KTreemapVertical, KTreemapAuto }; 16 | 17 | /** 18 | * Helper class for cushioned treemaps: This class holds the polynome 19 | * parameters for the cushion surface. The height of each point of such a 20 | * surface is defined as: 21 | * 22 | * z(x, y) = a*x^2 + b*y^2 + c*x + d*y 23 | * or 24 | * z(x, y) = xx2*x^2 + yy2*y^2 + xx1*x + yy1*y 25 | * 26 | * to better keep track of which coefficient belongs where. 27 | **/ 28 | class KCushionSurface { 29 | public: 30 | /** 31 | * Constructor. All polynome coefficients are set to 0. 32 | **/ 33 | KCushionSurface(); 34 | 35 | /** 36 | * Adds a ridge of the specified height in dimension 'dim' within 37 | * rectangle 'rect' to this surface. It's real voodo magic. 38 | * 39 | * Just kidding - read the paper about "cushion treemaps" by Jarke 40 | * J. van Wiik and Huub van de Wetering from the TU Eindhoven, NL for 41 | * more details. 42 | * 43 | * If you don't want to get all that involved: The coefficients are 44 | * changed in some way. 45 | **/ 46 | void addRidge(KOrientation dim, double height, const QRectF &rect); 47 | 48 | /** 49 | * Set the cushion's height. 50 | **/ 51 | void setHeight(double newHeight) { _height = newHeight; } 52 | 53 | /** 54 | * Returns the cushion's height. 55 | **/ 56 | double height() const { return _height; } 57 | 58 | /** 59 | * Returns the polynomal coefficient of the second order for X direction. 60 | **/ 61 | double xx2() const { return _xx2; } 62 | 63 | /** 64 | * Returns the polynomal coefficient of the first order for X direction. 65 | **/ 66 | double xx1() const { return _xx1; } 67 | 68 | /** 69 | * Returns the polynomal coefficient of the second order for Y direction. 70 | **/ 71 | double yy2() const { return _yy2; } 72 | 73 | /** 74 | * Returns the polynomal coefficient of the first order for Y direction. 75 | **/ 76 | double yy1() const { return _yy1; } 77 | 78 | protected: 79 | /** 80 | * Calculate a new square polynomal coefficient for adding a ridge of 81 | * specified height between x1 and x2. 82 | **/ 83 | double squareRidge(double squareCoefficient, double height, int x1, int x2); 84 | 85 | /** 86 | * Calculate a new linear polynomal coefficient for adding a ridge of 87 | * specified height between x1 and x2. 88 | **/ 89 | double linearRidge(double linearCoefficient, double height, int x1, int x2); 90 | 91 | // Data members 92 | 93 | double _xx2, _xx1; 94 | double _yy2, _yy1; 95 | double _height; 96 | 97 | }; // class KCushionSurface 98 | 99 | /** 100 | * This is the basic building block of a treemap view: One single tile of a 101 | * treemap. If it corresponds to a leaf in the tree, it will be visible as 102 | * one tile (one rectangle) of the treemap. If it has children, it will be 103 | * subdivided again. 104 | * 105 | * @short Basic building block of a treemap 106 | **/ 107 | class KTreemapTile : public QGraphicsRectItem { 108 | public: 109 | /** 110 | * Constructor: Create a treemap tile from 'fileinfo' that fits into a 111 | * rectangle 'rect' inside 'parent'. 112 | * 113 | * 'orientation' is the direction for further subdivision. 'Auto' 114 | * selects the wider direction inside 'rect'. 115 | **/ 116 | KTreemapTile(KTreemapView *parentView, KTreemapTile *parentTile, 117 | KFileInfo *orig, const QRectF &rect, 118 | KOrientation orientation = KTreemapAuto); 119 | 120 | protected: 121 | /** 122 | * Alternate constructor: Like the above, but explicitly specify a 123 | * cushion surface rather than using the parent's. 124 | **/ 125 | KTreemapTile(KTreemapView *parentView, KTreemapTile *parentTile, 126 | KFileInfo *orig, const QRect &rect, 127 | const KCushionSurface &cushionSurface, 128 | KOrientation orientation = KTreemapAuto); 129 | 130 | public: 131 | /** 132 | * Destructor. 133 | **/ 134 | virtual ~KTreemapTile(); 135 | 136 | /** 137 | * Returns the original @ref KFileInfo item that corresponds to this 138 | * treemap tile. 139 | **/ 140 | KFileInfo *orig() const { return _orig; } 141 | 142 | /** 143 | * Returns the parent @ref KTreemapView. 144 | **/ 145 | KTreemapView *parentView() const { return _parentView; } 146 | 147 | /** 148 | * Returns the parent @ref KTreemapTile or 0 if there is none. 149 | **/ 150 | KTreemapTile *parentTile() const { return _parentTile; } 151 | 152 | /** 153 | * Returns this tile's cushion surface parameters. 154 | **/ 155 | KCushionSurface &cushionSurface() { return _cushionSurface; } 156 | 157 | protected: 158 | /** 159 | * Create children (sub-tiles) of this tile. 160 | **/ 161 | void createChildren(const QRectF &rect, KOrientation orientation); 162 | 163 | /** 164 | * Create children (sub-tiles) using the simple treemap algorithm: 165 | * Alternate between horizontal and vertical subdivision in each 166 | * level. Each child will get the entire height or width, respectively, 167 | * of the specified rectangle. This algorithm is very fast, but often 168 | * results in very thin, elongated tiles. 169 | **/ 170 | void createChildrenSimple(const QRectF &rect, KOrientation orientation); 171 | 172 | /** 173 | * Create children using the "squarified treemaps" algorithm as 174 | * described by Mark Bruls, Kees Huizing, and Jarke J. van Wijk of the 175 | * TU Eindhoven, NL. 176 | * 177 | * This algorithm is not quite so simple and involves more expensive 178 | * operations, e.g., sorting the children of each node by size first, 179 | * try some variations of the layout and maybe backtrack to the 180 | * previous attempt. But it results in tiles that are much more 181 | * square-like, i.e. have more reasonable width-to-height ratios. It is 182 | * very much less likely to get thin, elongated tiles that are hard to 183 | * point at and even harder to compare visually against each other. 184 | * 185 | * This implementation includes some improvements to that basic 186 | * algorithm. For example, children below a certain size are 187 | * disregarded completely since they will not get an adequate visual 188 | * representation anyway (it would be way too small). They are 189 | * summarized in some kind of 'misc stuff' area in the parent treemap 190 | * tile - in fact, part of the parent directory's tile can be "seen 191 | * through". 192 | * 193 | * In short, a lot of small children that don't have any useful effect 194 | * for the user in finding wasted disk space are omitted from handling 195 | * and, most important, don't need to be sorted by size (which has a 196 | * cost of O(n*ln(n)) in the best case, so reducing n helps a lot). 197 | **/ 198 | void createSquarifiedChildren(const QRectF &rect); 199 | 200 | /** 201 | * Squarify as many children as possible: Try to squeeze members 202 | * referred to by 'it' into 'rect' until the aspect ratio doesn't get 203 | * better any more. Returns a list of children that should be laid out 204 | * in 'rect'. Moves 'it' until there is no more improvement or 'it' 205 | * runs out of items. 206 | * 207 | * 'scale' is the scaling factor between file sizes and pixels. 208 | **/ 209 | void squarify(const QRectF &rect, double scale, 210 | std::vector::iterator &, 211 | std::vector::iterator end, 212 | std::vector & output); 213 | 214 | /** 215 | * Lay out all members of 'row' within 'rect' along its longer side. 216 | * Returns the new rectangle with the layouted area subtracted. 217 | **/ 218 | QRectF layoutRow(const QRectF &rect, double scale, std::vector &row); 219 | 220 | /** 221 | * Draw the tile. 222 | * 223 | * Reimplemented from QCanvasRectangle. 224 | **/ 225 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 226 | QWidget *widget = 0) override; 227 | 228 | /** 229 | * Render a cushion as described in "cushioned treemaps" by Jarke 230 | * J. van Wijk and Huub van de Wetering of the TU Eindhoven, NL. 231 | **/ 232 | QPixmap renderCushion(); 233 | 234 | /** 235 | * Check if the contrast of the specified image is sufficient to 236 | * visually distinguish an outline at the right and bottom borders 237 | * and add a grey line there, if necessary. 238 | **/ 239 | void ensureContrast(QImage &image); 240 | 241 | /** 242 | * Returns a color that gives a reasonable contrast to 'col': Lighter 243 | * if 'col' is dark, darker if 'col' is light. 244 | **/ 245 | QRgb contrastingColor(QRgb col); 246 | 247 | private: 248 | /** 249 | * Initialization common to all constructors. 250 | **/ 251 | void init(); 252 | 253 | protected: 254 | // Data members 255 | 256 | KTreemapView *_parentView; 257 | KTreemapTile *_parentTile; 258 | KFileInfo *_orig; 259 | KCushionSurface _cushionSurface; 260 | QPixmap _cushion; 261 | 262 | }; // class KTreemapTile 263 | 264 | } // namespace KDirStat 265 | 266 | inline QDebug &operator<<(QDebug &stream, const QRect &rect) { 267 | stream << "(" << rect.width() << "x" << rect.height() << "+" << rect.x() 268 | << "+" << rect.y() << ")"; 269 | 270 | return stream; 271 | } 272 | 273 | -------------------------------------------------------------------------------- /src/kdirtree.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "kdirreadjob.h" 8 | #include "kdirtree.h" 9 | #include "kdirtreecache.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | using namespace KDirStat; 15 | 16 | KDirTree::KDirTree() : QObject() { 17 | _root = 0; 18 | _isFileProtocol = false; 19 | _isBusy = false; 20 | _readMethod = KDirReadUnknown; 21 | 22 | readConfig(); 23 | 24 | connect(&_jobQueue, SIGNAL(finished()), this, SLOT(slotFinished())); 25 | } 26 | 27 | KDirTree::~KDirTree() { 28 | _jobQueue.clear(); 29 | selectItems(); 30 | 31 | if (_root) 32 | delete _root; 33 | } 34 | 35 | void KDirTree::readConfig() { 36 | KConfigGroup config = KSharedConfig::openConfig()->group("Directory Reading"); 37 | 38 | _crossFileSystems = config.readEntry("CrossFileSystems", false); 39 | _enableLocalDirReader = config.readEntry("EnableLocalDirReader", true); 40 | } 41 | 42 | void KDirTree::setRoot(KFileInfo *newRoot) { 43 | if (_root) { 44 | selectItems(); 45 | emit deletingChild(_root); 46 | delete _root; 47 | emit childDeleted(); 48 | } 49 | 50 | _root = newRoot; 51 | } 52 | 53 | void KDirTree::clear(bool sendSignals) { 54 | _jobQueue.clear(); 55 | 56 | if (_root) { 57 | selectItems(); 58 | 59 | if (sendSignals) 60 | emit deletingChild(_root); 61 | 62 | delete _root; 63 | _root = 0; 64 | 65 | if (sendSignals) 66 | emit childDeleted(); 67 | } 68 | 69 | _isBusy = false; 70 | } 71 | 72 | void KDirTree::startReading(const QUrl &url) { 73 | // qDebug() << Q_FUNC_INFO << " " << url.url() << endl; 74 | 75 | #if 0 76 | qDebug() << "url: " << url.url() << endl; 77 | qDebug() << "path: " << url.path() << endl; 78 | qDebug() << "filename: " << url.filename() << endl; 79 | qDebug() << "protocol: " << url.protocol() << endl; 80 | qDebug() << "isValid: " << url.isValid() << endl; 81 | qDebug() << "isMalformed: " << url.isMalformed() << endl; 82 | qDebug() << "isLocalFile: " << url.isLocalFile() << endl; 83 | #endif 84 | 85 | _isBusy = true; 86 | emit startingReading(); 87 | 88 | setRoot(0); 89 | readConfig(); 90 | _isFileProtocol = url.isLocalFile(); 91 | 92 | if (_isFileProtocol && _enableLocalDirReader) { 93 | // qDebug() << "Using local directory reader for " << url.url() << endl; 94 | _readMethod = KDirReadLocal; 95 | _root = KLocalDirReadJob::stat(url); 96 | } else { 97 | // qDebug() << "Using KIO methods for " << url.url() << endl; 98 | _readMethod = KDirReadKIO; 99 | _root = KioDirReadJob::stat(url); 100 | } 101 | 102 | if (_root) { 103 | childAddedNotify(_root); 104 | 105 | if (_root->isDir()) { 106 | KDirInfo *dir = (KDirInfo *)_root; 107 | 108 | if (_readMethod == KDirReadLocal) 109 | addJob(new KLocalDirReadJob(this, dir)); 110 | else 111 | addJob(new KioDirReadJob(this, dir)); 112 | } else { 113 | _isBusy = false; 114 | emit finished(); 115 | } 116 | } else // stat() failed 117 | { 118 | // qWarning() << "stat(" << url.url() << ") failed" << endl; 119 | _isBusy = false; 120 | emit finished(); 121 | emit finalizeLocal(0); 122 | } 123 | } 124 | 125 | void KDirTree::selectionInSubTree(KFileInfo *subtree) { 126 | for(size_t i = 0; i < _selection.size(); i++) { 127 | if(_selection[i]->isInSubtree(subtree)) { 128 | selectItems(); 129 | break; 130 | } 131 | } 132 | } 133 | 134 | void KDirTree::refresh(KFileInfo *subtree) { 135 | if (!_root) 136 | return; 137 | 138 | if (!subtree || !subtree->parent()) // Refresh all (from root) 139 | { 140 | startReading(fixedUrl(_root->url())); 141 | } else // Refresh subtree 142 | { 143 | // Save some values from the old subtree. 144 | 145 | QUrl url = QUrl::fromUserInput(subtree->url(), QDir::currentPath(), 146 | QUrl::AssumeLocalFile); 147 | KDirInfo *parent = subtree->parent(); 148 | 149 | // Select nothing if the current selection is to be deleted 150 | selectionInSubTree(subtree); 151 | 152 | // Clear any old "excluded" status 153 | 154 | subtree->setExcluded(false); 155 | 156 | // Get rid of the old subtree. 157 | 158 | emit deletingChild(subtree); 159 | 160 | // qDebug() << "Deleting subtree " << subtree << endl; 161 | 162 | /** 163 | * This may sound stupid, but the parent must be told to unlink its 164 | * child from the children list. The child cannot simply do this by 165 | * itself in its destructor since at this point important parts of the 166 | * object may already be destroyed, e.g., the virtual table - 167 | * i.e. virtual methods won't work any more. 168 | * 169 | * I just found that out the hard way by several hours of debugging. ;-} 170 | **/ 171 | parent->deletingChild(subtree); 172 | delete subtree; 173 | emit childDeleted(); 174 | 175 | _isBusy = true; 176 | emit startingReading(); 177 | 178 | // Create new subtree root. 179 | 180 | subtree = (_readMethod == KDirReadLocal) 181 | ? KLocalDirReadJob::stat(url, parent) 182 | : KioDirReadJob::stat(url, parent); 183 | 184 | // qDebug() << "New subtree: " << subtree << endl; 185 | 186 | if (subtree) { 187 | // Insert new subtree root into the tree hierarchy. 188 | 189 | parent->insertChild(subtree); 190 | childAddedNotify(subtree); 191 | 192 | if (subtree->isDir()) { 193 | // Prepare reading this subtree's contents. 194 | 195 | KDirInfo *dir = (KDirInfo *)subtree; 196 | 197 | if (_readMethod == KDirReadLocal) 198 | addJob(new KLocalDirReadJob(this, dir)); 199 | else 200 | addJob(new KioDirReadJob(this, dir)); 201 | } else { 202 | _isBusy = false; 203 | emit finished(); 204 | } 205 | } 206 | } 207 | } 208 | 209 | void KDirTree::abortReading() { 210 | if (_jobQueue.isEmpty()) 211 | return; 212 | 213 | _jobQueue.abort(); 214 | 215 | _isBusy = false; 216 | emit aborted(); 217 | } 218 | 219 | void KDirTree::slotFinished() { 220 | _isBusy = false; 221 | emit finished(); 222 | } 223 | 224 | void KDirTree::childAddedNotify(KFileInfo *newChild) { 225 | emit childAdded(newChild); 226 | 227 | if (newChild->dotEntry()) 228 | emit childAdded(newChild->dotEntry()); 229 | } 230 | 231 | void KDirTree::deletingChildNotify(KFileInfo *deletedChild) { 232 | emit deletingChild(deletedChild); 233 | 234 | // Only now check for selection and root: Give connected objects 235 | // (i.e. views) a chance to change either while handling the signal. 236 | selectionInSubTree(deletedChild); 237 | 238 | if (deletedChild == _root) 239 | _root = 0; 240 | } 241 | 242 | void KDirTree::childDeletedNotify() { emit childDeleted(); } 243 | 244 | void KDirTree::deleteSubtree(KFileInfo *subtree) { 245 | // qDebug() << "Deleting subtree " << subtree << endl; 246 | KDirInfo *parent = subtree->parent(); 247 | 248 | if (parent) { 249 | // Give the parent of the child to be deleted a chance to unlink the 250 | // child from its children list and take care of internal summary 251 | // fields 252 | parent->deletingChild(subtree); 253 | } 254 | 255 | // Send notification to anybody interested (e.g., to attached views) 256 | deletingChildNotify(subtree); 257 | 258 | if (parent) { 259 | if (parent->isDotEntry() && !parent->hasChildren()) 260 | // This was the last child of a dot entry 261 | { 262 | // Get rid of that now empty and useless dot entry 263 | 264 | if (parent->parent()) { 265 | if (parent->parent()->isFinished()) { 266 | // qDebug() << "Removing empty dot entry " << parent << endl; 267 | 268 | deletingChildNotify(parent); 269 | parent->parent()->setDotEntry(0); 270 | 271 | delete parent; 272 | } 273 | } else // no parent - this should never happen (?) 274 | { 275 | qCritical() << "Internal error: Killing dot entry without parent " 276 | << parent << Qt::endl; 277 | 278 | // Better leave that dot entry alone - we shouldn't have come 279 | // here in the first place. Who knows what will happen if this 280 | // thing is deleted now?! 281 | // 282 | // Intentionally NOT calling: 283 | // delete parent; 284 | } 285 | } 286 | } 287 | 288 | delete subtree; 289 | 290 | if (subtree == _root) { 291 | selectItems(); 292 | _root = 0; 293 | } 294 | 295 | emit childDeleted(); 296 | } 297 | 298 | void KDirTree::addJob(KDirReadJob *job) { _jobQueue.enqueue(job); } 299 | 300 | void KDirTree::sendProgressInfo(const QString &infoLine) { 301 | emit progressInfo(infoLine); 302 | } 303 | 304 | void KDirTree::sendFinalizeLocal(KDirInfo *dir) { emit finalizeLocal(dir); } 305 | 306 | void KDirTree::sendStartingReading() { emit startingReading(); } 307 | 308 | void KDirTree::sendFinished() { emit finished(); } 309 | 310 | void KDirTree::sendAborted() { emit aborted(); } 311 | 312 | void KDirTree::selectItems(const std::vector & newSelection) { 313 | if (newSelection != _selection) { 314 | _selection = newSelection; 315 | emit selectionChanged(this); 316 | } 317 | } 318 | 319 | bool KDirTree::writeCache(const QString &cacheFileName) { 320 | KCacheWriter writer(cacheFileName, this); 321 | return writer.ok(); 322 | } 323 | 324 | void KDirTree::readCache(const QString &cacheFileName) { 325 | _isBusy = true; 326 | emit startingReading(); 327 | addJob(new KCacheReadJob(this, 0, cacheFileName)); 328 | } 329 | 330 | -------------------------------------------------------------------------------- /src/kdirinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * License: LGPL - See file COPYING.LIB for details. 4 | * Author: Stefan Hundhammer 5 | */ 6 | 7 | #include "kfileinfo.h" 8 | #include 9 | 10 | #ifndef NOT_USED 11 | #define NOT_USED(PARAM) ((void)(PARAM)) 12 | #endif 13 | 14 | // Open a new name space since KDE's name space is pretty much cluttered 15 | // already - all names that would even remotely match are already used up, 16 | // yet the resprective classes don't quite fit the purposes required here. 17 | 18 | namespace KDirStat { 19 | // Forward declarations 20 | class KDirTree; 21 | 22 | /** 23 | * A more specialized version of @ref KFileInfo: This class can actually 24 | * manage children. The base class (@ref KFileInfo) has only stubs for the 25 | * respective methods to integrate seamlessly with the abstraction of a 26 | * file / directory tree; this class fills those stubs with life. 27 | * 28 | * @short directory item within a @ref KDirTree. 29 | **/ 30 | class KDirInfo : public KFileInfo { 31 | public: 32 | /** 33 | * Default constructor. 34 | * 35 | * If "asDotEntry" is set, this will be used as the parent's 36 | * "dot entry", i.e. the pseudo directory that holds all the parent's 37 | * non-directory children. This is the only way to create a "dot 38 | * entry"! 39 | **/ 40 | KDirInfo(KDirInfo *parent = nullptr, bool asDotEntry = false); 41 | 42 | /** 43 | * Constructor from a stat buffer (i.e. based on an lstat() call). 44 | **/ 45 | KDirInfo(const QString &filenameWithoutPath, struct stat *statInfo, 46 | KDirInfo *parent = nullptr); 47 | 48 | /** 49 | * Constructor from a KFileItem, i.e. from a @ref KIO::StatJob 50 | **/ 51 | KDirInfo(const KFileItem *fileItem, KDirInfo *parent = nullptr); 52 | 53 | /** 54 | * Constructor from the bare neccessary fields 55 | * for use from a cache file reader 56 | **/ 57 | KDirInfo(KDirInfo *parent, const QString &filenameWithoutPath, 58 | mode_t mode, KFileSize size, time_t mtime); 59 | 60 | /** 61 | * Destructor. 62 | **/ 63 | virtual ~KDirInfo(); 64 | 65 | /** 66 | * Returns the total size in bytes of this subtree. 67 | * 68 | * Reimplemented - inherited from @ref KFileInfo. 69 | **/ 70 | KFileSize totalSize() override; 71 | 72 | /** 73 | * Returns the total number of children in this subtree, excluding this item. 74 | * 75 | * Reimplemented - inherited from @ref KFileInfo. 76 | **/ 77 | int totalItems() override; 78 | 79 | /** 80 | * Returns the total number of subdirectories in this subtree, 81 | * excluding this item. Dot entries and "." or ".." are not counted. 82 | * 83 | * Reimplemented - inherited from @ref KFileInfo. 84 | **/ 85 | int totalSubDirs() override; 86 | 87 | /** 88 | * Returns the total number of plain file children in this subtree, 89 | * excluding this item. 90 | * 91 | * Reimplemented - inherited from @ref KFileInfo. 92 | **/ 93 | int totalFiles() override; 94 | 95 | /** 96 | * Returns the latest modification time of this subtree. 97 | * 98 | * Reimplemented - inherited from @ref KFileInfo. 99 | **/ 100 | time_t latestMtime() override; 101 | 102 | /** 103 | * Returns 'true' if this had been excluded while reading. 104 | **/ 105 | bool isExcluded() const override { return _isExcluded; } 106 | 107 | /** 108 | * Set the 'excluded' status. 109 | **/ 110 | void setExcluded(bool excl = true) override { _isExcluded = excl; } 111 | 112 | /** 113 | * Returns whether or not this is a mount point. 114 | * 115 | * This will return 'false' only if this information can be obtained at 116 | * all, i.e. if local directory reading methods are used. 117 | * 118 | * Reimplemented - inherited from @ref KFileInfo. 119 | **/ 120 | bool isMountPoint() override { return _isMountPoint; } 121 | 122 | /** 123 | * Sets the mount point state, i.e. whether or not this is a mount 124 | * point. 125 | * 126 | * Reimplemented - inherited from @ref KFileInfo. 127 | **/ 128 | void setMountPoint(bool isMountPoint = true) override; 129 | 130 | /** 131 | * Returns true if this subtree is finished reading. 132 | * 133 | * Reimplemented - inherited from @ref KFileInfo. 134 | **/ 135 | bool isFinished() override; 136 | 137 | /** 138 | * Returns true if this subtree is busy, i.e. it is not finished 139 | * reading yet. 140 | * 141 | * Reimplemented - inherited from @ref KFileInfo. 142 | **/ 143 | bool isBusy() override; 144 | 145 | /** 146 | * Returns the number of pending read jobs in this subtree. When this 147 | * number reaches zero, the entire subtree is done. 148 | * 149 | * Reimplemented - inherited from @ref KFileInfo. 150 | **/ 151 | int pendingReadJobs() override { return _pendingReadJobs; } 152 | 153 | size_t numChildren() const override { return children_.size(); } 154 | KFileInfo * child(size_t i) override { return children_[i]; } 155 | 156 | /** 157 | * Insert a child into the children list. 158 | * 159 | * The order of children in this list is absolutely undefined; 160 | * don't rely on any implementation-specific order. 161 | **/ 162 | void insertChild(KFileInfo *newChild) override; 163 | 164 | /** 165 | * Get the "Dot Entry" for this node if there is one (or 0 otherwise): 166 | * This is a pseudo entry that directory nodes use to store 167 | * non-directory children separately from directories. This way the end 168 | * user can easily tell which summary fields belong to the directory 169 | * itself and which are the accumulated values of the entire subtree. 170 | **/ 171 | KDirInfo *dotEntry() const override { return _dotEntry; } 172 | 173 | /** 174 | * Set a "Dot Entry". This makes sense for directories only. 175 | **/ 176 | void setDotEntry(KDirInfo *newDotEntry) override { _dotEntry = newDotEntry; } 177 | 178 | /** 179 | * Returns true if this is a "Dot Entry". See @ref dotEntry() for 180 | * details. 181 | * 182 | * Reimplemented - inherited from @ref KFileInfo. 183 | **/ 184 | bool isDotEntry() const override { return _isDotEntry; } 185 | 186 | /** 187 | * Notification that a child has been added somewhere in the subtree. 188 | * 189 | * Reimplemented - inherited from @ref KFileInfo. 190 | **/ 191 | void childAdded(KFileInfo *newChild) override; 192 | 193 | /** 194 | * Notification that a child is about to be deleted somewhere in the 195 | * subtree. 196 | * 197 | * Reimplemented - inherited from @ref KFileInfo. 198 | **/ 199 | void deletingChild(KFileInfo *deletedChild) override; 200 | 201 | /** 202 | * Notification of a new directory read job somewhere in the subtree. 203 | **/ 204 | void readJobAdded(); 205 | 206 | /** 207 | * Notification of a finished directory read job somewhere in the 208 | * subtree. 209 | **/ 210 | void readJobFinished(); 211 | 212 | /** 213 | * Notification of an aborted directory read job somewhere in the 214 | * subtree. 215 | **/ 216 | void readJobAborted(); 217 | 218 | /** 219 | * Finalize this directory level after reading it is completed. 220 | * This does _not_ mean reading reading all subdirectories is completed 221 | * as well! 222 | * 223 | * Clean up unneeded dot entries. 224 | **/ 225 | virtual void finalizeLocal(); 226 | 227 | /** 228 | * Recursively finalize all directories from here on - 229 | * call finalizeLocal() recursively. 230 | **/ 231 | void finalizeAll(KDirTree *); 232 | 233 | /** 234 | * Get the current state of the directory reading process: 235 | * 236 | * KDirQueued waiting in the directory read queue 237 | * KDirReading reading in progress 238 | * KDirFinished reading finished and OK 239 | * KDirAborted reading aborted upon user request 240 | * KDirError error while reading 241 | * 242 | * Reimplemented - inherited from @ref KFileInfo. 243 | **/ 244 | KDirReadState readState() const override; 245 | 246 | /** 247 | * Set the state of the directory reading process. 248 | * See @ref readState() for details. 249 | **/ 250 | void setReadState(KDirReadState newReadState); 251 | 252 | /** 253 | * Returns true if this is a @ref KDirInfo object. 254 | * 255 | * Don't confuse this with @ref isDir() which tells whether or not this 256 | * is a disk directory! Both should return the same, but you'll never 257 | * know - better be safe than sorry! 258 | * 259 | * Reimplemented - inherited from @ref KFileInfo. 260 | **/ 261 | bool isDirInfo() const override { return true; } 262 | 263 | protected: 264 | /** 265 | * Recursively recalculate the summary fields when they are dirty. 266 | * 267 | * This is a _very_ expensive operation since the entire subtree may 268 | * recursively be traversed. 269 | **/ 270 | void recalc(); 271 | 272 | /** 273 | * Clean up unneeded / undesired dot entries: 274 | * Delete dot entries that don't have any children, 275 | * reparent dot entry children to the "real" (parent) directory if 276 | * there are not subdirectory siblings at the level of the dot entry. 277 | **/ 278 | void cleanupDotEntries(); 279 | 280 | // 281 | // Data members 282 | // 283 | 284 | bool _isDotEntry : 1; // Flag: is this entry a "dot entry"? 285 | bool _isMountPoint : 1; // Flag: is this a mount point? 286 | bool _isExcluded : 1; // Flag: was this directory excluded? 287 | int _pendingReadJobs; // number of open directories in this subtree 288 | KDirInfo *_dotEntry; // pseudo entry to hold non-dir children 289 | 290 | // Some cached values 291 | 292 | KFileSize _totalSize; 293 | int _totalItems; 294 | int _totalSubDirs; 295 | int _totalFiles; 296 | time_t _latestMtime; 297 | 298 | bool _summaryDirty : 1; // dirty flag for the cached values 299 | bool _beingDestroyed : 1; 300 | KDirReadState _readState; 301 | 302 | private: 303 | void recalcOneChild(KFileInfo*); 304 | void init(); 305 | //TODO: could we store KFileInfo instead of KFileInfo* ? 306 | std::vector children_; 307 | 308 | }; // class KDirInfo 309 | 310 | } // namespace KDirStat 311 | 312 | -------------------------------------------------------------------------------- /src/kcleanup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include "kdirtree.h" 10 | 11 | namespace KDirStat { 12 | /** 13 | * Cleanup action to be performed for @ref KDirTree items. 14 | * 15 | * @short KDirStat cleanup action 16 | **/ 17 | 18 | class KCleanup { 19 | public: 20 | enum RefreshPolicy { noRefresh, refreshThis, refreshParent, assumeDeleted }; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * 'id' is the name of this cleanup action as used in the XML UI file 26 | * and config files, 'title' is the human readable menu title. 27 | * 'command' is the shell command to execute. 28 | * 29 | * Most applications will want to pass KMainWindow::actionCollection() 30 | * for 'parent' so the menus and toolbars can be created using the XML 31 | * UI description ('kdirstatui.rc' for KDirStat). 32 | **/ 33 | KCleanup(QString id, QString command, QString title); 34 | 35 | /** 36 | * Copy Constructor. 37 | * 38 | * Notice that this is a not quite complete copy constructor: Since 39 | * there is no KAction copy constructor, the inherited KAction members 40 | * will be constructed with the KAction default constructor. Thus, an 41 | * object created with this copy constructor can rely only on its 42 | * KCleanup members. This is intended for save/restore operations only, 43 | * not for general use. In particular, DO NOT connect an object thus 44 | * constructed with signals. The results will be undefined (at best). 45 | **/ 46 | KCleanup(const KCleanup &src); 47 | 48 | virtual ~KCleanup() {} 49 | /** 50 | * Assignment operator. 51 | * 52 | * This will not modify the KAction members, just the KCleanup 53 | * members. Just like the copy constructor, this is intended for 54 | * save/restore operations, not for general use. 55 | **/ 56 | KCleanup &operator=(const KCleanup &src); 57 | 58 | /** 59 | * Return the ID (name) of this cleanup action as used for setup files 60 | * and the XML UI description. This ID should be unique within the 61 | * application. 62 | **/ 63 | const QString &id() const { return _id; } 64 | 65 | /** 66 | * Return the command line that will be executed upon calling @ref 67 | * KCleanup::execute(). This command line may contain %p for the 68 | * complete path of the directory or file concerned or %n for the pure 69 | * file or directory name without path. 70 | **/ 71 | const QString &command() const { return _command; } 72 | 73 | /** 74 | * Return the user title of this command as displayed in menus. 75 | * This may include '&' characters for keyboard shortcuts. 76 | * See also @ref cleanTitle() . 77 | **/ 78 | const QString &title() const { return _title; } 79 | 80 | /** 81 | * Returns the cleanup action's title without '&' keyboard shortcuts. 82 | * Uses the ID as fallback if the name is empty. 83 | **/ 84 | QString cleanTitle() const; 85 | 86 | /** 87 | * Return whether or not this cleanup action is generally enabled. 88 | **/ 89 | bool enabled() const { return _enabled; } 90 | 91 | /** 92 | * Return whether or not this cleanup action works for this particular 93 | * KFileInfo. Checks all the other conditions (enabled(), 94 | * worksForDir(), worksForFile(), ...) accordingly. 95 | **/ 96 | bool worksFor(KFileInfo *item, KDirTree *) const; 97 | 98 | /** 99 | * Return whether or not this cleanup action works for directories, 100 | * i.e. whether or not @ref KCleanup::execute() will be successful if 101 | * the object passed is a directory. 102 | **/ 103 | bool worksForDir() const { return _worksForDir; } 104 | 105 | /** 106 | * Return whether or not this cleanup action works for plain files. 107 | **/ 108 | bool worksForFile() const { return _worksForFile; } 109 | 110 | /** 111 | * Return whether or not this cleanup action works for KDirStat's 112 | * special 'Dot Entry' items, i.e. the pseudo nodes created in most 113 | * directories that hold the plain files. 114 | **/ 115 | bool worksForDotEntry() const { return _worksForDotEntry; } 116 | 117 | /** 118 | * Return whether or not this cleanup action works for simple local 119 | * files and directories only ('file:/' protocol) or network 120 | * transparent, i.e. all protocols KDE supports ('ftp', 'smb' - but 121 | * even 'tar', even though it is - strictly spoken - local). 122 | **/ 123 | bool worksLocalOnly() const { return _worksLocalOnly; } 124 | 125 | /** 126 | * Return whether or not the cleanup action should be performed 127 | * recursively in subdirectories of the initial KFileInfo. 128 | **/ 129 | bool recurse() const { return _recurse; } 130 | 131 | /** 132 | * Return whether or not this cleanup should ask the user for 133 | * confirmation when it is executed. 134 | * 135 | * The default is 'false'. Use with caution - not only can this become 136 | * very annoying, people also tend to automatically click on 'OK' when 137 | * too many confirmation dialogs pop up! 138 | **/ 139 | bool askForConfirmation() const { return _askForConfirmation; } 140 | 141 | /** 142 | * Return the refresh policy of this cleanup action - i.e. the action 143 | * to perform after each call to KCleanup::execute(). This is supposed 144 | * to bring the corresponding KDirTree back into sync after the cleanup 145 | * action - the underlying file tree might have changed due to that 146 | * cleanup action. 147 | * 148 | * noRefresh: Don't refresh anything. Assume nothing has changed. 149 | * This is the default. 150 | * 151 | * refreshThis: Refresh the KDirTree from the item on that was passed 152 | * to KCleanup::execute(). 153 | * 154 | * refreshParent: Refresh the KDirTree from the parent of the item on 155 | * that was passed to KCleanup::execute(). If there is no such parent, 156 | * refresh the entire tree. 157 | * 158 | * assumeDeleted: Do not actually refresh the KDirTree. Instead, 159 | * blindly assume the cleanup action has deleted the item that was 160 | * passed to KCleanup::execute() and delete the corresponding subtree 161 | * in the KDirTree accordingly. This will work well for most deleting 162 | * actions as long as they can be performed without problems. If there 163 | * are any problems, however, the KDirTree might easily run out of sync 164 | * with the directory tree: The KDirTree will show the subtree as 165 | * deleted (i.e. it will not show it any more), but it still exists on 166 | * disk. This is the tradeoff to a very quick response. On the other 167 | * hand, the user can easily at any time hit one of the explicit 168 | * refresh buttons and everything will be back into sync again. 169 | **/ 170 | enum RefreshPolicy refreshPolicy() const { return _refreshPolicy; } 171 | 172 | void setTitle(const QString &title); 173 | void setId(const QString &id) { _id = id; } 174 | void setCommand(const QString &command) { _command = command; } 175 | void setEnabled(bool enabled) { _enabled = enabled; } 176 | void setWorksForDir(bool canDo) { _worksForDir = canDo; } 177 | void setWorksForFile(bool canDo) { _worksForFile = canDo; } 178 | void setWorksForDotEntry(bool canDo) { _worksForDotEntry = canDo; } 179 | void setWorksLocalOnly(bool canDo) { _worksLocalOnly = canDo; } 180 | void setRecurse(bool recurse) { _recurse = recurse; } 181 | void setAskForConfirmation(bool ask) { _askForConfirmation = ask; } 182 | void setRefreshPolicy(enum RefreshPolicy refreshPolicy) { 183 | _refreshPolicy = refreshPolicy; 184 | } 185 | 186 | /** 187 | * The heart of the matter: Perform the cleanup with the KFileInfo 188 | * specified. 189 | **/ 190 | virtual void execute(KDirTree *); 191 | 192 | bool isEnabledFromSelection(KDirTree *); 193 | 194 | /** 195 | * Read configuration. 196 | **/ 197 | void readConfig(); 198 | 199 | /** 200 | * Save configuration. 201 | **/ 202 | void saveConfig() const; 203 | 204 | protected: 205 | /** 206 | * Recursively perform the cleanup. 207 | **/ 208 | void executeRecursive(KFileInfo *item, KDirTree*); 209 | 210 | /** 211 | * Ask user for confirmation to execute this cleanup action for 212 | * 'item'. Returns 'true' if user accepts, 'false' otherwise. 213 | **/ 214 | bool confirmation(KDirTree *); 215 | 216 | /** 217 | * Retrieve the directory part of a KFileInfo's path. 218 | **/ 219 | const QString itemDir(const KFileInfo *item) const; 220 | 221 | /** 222 | * Expand some variables in string 'unexpanded' to information from 223 | * within 'item'. Multiple expansion is performed as needed, i.e. the 224 | * string may contain more than one variable to expand. The resulting 225 | * string is returned. 226 | * 227 | * %p expands to item->path(), i.e. the item's full path name. 228 | * 229 | * /usr/local/bin for that directory 230 | * /usr/local/bin/doit for a file within it 231 | * 232 | * %n expands to item->name(), i.e. the last component of the pathname. 233 | * The examples above would expand to: 234 | * 235 | * bin 236 | * doit 237 | * 238 | * For commands that are to be executed from within the 'Clean up' 239 | * menu, you might specify something like: 240 | * 241 | * "kfmclient openURL %p" 242 | * "tar czvf %{name}.tgz && rm -rf %{name}" 243 | **/ 244 | QString expandVariables(const KFileInfo *item, 245 | const QString &unexpanded) const; 246 | 247 | /** 248 | * Run a command with 'item' as base to expand variables. 249 | **/ 250 | void runCommand(const KFileInfo *item, const QString &command) const; 251 | 252 | /** 253 | * Internal implementation of the copy constructor and assignment 254 | * operator: Copy all data members from 'src'. 255 | **/ 256 | void copy(const KCleanup &src); 257 | 258 | // 259 | // Data members 260 | // 261 | QString _id; 262 | QString _command; 263 | QString _title; 264 | bool _enabled; 265 | bool _worksForDir; 266 | bool _worksForFile; 267 | bool _worksForDotEntry; 268 | bool _worksLocalOnly; 269 | bool _recurse; 270 | bool _askForConfirmation; 271 | enum RefreshPolicy _refreshPolicy; 272 | }; 273 | } // namespace KDirStat 274 | 275 | -------------------------------------------------------------------------------- /src/kcleanup.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Summary: Support classes for KDirStat 3 | * License: LGPL - See file COPYING.LIB for details. 4 | * Author: Stefan Hundhammer 5 | * Joshua Hodosh 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "kcleanup.h" 18 | #include 19 | 20 | #define VERBOSE_RUN_COMMAND 1 21 | #define SIMULATE_COMMAND 0 22 | 23 | using namespace KDirStat; 24 | 25 | KCleanup::KCleanup(QString id, QString command, QString title) 26 | : _id(id), _command(command), _title(title) { 27 | _enabled = true; 28 | _worksForDir = true; 29 | _worksForFile = false; 30 | _worksForDotEntry = false; 31 | _worksLocalOnly = true; 32 | _recurse = false; 33 | _askForConfirmation = false; 34 | _refreshPolicy = noRefresh; 35 | } 36 | 37 | KCleanup::KCleanup(const KCleanup &src) { copy(src); } 38 | 39 | KCleanup &KCleanup::operator=(const KCleanup &src) { 40 | copy(src); 41 | 42 | return *this; 43 | } 44 | 45 | void KCleanup::copy(const KCleanup &src) { 46 | setTitle(src.title()); 47 | _id = src.id(); 48 | _command = src.command(); 49 | _enabled = src.enabled(); 50 | _worksForDir = src.worksForDir(); 51 | _worksForFile = src.worksForFile(); 52 | _worksForDotEntry = src.worksForDotEntry(); 53 | _worksLocalOnly = src.worksLocalOnly(); 54 | _recurse = src.recurse(); 55 | _askForConfirmation = src.askForConfirmation(); 56 | _refreshPolicy = src.refreshPolicy(); 57 | } 58 | 59 | void KCleanup::setTitle(const QString &title) { _title = title; } 60 | 61 | bool KCleanup::worksFor(KFileInfo *item, KDirTree * tree) const { 62 | if (!_enabled || !item) 63 | return false; 64 | 65 | if (worksLocalOnly() && !tree->isFileProtocol()) 66 | return false; 67 | 68 | if (item->isDotEntry()) 69 | return worksForDotEntry(); 70 | if (item->isDir()) 71 | return worksForDir(); 72 | 73 | return worksForFile(); 74 | } 75 | 76 | bool KCleanup::isEnabledFromSelection(KDirTree* tree) { 77 | if(tree->selection().empty()) 78 | return false; 79 | for(auto it = tree->selection().begin(); it != tree->selection().end(); ++it) { 80 | KFileInfo * selection = *it; 81 | if(!worksFor(selection, tree)) 82 | return false; 83 | 84 | if (!selection->isFinished()) { 85 | // This subtree isn't finished reading yet 86 | 87 | switch (_refreshPolicy) { 88 | // Refresh policies that would cause this subtree to be deleted 89 | case refreshThis: 90 | case refreshParent: 91 | case assumeDeleted: 92 | 93 | // Prevent premature deletion of this tree - this would 94 | // cause a core dump for sure. 95 | return false; 96 | break; 97 | 98 | default: 99 | break; 100 | } 101 | } 102 | } 103 | return true; 104 | } 105 | 106 | bool KCleanup::confirmation(KDirTree * tree) { 107 | QString msg = cleanTitle(); 108 | 109 | if(tree->selection().size() == 1) { 110 | KFileInfo * item = tree->selection()[0]; 111 | if (item->isDir() || item->isDotEntry()) { 112 | msg = i18n("%1\nin directory %2", cleanTitle(), item->url()); 113 | } else { 114 | msg = i18n("%1\nfor file %2", cleanTitle(), item->url()); 115 | } 116 | } 117 | 118 | if (KMessageBox::warningContinueCancel( 119 | 0, // parentWidget 120 | msg, // message 121 | i18n("Please Confirm"), // caption 122 | KGuiItem(i18n("Confirm")) // confirmButtonLabel 123 | ) == KMessageBox::Continue) 124 | return true; 125 | else 126 | return false; 127 | } 128 | 129 | void KCleanup::execute(KDirTree * tree) { 130 | if (_askForConfirmation && !confirmation(tree)) 131 | return; 132 | 133 | std::vector selection = tree->selection(); 134 | for(auto it = selection.begin(); it != selection.end(); ++it) { 135 | KFileInfo * item = *it; 136 | executeRecursive(item, tree); 137 | 138 | switch (_refreshPolicy) { 139 | case noRefresh: 140 | // Do nothing. 141 | break; 142 | 143 | case refreshThis: 144 | tree->refresh(item); 145 | break; 146 | 147 | case refreshParent: 148 | tree->refresh(item->parent()); 149 | break; 150 | 151 | case assumeDeleted: 152 | 153 | // Assume the cleanup action has deleted the item. 154 | // Modify the KDirTree accordingly. 155 | 156 | tree->deleteSubtree(item); 157 | 158 | // Don't try to figure out a reasonable next selection - the 159 | // views have to do that while handling the subtree 160 | // deletion. Only the views have any knowledge about a 161 | // reasonable strategy for choosing a next selection. Unlike 162 | // the view items, the KFileInfo items don't have an order that 163 | // makes any sense to the user. 164 | 165 | break; 166 | } 167 | } 168 | } 169 | 170 | void KCleanup::executeRecursive(KFileInfo *item, KDirTree* tree) { 171 | if (worksFor(item, tree)) { 172 | if (_recurse) { 173 | // Recurse into all subdirectories. 174 | for(size_t i = 0; i < item->numChildren(); i++) { 175 | KFileInfo *subdir = item->child(i); 176 | if (subdir->isDir()) { 177 | /** 178 | * Recursively execute in this subdirectory, but only if it 179 | * really is a directory: File children might have been 180 | * reparented to the directory (normally, they reside in 181 | * the dot entry) if there are no real subdirectories on 182 | * this directory level. 183 | **/ 184 | executeRecursive(subdir, tree); 185 | } 186 | } 187 | } 188 | 189 | // Perform cleanup for this directory. 190 | 191 | runCommand(item, _command); 192 | } 193 | } 194 | 195 | const QString KCleanup::itemDir(const KFileInfo *item) const { 196 | QString dir = item->url(); 197 | 198 | if (!item->isDir() && !item->isDotEntry()) { 199 | dir.replace(QRegExp("/[^/]*$"), ""); 200 | } 201 | 202 | return dir; 203 | } 204 | 205 | QString KCleanup::cleanTitle() const { 206 | // Use the cleanup action's title, if possible. 207 | 208 | QString title = _title; 209 | 210 | if (title.isEmpty()) { 211 | title = _id; 212 | } 213 | 214 | // Get rid of any "&" characters in the text that denote keyboard 215 | // shortcuts in menus. 216 | title.replace(QRegExp("&"), ""); 217 | 218 | return title; 219 | } 220 | 221 | QString KCleanup::expandVariables(const KFileInfo *item, 222 | const QString &unexpanded) const { 223 | QString expanded = unexpanded; 224 | QString url = item->url().replace("'", "'\\''"); 225 | expanded.replace(QRegExp("%p"), "'" + url + "'"); 226 | QString name = item->name().replace("'", "'\\''"); 227 | expanded.replace(QRegExp("%n"), "'" + name + "'"); 228 | 229 | // if ( KDE::versionMajor() >= 3 && KDE::versionMinor() >= 4 ) 230 | expanded.replace(QRegExp("%t"), "trash:/"); 231 | // else 232 | // expanded.replace( QRegExp( "%t" ), KGlobalSettings::trashPath() ); 233 | 234 | return expanded; 235 | } 236 | 237 | #include 238 | void KCleanup::runCommand(const KFileInfo *item, const QString &command) const { 239 | QProcess *proc = new QProcess(); 240 | QStringList args; 241 | args << "-c" << expandVariables(item, command); 242 | 243 | #if !SIMULATE_COMMAND 244 | switch (_refreshPolicy) { 245 | case noRefresh: 246 | case assumeDeleted: 247 | 248 | // In either case it is no use waiting for the command to 249 | // finish, so we are starting the command as a pure 250 | // background process. 251 | 252 | proc->start("sh", args); 253 | break; 254 | 255 | case refreshThis: 256 | case refreshParent: 257 | 258 | // If a display refresh is due after the command, we need to 259 | // wait for the command to be finished in order to avoid 260 | // performing the update prematurely, so we are starting this 261 | // process in blocking mode. 262 | 263 | QApplication::setOverrideCursor(Qt::WaitCursor); 264 | proc->start("sh", args); 265 | proc->waitForFinished(); 266 | QApplication::restoreOverrideCursor(); 267 | break; 268 | } 269 | 270 | #endif 271 | } 272 | 273 | void KCleanup::readConfig() { 274 | KConfigGroup config = KSharedConfig::openConfig()->group(_id); 275 | 276 | bool valid = config.readEntry("valid", false); 277 | 278 | // If the config section requested exists, it should contain a 279 | // "valid" field with a true value. If not, there is no such 280 | // section within the config file. In this case, just leave this 281 | // cleanup action undisturbed - we'd rather have a good default 282 | // value (as provided - hopefully - by our application upon 283 | // startup) than a generic empty cleanup action. 284 | 285 | if (valid) { 286 | _command = config.readEntry("command"); 287 | _enabled = config.readEntry("enabled", false); 288 | _worksForDir = config.readEntry("worksForDir", true); 289 | _worksForFile = config.readEntry("worksForFile", true); 290 | _worksForDotEntry = config.readEntry("worksForDotEntry", true); 291 | _worksLocalOnly = config.readEntry("worksLocalOnly", true); 292 | _recurse = config.readEntry("recurse", false); 293 | _askForConfirmation = config.readEntry("askForConfirmation", false); 294 | _refreshPolicy = 295 | (KCleanup::RefreshPolicy)config.readEntry("refreshPolicy", 0); 296 | setTitle(config.readEntry("title")); 297 | } 298 | } 299 | 300 | void KCleanup::saveConfig() const { 301 | KConfigGroup config = KSharedConfig::openConfig()->group(_id); 302 | 303 | config.writeEntry("valid", true); 304 | config.writeEntry("command", _command); 305 | config.writeEntry("title", _title); 306 | config.writeEntry("enabled", _enabled); 307 | config.writeEntry("worksForDir", _worksForDir); 308 | config.writeEntry("worksForFile", _worksForFile); 309 | config.writeEntry("worksForDotEntry", _worksForDotEntry); 310 | config.writeEntry("worksLocalOnly", _worksLocalOnly); 311 | config.writeEntry("recurse", _recurse); 312 | config.writeEntry("askForConfirmation", _askForConfirmation); 313 | config.writeEntry("refreshPolicy", (int)_refreshPolicy); 314 | } 315 | 316 | -------------------------------------------------------------------------------- /src/kdirinfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include "kdirinfo.h" 8 | #include "kdirtree.h" 9 | #include 10 | 11 | using namespace KDirStat; 12 | 13 | KDirInfo::KDirInfo(KDirInfo *parent, bool asDotEntry) 14 | : KFileInfo(parent) { 15 | init(); 16 | 17 | if (asDotEntry) { 18 | _isDotEntry = true; 19 | _dotEntry = 0; 20 | _name = "."; 21 | } else { 22 | _isDotEntry = false; 23 | _dotEntry = new KDirInfo(this, true); 24 | } 25 | } 26 | 27 | KDirInfo::KDirInfo(const QString &filenameWithoutPath, struct stat *statInfo, 28 | KDirInfo *parent) 29 | : KFileInfo(filenameWithoutPath, statInfo, parent) { 30 | init(); 31 | _dotEntry = new KDirInfo(this, true); 32 | } 33 | 34 | KDirInfo::KDirInfo(const KFileItem *fileItem, KDirInfo *parent) 35 | : KFileInfo(fileItem, parent) { 36 | init(); 37 | _dotEntry = new KDirInfo(this, true); 38 | } 39 | 40 | KDirInfo::KDirInfo(KDirInfo *parent, 41 | const QString &filenameWithoutPath, mode_t mode, 42 | KFileSize size, time_t mtime) 43 | : KFileInfo(parent, filenameWithoutPath, mode, size, mtime) { 44 | init(); 45 | _dotEntry = new KDirInfo(this, true); 46 | } 47 | 48 | void KDirInfo::init() { 49 | _isDotEntry = false; 50 | _pendingReadJobs = 0; 51 | _dotEntry = 0; 52 | _totalSize = _size; 53 | _totalItems = 0; 54 | _totalSubDirs = 0; 55 | _totalFiles = 0; 56 | _latestMtime = _mtime; 57 | _isMountPoint = false; 58 | _isExcluded = false; 59 | _summaryDirty = false; 60 | _beingDestroyed = false; 61 | _readState = KDirQueued; 62 | } 63 | 64 | KDirInfo::~KDirInfo() { 65 | _beingDestroyed = true; 66 | // Recursively delete all children. 67 | for(size_t i = 0; i < numChildren(); i++) 68 | delete child(i); 69 | 70 | // Delete the dot entry. 71 | if (_dotEntry) { 72 | delete _dotEntry; 73 | } 74 | } 75 | 76 | void KDirInfo::recalcOneChild(KFileInfo * child) { 77 | _totalSize += child->totalSize(); 78 | _totalItems += child->totalItems() + 1; 79 | _totalSubDirs += child->totalSubDirs(); 80 | _totalFiles += child->totalFiles(); 81 | 82 | if (child->isDir()) 83 | _totalSubDirs++; 84 | 85 | if (child->isFile()) 86 | _totalFiles++; 87 | 88 | time_t childLatestMtime = child->latestMtime(); 89 | 90 | if (childLatestMtime > _latestMtime) 91 | _latestMtime = childLatestMtime; 92 | } 93 | 94 | void KDirInfo::recalc() { 95 | // qDebug() << Q_FUNC_INFO << this << endl; 96 | 97 | _totalSize = _size; 98 | _totalItems = 0; 99 | _totalSubDirs = 0; 100 | _totalFiles = 0; 101 | _latestMtime = _mtime; 102 | for(size_t i = 0; i < numChildren(); i++) { 103 | recalcOneChild(child(i)); 104 | } 105 | if(dotEntry()) 106 | recalcOneChild(dotEntry()); 107 | _summaryDirty = false; 108 | } 109 | 110 | void KDirInfo::setMountPoint(bool isMountPoint) { 111 | _isMountPoint = isMountPoint; 112 | } 113 | 114 | KFileSize KDirInfo::totalSize() { 115 | if (_readState == KDirOnRequestOnly) 116 | return 0; 117 | 118 | if (_summaryDirty) 119 | recalc(); 120 | 121 | return _totalSize; 122 | } 123 | 124 | int KDirInfo::totalItems() { 125 | if (_summaryDirty) 126 | recalc(); 127 | 128 | return _totalItems; 129 | } 130 | 131 | int KDirInfo::totalSubDirs() { 132 | if (_summaryDirty) 133 | recalc(); 134 | 135 | return _totalSubDirs; 136 | } 137 | 138 | int KDirInfo::totalFiles() { 139 | if (_summaryDirty) 140 | recalc(); 141 | 142 | return _totalFiles; 143 | } 144 | 145 | time_t KDirInfo::latestMtime() { 146 | if (_summaryDirty) 147 | recalc(); 148 | 149 | return _latestMtime; 150 | } 151 | 152 | bool KDirInfo::isFinished() { return !isBusy(); } 153 | 154 | void KDirInfo::setReadState(KDirReadState newReadState) { 155 | // "aborted" has higher priority than "finished" 156 | 157 | if (_readState == KDirAborted && newReadState == KDirFinished) 158 | return; 159 | 160 | _readState = newReadState; 161 | } 162 | 163 | bool KDirInfo::isBusy() { 164 | if (_pendingReadJobs > 0 && _readState != KDirAborted) 165 | return true; 166 | 167 | if (readState() == KDirReading || readState() == KDirQueued) 168 | return true; 169 | 170 | return false; 171 | } 172 | 173 | void KDirInfo::insertChild(KFileInfo *newChild) { 174 | Q_CHECK_PTR(newChild); 175 | 176 | if (newChild->isDir() || _dotEntry == 0 || _isDotEntry) { 177 | /** 178 | * Only directories are stored directly in pure directory nodes - 179 | * unless something went terribly wrong, e.g. there is no dot entry to use. 180 | * If this is a dot entry, store everything it gets directly within it. 181 | * 182 | * In any of those cases, insert the new child in the children list. 183 | * 184 | * We don't bother with this list's order - it's explicitly declared to 185 | * be unordered, so be warned! We simply insert this new child at the 186 | * list head since this operation can be performed in constant time 187 | * without the need for any additional lastChild etc. pointers or - 188 | * even worse - seeking the correct place for insertion first. This is 189 | * none of our business; the corresponding "view" object for this tree 190 | * will take care of such niceties. 191 | **/ 192 | children_.push_back(newChild); 193 | newChild->setParent(this); // make sure the parent pointer is correct 194 | 195 | childAdded(newChild); // update summaries 196 | } else { 197 | /* 198 | * If the child is not a directory, don't store it directly here - use 199 | * this entry's dot entry instead. 200 | */ 201 | _dotEntry->insertChild(newChild); 202 | } 203 | } 204 | 205 | void KDirInfo::childAdded(KFileInfo *newChild) { 206 | if (!_summaryDirty) { 207 | _totalSize += newChild->totalSize(); 208 | _totalItems++; 209 | 210 | if (newChild->isDir()) 211 | _totalSubDirs++; 212 | 213 | if (newChild->isFile()) 214 | _totalFiles++; 215 | 216 | if (newChild->mtime() > _latestMtime) 217 | _latestMtime = newChild->mtime(); 218 | } else { 219 | // NOP 220 | 221 | /* 222 | * Don't bother updating the summary fields if the summary is dirty 223 | * (i.e. outdated) anyway: As soon as anybody wants to know some exact 224 | * value a complete recalculation of the entire subtree will be 225 | * triggered. On the other hand, if nobody wants to know (which is very 226 | * likely) we can save this effort. 227 | */ 228 | } 229 | 230 | if (_parent) 231 | _parent->childAdded(newChild); 232 | } 233 | 234 | void KDirInfo::deletingChild(KFileInfo *deletedChild) { 235 | /** 236 | * When children are deleted, things go downhill: Marking the summary 237 | * fields as dirty (i.e. outdated) is the only thing that can be done here. 238 | * 239 | * The accumulated sizes could be updated (by subtracting this deleted 240 | * child's values from them), but the latest mtime definitely has to be 241 | * recalculated: The child now being deleted might just be the one with the 242 | * latest mtime, and figuring out the second-latest cannot easily be 243 | * done. So we merely mark the summary as dirty and wait until a recalc() 244 | * will be triggered from outside - which might as well never happen when 245 | * nobody wants to know some summary field anyway. 246 | **/ 247 | 248 | _summaryDirty = true; 249 | 250 | if (_parent) 251 | _parent->deletingChild(deletedChild); 252 | 253 | if (!_beingDestroyed && deletedChild->parent() == this) { 254 | /** 255 | * Unlink the child from the children's list - but only if this doesn't 256 | * happen recursively in the destructor of this object: No use 257 | * bothering about the validity of the children's list if this will all 258 | * be history anyway in a moment. 259 | **/ 260 | if (deletedChild->parent() != this) { 261 | qCritical() << deletedChild << " is not a child of " << this 262 | << " - cannot unlink from children list!" << Qt::endl; 263 | return; 264 | } 265 | auto it = std::find(children_.begin(), children_.end(), deletedChild); 266 | if(it == children_.end()) { 267 | qCritical() << "Couldn't unlink " << deletedChild << " from " << this 268 | << " children list" << Qt::endl; 269 | } else { 270 | children_.erase(it); 271 | } 272 | } 273 | } 274 | 275 | void KDirInfo::readJobAdded() { 276 | _pendingReadJobs++; 277 | 278 | if (_parent) 279 | _parent->readJobAdded(); 280 | } 281 | 282 | void KDirInfo::readJobFinished() { 283 | _pendingReadJobs--; 284 | 285 | if (_parent) 286 | _parent->readJobFinished(); 287 | } 288 | 289 | void KDirInfo::readJobAborted() { 290 | _readState = KDirAborted; 291 | 292 | if (_parent) 293 | _parent->readJobAborted(); 294 | } 295 | 296 | void KDirInfo::finalizeLocal() { cleanupDotEntries(); } 297 | 298 | void KDirInfo::finalizeAll(KDirTree* tree) { 299 | if (_isDotEntry) 300 | return; 301 | 302 | for(size_t i = 0; i < numChildren(); i++) { 303 | if(child(i)->isDirInfo()) { 304 | KDirInfo *dir = static_cast(child(i)); 305 | if(!dir->isDotEntry()) 306 | dir->finalizeAll(tree); 307 | } 308 | } 309 | 310 | // Optimization: As long as this directory is not finalized yet, it does 311 | // (very likely) have a dot entry and thus all direct children are 312 | // subdirectories, not plain files, so we don't need to bother checking 313 | // plain file children as well - so do finalizeLocal() only after all 314 | // children are processed. If this step were the first, for directories 315 | // that don't have any subdirectories finalizeLocal() would immediately 316 | // get all their plain file children reparented to themselves, so they 317 | // would need to be processed in the loop, too. 318 | 319 | tree->sendFinalizeLocal(this); // Must be sent _before_ finalizeLocal()! 320 | finalizeLocal(); 321 | } 322 | 323 | KDirReadState KDirInfo::readState() const { 324 | if (_isDotEntry && _parent) 325 | return _parent->readState(); 326 | else 327 | return _readState; 328 | } 329 | 330 | void KDirInfo::cleanupDotEntries() { 331 | if (!_dotEntry || _isDotEntry) { 332 | children_.shrink_to_fit(); 333 | return; 334 | } 335 | 336 | // Reparent dot entry children if there are no subdirectories on this level 337 | 338 | if (numChildren() == 0) { 339 | // qDebug() << "Reparenting children of solo dot entry " << this << endl; 340 | children_ = _dotEntry->children_; 341 | _dotEntry->children_.clear(); 342 | for(size_t i = 0; i < numChildren(); i++) 343 | children_[i]->setParent(this); 344 | } 345 | 346 | // Delete dot entries without any children 347 | 348 | if (_dotEntry->numChildren() == 0) { 349 | // qDebug() << "Removing empty dot entry " << this << endl; 350 | 351 | delete _dotEntry; 352 | _dotEntry = 0; 353 | } 354 | if(_dotEntry) 355 | _dotEntry->cleanupDotEntries(); // just to shrink_to_fit 356 | children_.shrink_to_fit(); 357 | } 358 | -------------------------------------------------------------------------------- /src/ktreemapview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * License: LGPL - See file COPYING.LIB for details. 4 | * Author: Stefan Hundhammer 5 | * Joshua Hodosh 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define MinAmbientLight 0 13 | #define MaxAmbientLight 200 14 | #define DefaultAmbientLight 40 15 | 16 | #define MinHeightScalePercent 10 17 | #define MaxHeightScalePercent 200 18 | #define DefaultHeightScalePercent 100 19 | #define DefaultHeightScaleFactor (DefaultHeightScalePercent / 100.0) 20 | 21 | #define DefaultMinTileSize 3 22 | #define CushionHeight 1.0 23 | 24 | class QMouseEvent; 25 | class KConfig; 26 | 27 | namespace KDirStat { 28 | class KTreemapTile; 29 | class KTreemapSelectionRect; 30 | class KDirTree; 31 | class KFileInfo; 32 | 33 | class KTreemapView : public QGraphicsView { 34 | Q_OBJECT 35 | 36 | public: 37 | /** 38 | * Constructor. 39 | **/ 40 | KTreemapView(KDirTree *tree, QWidget *parent = 0, 41 | const QSize &initialSize = QSize()); 42 | 43 | /** 44 | * Destructor. 45 | **/ 46 | virtual ~KTreemapView(); 47 | 48 | /** 49 | * Returns the (topmost) treemap tile at the specified position 50 | * or 0 if there is none. 51 | **/ 52 | KTreemapTile *tileAt(QPoint pos); 53 | 54 | /** 55 | * Returns the minimum recommended size for this widget. 56 | * Reimplemented from QWidget. 57 | **/ 58 | QSize minimumSizeHint() const override { return QSize(0, 0); } 59 | 60 | /** 61 | * Returns this treemap view's currently selected treemap tile or 0 if 62 | * there is none. 63 | **/ 64 | KTreemapTile *selectedTile() const { return _selectedTile; } 65 | 66 | /** 67 | * Returns this treemap view's root treemap tile or 0 if there is none. 68 | **/ 69 | KTreemapTile *rootTile() const { return _rootTile; } 70 | 71 | /** 72 | * Returns this treemap view's @ref KDirTree. 73 | **/ 74 | KDirTree *tree() const { return _tree; } 75 | 76 | /** 77 | * Search the treemap for a tile that corresponds to the specified 78 | * KFileInfo node. Returns 0 if there is none. 79 | * 80 | * Notice: This is an expensive operation since all treemap tiles need 81 | * to be searched. 82 | **/ 83 | KTreemapTile *findTile(KFileInfo *node); 84 | 85 | /** 86 | * Returns a suitable color for 'file' based on a set of internal rules 87 | * (according to filename extension, MIME type or permissions). 88 | **/ 89 | QColor tileColor(KFileInfo *file); 90 | 91 | public slots: 92 | 93 | /** 94 | * Make a treemap tile this treemap's selected tile. 95 | * 'tile' may be 0. In this case, only the previous selection is 96 | * deselected. 97 | **/ 98 | void selectTile(KTreemapTile *tile, bool emitEvent = true); 99 | 100 | /** 101 | * Update the map view selection from the KDirTree selection 102 | **/ 103 | void updateSelection(KDirTree *); 104 | 105 | /** 106 | * Zoom in one level towards the currently selected treemap tile: 107 | * The entire treemap will be rebuilt with the near-topmost ancestor of 108 | * the selected tile as the new root. 109 | **/ 110 | void zoomIn(); 111 | 112 | /** 113 | * Zoom out one level: The parent (if there is any) KFileInfo node of 114 | * the current treemap root becomes the new root. This usually works 115 | * only after zoomIn(). 116 | **/ 117 | void zoomOut(); 118 | 119 | /** 120 | * Select the parent of the currently selected tile (if possible). 121 | * 122 | * This is very much the same as clicking with the middle mouse button, 123 | * but not quite: The middle mouse button cycles back to the tile 124 | * clicked at if there is no more parent. This method does not (because 125 | * there is no known mouse position). 126 | **/ 127 | void selectParent(); 128 | 129 | /** 130 | * Completely rebuild the entire treemap from the internal tree's root 131 | * on. 132 | **/ 133 | void rebuildTreemap(); 134 | 135 | /** 136 | * Clear the treemap contents. 137 | **/ 138 | void clear(); 139 | 140 | /** 141 | * Notification that a dir tree node has been deleted. 142 | **/ 143 | void deleteNotify(KFileInfo *node); 144 | 145 | /** 146 | * Read some parameters from the global @ref KConfig object. 147 | **/ 148 | void readConfig(); 149 | 150 | public: 151 | /** 152 | * Returns true if it is possible to zoom in with the currently 153 | * selected tile, false if not. 154 | **/ 155 | bool canZoomIn() const; 156 | 157 | /** 158 | * Returns true if it is possible to zoom out with the currently 159 | * selected tile, false if not. 160 | **/ 161 | bool canZoomOut() const; 162 | 163 | /** 164 | * Returns true if it is possible to select the parent of the currently 165 | * selected tile, false if not. 166 | **/ 167 | bool canSelectParent() const; 168 | 169 | /** 170 | * Returns 'true' if the treemap is automatically resized to fit into 171 | * the available space, 'false' if not. 172 | **/ 173 | bool autoResize() const { return _autoResize; } 174 | 175 | /** 176 | * Returns 'true' if treemap tiles are to be squarified upon creation, 177 | * 'false' if not. 178 | **/ 179 | bool squarify() const { return _squarify; } 180 | 181 | /** 182 | * Returns 'true' if cushion shading is to be used, 'false' if not. 183 | **/ 184 | bool doCushionShading() const { return _doCushionShading; } 185 | 186 | /** 187 | * Returns 'true' if cushion shaded treemap tiles are to be separated 188 | * by a grid, 'false' if not. 189 | **/ 190 | bool forceCushionGrid() const { return _forceCushionGrid; } 191 | 192 | /** 193 | * Returns 'true' if tile boundary lines should be drawn for cushion 194 | * treemaps, 'false' if not. 195 | **/ 196 | bool ensureContrast() const { return _ensureContrast; } 197 | 198 | /** 199 | * Returns the minimum tile size in pixels. No treemap tiles less than 200 | * this in width or height are desired. 201 | **/ 202 | int minTileSize() const { return _minTileSize; } 203 | 204 | /** 205 | * Returns the cushion grid color. 206 | **/ 207 | const QColor &cushionGridColor() const { return _cushionGridColor; } 208 | 209 | /** 210 | * Returns the outline color to use if cushion shading is not used. 211 | **/ 212 | const QColor &outlineColor() const { return _outlineColor; } 213 | 214 | /** 215 | * Returns the fill color for non-directory treemap tiles when cushion 216 | * shading is not used. 217 | **/ 218 | const QColor &fileFillColor() const { return _fileFillColor; } 219 | 220 | /** 221 | * Returns the fill color for directory (or "dotentry") treemap tiles 222 | * when cushion shading is not used. 223 | **/ 224 | const QColor &dirFillColor() const { return _dirFillColor; } 225 | 226 | /** 227 | * Returns the intensity of ambient light for cushion shading 228 | * [0..255] 229 | **/ 230 | int ambientLight() const { return _ambientLight; } 231 | 232 | /** 233 | * Returns the X coordinate of a directed light source for cushion 234 | * shading. 235 | **/ 236 | 237 | double lightX() const { return _lightX; } 238 | 239 | /** 240 | * Returns the Y coordinate of a directed light source for cushion 241 | * shading. 242 | **/ 243 | double lightY() const { return _lightY; } 244 | 245 | /** 246 | * Returns the Z coordinate of a directed light source for cushion 247 | * shading. 248 | **/ 249 | double lightZ() const { return _lightZ; } 250 | 251 | /** 252 | * Returns cushion ridge height degradation factor (0 .. 1.0) for each 253 | * level of subdivision. 254 | **/ 255 | double heightScaleFactor() const { return _heightScaleFactor; } 256 | 257 | signals: 258 | /** 259 | * Emitted when the treemap changes, e.g. is rebuilt, zoomed in, or 260 | * zoomed out. 261 | **/ 262 | void treemapChanged(); 263 | 264 | /** 265 | * Emitted when a context menu for this tile should be opened. 266 | * (usually on right click). 'pos' contains the click's mouse 267 | * coordinates. 268 | **/ 269 | void contextMenu(KTreemapTile *tile, const QPoint &pos); 270 | 271 | /** 272 | * Emitted at user activity. Some interactive actions are assigned an 273 | * amount of "activity points" that can be used to judge whether or not 274 | * the user is actually using this program or if it's just idly sitting 275 | * around on the desktop. This is intended for use together with a @ref 276 | * KActivityTracker. 277 | **/ 278 | void userActivity(int points); 279 | 280 | protected: 281 | /** 282 | * Rebuild the treemap with 'newRoot' as the new root and the specified 283 | * size. If 'newSize' is (0, 0), visibleSize() is used. 284 | **/ 285 | void rebuildTreemap(KFileInfo *newRoot); 286 | 287 | /** 288 | * Catch mouse click - emits a selectionChanged() signal. 289 | **/ 290 | void mousePressEvent(QMouseEvent *event) override; 291 | 292 | /** 293 | * Catch mouse double click: 294 | * Left button double-click zooms in, 295 | * right button double-click zooms out, 296 | * middle button double-click rebuilds treemap. 297 | **/ 298 | virtual void contentsMouseDoubleClickEvent(QMouseEvent *event); 299 | 300 | /** 301 | * Resize the treemap view. Suppress the treemap contents if the size 302 | * falls below a minimum size, redisplay it if it grows above that 303 | * minimum size. 304 | * 305 | * Reimplemented from QFrame. 306 | **/ 307 | void resizeEvent(QResizeEvent *event) override; 308 | 309 | /** 310 | * Convenience method to read a color from 'config'. 311 | **/ 312 | QColor readColorEntry(KConfigGroup *config, const char *entryName, 313 | QColor defaultColor); 314 | 315 | // Data members 316 | 317 | KDirTree *_tree; 318 | KTreemapTile *_rootTile; 319 | KTreemapTile *_selectedTile; 320 | KTreemapSelectionRect *_selectionRect; 321 | QString _savedRootUrl; 322 | 323 | bool _autoResize; 324 | bool _squarify; 325 | bool _doCushionShading; 326 | bool _forceCushionGrid; 327 | bool _ensureContrast; 328 | int _minTileSize; 329 | 330 | QColor _highlightColor; 331 | QColor _cushionGridColor; 332 | QColor _outlineColor; 333 | QColor _fileFillColor; 334 | QColor _dirFillColor; 335 | 336 | int _ambientLight; 337 | 338 | double _lightX; 339 | double _lightY; 340 | double _lightZ; 341 | 342 | double _heightScaleFactor; 343 | QTimer _refreshTimer; 344 | }; // class KTreemapView 345 | 346 | /** 347 | * Transparent rectangle to make a treemap tile clearly visible as 348 | * "selected". Leaf tiles could do that on their own, but higher-level 349 | * tiles (corresponding to directories) are obscured for the most part, so 350 | * only a small portion (if any) of their highlighted outline could be 351 | * visible. This selection rectangle simply draws a two-pixel red outline 352 | * on top (i.e., great z-height) of everything else. The rectangle is 353 | * transparent, so the treemap tile contents remain visible. 354 | **/ 355 | class KTreemapSelectionRect : public QGraphicsRectItem { 356 | public: 357 | /** 358 | * Constructor. 359 | **/ 360 | KTreemapSelectionRect(const QColor &color); 361 | 362 | /** 363 | * Highlight the specified treemap tile: Resize this selection 364 | * rectangle to match this tile and move it to this tile's 365 | * position. Show the selection rectangle if it is currently 366 | * invisible. 367 | **/ 368 | void highlight(KTreemapTile *tile); 369 | 370 | }; // class KTreemapSelectionRect 371 | 372 | } // namespace KDirStat 373 | 374 | -------------------------------------------------------------------------------- /src/kdirtree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include "kdirinfo.h" 10 | #include "kdirreadjob.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef NOT_USED 17 | #define NOT_USED(PARAM) ((void)(PARAM)) 18 | #endif 19 | 20 | // Open a new name space since KDE's name space is pretty much cluttered 21 | // already - all names that would even remotely match are already used up, 22 | // yet the resprective classes don't quite fit the purposes required here. 23 | 24 | namespace KDirStat { 25 | // Forward declarations 26 | class KDirReadJob; 27 | 28 | /** 29 | * Directory read methods. 30 | **/ 31 | typedef enum { 32 | KDirReadUnknown, // Unknown (yet) 33 | KDirReadLocal, // Use opendir() and lstat() 34 | KDirReadKIO // Use KDE's KIO network transparent methods 35 | } KDirReadMethod; 36 | 37 | /** 38 | * This class provides some infrastructure as well as global data for a 39 | * directory tree. It acts as the glue that holds things together: The root 40 | * item from which to descend into the subtree, the read queue and some 41 | * global policies (like whether or not to cross file systems while reading 42 | * directories). 43 | * 44 | * @short Directory tree global data and infrastructure 45 | **/ 46 | class KDirTree : public QObject { 47 | Q_OBJECT 48 | void selectionInSubTree(KFileInfo *); 49 | public: 50 | /** 51 | * Constructor. 52 | * 53 | * Remember to call @ref startReading() after the constructor and 54 | * setting up connections. 55 | **/ 56 | KDirTree(); 57 | 58 | /** 59 | * Destructor. 60 | **/ 61 | virtual ~KDirTree(); 62 | 63 | public slots: 64 | 65 | /** 66 | * Actually start reading. 67 | * 68 | * It's not very pretty this is required as an extra method, but this 69 | * cannot simply be done in the constructor: We need to give the caller 70 | * a chance to set up Qt signal connections, and for this the 71 | * constructor must return before any signals are sent, i.e. before 72 | * anything is read. 73 | **/ 74 | void startReading(const QUrl &url); 75 | 76 | /** 77 | * Forcefully stop a running read process. 78 | **/ 79 | void abortReading(); 80 | 81 | /** 82 | * Refresh a subtree, i.e. read its contents from disk again. 83 | * 84 | * The old subtree will be deleted and rebuilt from scratch, i.e. all 85 | * pointers to elements within this subtree will become invalid (a 86 | * @ref subtreeDeleted() signal will be emitted to notify about that 87 | * fact). 88 | * 89 | * When 0 is passed, the entire tree will be refreshed, i.e. from the 90 | * root element on. 91 | **/ 92 | void refresh(KFileInfo *subtree = 0); 93 | 94 | /** 95 | * Select some other item in this tree. Triggers the @ref 96 | * selectionChanged() signal - even to the sender of this signal, 97 | * i.e. take care not to cause endless signal ping-pong! 98 | * 99 | * Select nothing if '0' is passed. 100 | **/ 101 | void selectItems(const std::vector & 102 | newSelection = std::vector()); 103 | 104 | /** 105 | * Delete a subtree. 106 | **/ 107 | void deleteSubtree(KFileInfo *subtree); 108 | 109 | public: 110 | /** 111 | * Returns the root item of this tree. 112 | * 113 | * Currently, there can only be one single root item for each tree. 114 | */ 115 | KFileInfo *root() const { return _root; } 116 | 117 | /** 118 | * Sets the root item of this tree. 119 | **/ 120 | void setRoot(KFileInfo *newRoot); 121 | 122 | /** 123 | * Clear all items of this tree. 124 | * 125 | * 'sendSignals' indicates whether or not to send deletingChild() and 126 | *childDeleted() signals. A selectionChanged() signal will be sent in any case 127 | *if there was a selected item. 128 | **/ 129 | void clear(bool sendSignals = false); 130 | 131 | /** 132 | * Locate a child somewhere in the tree whose URL (i.e. complete path) 133 | * matches the URL passed. Returns 0 if there is no such child. 134 | * 135 | * Notice: This is a very expensive operation since the entire tree is 136 | * searched recursively. 137 | * 138 | * 'findDotEntries' specifies if locating "dot entries" (".../") 139 | * is desired. 140 | * 141 | * This is just a convenience method that maps to 142 | * KDirTree::root()->locate( url, findDotEntries ) 143 | **/ 144 | KFileInfo *locate(QString url, bool findDotEntries = false) { 145 | return _root ? _root->locate(url, findDotEntries) : 0; 146 | } 147 | 148 | #if 0 149 | /** 150 | * Notification of a finished directory read job. 151 | * All read jobs are required to call this upon (successful or 152 | * unsuccessful) completion. 153 | **/ 154 | void jobFinishedNotify( KDirReadJob *job ); 155 | #endif 156 | 157 | /** 158 | * Add a new directory read job to the queue. 159 | **/ 160 | void addJob(KDirReadJob *job); 161 | 162 | /** 163 | * Obtain the directory read method for this tree: 164 | * KDirReadLocal use opendir() and lstat() 165 | * KDirReadKDirLister use KDE 2.x's KDirLister 166 | **/ 167 | KDirReadMethod readMethod() const { return _readMethod; } 168 | 169 | /** 170 | * Should directory scans cross file systems? 171 | * 172 | * Notice: This can only be avoided with local directories where the 173 | * device number a file resides on can be obtained. 174 | * Remember, that's what this KDirStat business is all about. ;-) 175 | **/ 176 | bool crossFileSystems() const { return _crossFileSystems; } 177 | 178 | /** 179 | * Set or unset the "cross file systems" flag. 180 | **/ 181 | void setCrossFileSystems(bool doCross) { _crossFileSystems = doCross; } 182 | 183 | /** 184 | * Return the tree's current selection. 185 | * 186 | * Even though the KDirTree by itself doesn't have a visual 187 | * representation, it supports the concept of selection. Views can use 188 | * this to transparently keep track of the selection, notifying the 189 | * KDirTree and thus other views with @ref KDirTree::selectItem(). 190 | * Attached views should connect to the @ref 191 | * selectionChanged() signal to be notified when the selection changes. 192 | **/ 193 | const std::vector & selection() const { return _selection; } 194 | 195 | /** 196 | * Notification that a child has been added. 197 | * 198 | * Directory read jobs are required to call this for each child added 199 | * so the tree can emit the corresponding @ref childAdded() signal. 200 | **/ 201 | virtual void childAddedNotify(KFileInfo *newChild); 202 | 203 | /** 204 | * Notification that a child is about to be deleted. 205 | * 206 | * Directory read jobs are required to call this for each deleted child 207 | * so the tree can emit the corresponding @ref deletingChild() signal. 208 | **/ 209 | virtual void deletingChildNotify(KFileInfo *deletedChild); 210 | 211 | /** 212 | * Notification that one or more children have been deleted. 213 | * 214 | * Directory read jobs are required to call this when one or more 215 | * children are deleted so the tree can emit the corresponding @ref 216 | * deletingChild() signal. For multiple deletions (e.g. entire 217 | * subtrees) this should only happen once at the end. 218 | **/ 219 | virtual void childDeletedNotify(); 220 | 221 | /** 222 | * Send a @ref progressInfo() signal to keep the user entertained while 223 | * directories are being read. 224 | **/ 225 | void sendProgressInfo(const QString &infoLine); 226 | 227 | /** 228 | * Send a @ref finalizeLocal() signal to give views a chance to 229 | * finalize the display of this directory level - e.g. clean up dot 230 | * entries, set the final "expandable" state etc. 231 | **/ 232 | void sendFinalizeLocal(KDirInfo *dir); 233 | 234 | /** 235 | * Send a @ref startingReading() signal. 236 | **/ 237 | void sendStartingReading(); 238 | 239 | /** 240 | * Send a @ref finished() signal. 241 | **/ 242 | void sendFinished(); 243 | 244 | /** 245 | * Send a @ref aborted() signal. 246 | **/ 247 | void sendAborted(); 248 | 249 | /** 250 | * Returns 'true' if this tree uses the 'file:/' protocol (regardless 251 | * of local or network transparent directory reader). 252 | **/ 253 | bool isFileProtocol() { return _isFileProtocol; } 254 | 255 | /** 256 | * Returns 'true' if directory reading is in progress in this tree. 257 | **/ 258 | bool isBusy() { return _isBusy; } 259 | 260 | /** 261 | * Write the complete tree to a cache file. 262 | * 263 | * Returns true if OK, false upon error. 264 | **/ 265 | bool writeCache(const QString &cacheFileName); 266 | 267 | /** 268 | * Read a cache file. 269 | **/ 270 | void readCache(const QString &cacheFileName); 271 | 272 | signals: 273 | 274 | /** 275 | * Emitted when a child has been added. 276 | **/ 277 | void childAdded(KFileInfo *newChild); 278 | 279 | /** 280 | * Emitted when a child is about to be deleted. 281 | **/ 282 | void deletingChild(KFileInfo *deletedChild); 283 | 284 | /** 285 | * Emitted after a child is deleted. If you are interested which child 286 | * it was, better use the @ref deletingChild() signal. 287 | * @ref childDeleted() is only useful to rebuild a view etc. completely. 288 | * If possible, this signal is sent only once for multiple deletions - 289 | * e.g., when entire subtrees are deleted. 290 | **/ 291 | void childDeleted(); 292 | 293 | /** 294 | * Emitted when reading is started. 295 | **/ 296 | void startingReading(); 297 | 298 | /** 299 | * Emitted when reading this directory tree is finished. 300 | **/ 301 | void finished(); 302 | 303 | /** 304 | * Emitted when reading this directory tree has been aborted. 305 | **/ 306 | void aborted(); 307 | 308 | /** 309 | * Emitted when reading a directory is finished. 310 | * This does _not_ mean reading all subdirectories is finished, too - 311 | * only this directory level is complete! 312 | * 313 | * WARNING: 'dir' may be 0 if the the tree's root could not be read. 314 | * 315 | * Use this signal to do similar cleanups like 316 | * @ref KDirInfo::finalizeLocal(), e.g. cleaning up unused / undesired 317 | * dot entries like in @ref KDirInfo::cleanupDotEntries(). 318 | **/ 319 | void finalizeLocal(KDirInfo *dir); 320 | 321 | /** 322 | * Emitted when the current selection has changed, i.e. whenever some 323 | * attached view triggers the @ref selectItem() slot or when the 324 | * current selection is deleted. 325 | * 326 | * NOTE: 'newSelection' may be 0 if nothing is selected. 327 | **/ 328 | void selectionChanged(KDirTree*); 329 | 330 | /** 331 | * Single line progress information, emitted when the read status 332 | * changes - typically when a new directory is being read. Connect to a 333 | * status bar etc. to keep the user entertained. 334 | **/ 335 | void progressInfo(const QString &infoLine); 336 | 337 | protected slots: 338 | 339 | /** 340 | * Read some parameters from the global @ref KConfig object. 341 | **/ 342 | void readConfig(); 343 | 344 | /** 345 | * Notification that all jobs in the job queue are finished. 346 | * This will emit the finished() signal. 347 | **/ 348 | void slotFinished(); 349 | 350 | protected: 351 | KFileInfo *_root; 352 | std::vector _selection; 353 | KDirReadJobQueue _jobQueue; 354 | KDirReadMethod _readMethod; 355 | bool _crossFileSystems; 356 | bool _enableLocalDirReader; 357 | bool _isFileProtocol; 358 | bool _isBusy; 359 | 360 | }; // class KDirTree 361 | 362 | } // namespace KDirStat 363 | 364 | -------------------------------------------------------------------------------- /src/kdirreadjob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * License: LGPL - See file COPYING.LIB for details. 5 | * Author: Stefan Hundhammer 6 | * Joshua Hodosh 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifndef NOT_USED 15 | #define NOT_USED(PARAM) ((void)(PARAM)) 16 | #endif 17 | 18 | // Open a new name space since KDE's name space is pretty much cluttered 19 | // already - all names that would even remotely match are already used up, 20 | // yet the resprective classes don't quite fit the purposes required here. 21 | 22 | namespace KDirStat { 23 | // Forward declarations 24 | class KFileInfo; 25 | class KDirInfo; 26 | class KDirTree; 27 | class KCacheReader; 28 | class KDirReadJobQueue; 29 | 30 | /** 31 | * A directory read job that can be queued. This is mainly to prevent 32 | * buffer thrashing because of too many directories opened at the same time 33 | * because of simultaneous reads or even system resource consumption 34 | * (directory handles in this case). 35 | * 36 | * Objects of this kind are transient by nature: They live only as long as 37 | * the job is queued or executed. When it is done, the data is contained in 38 | * the corresponding @ref KDirInfo subtree of the corresponding @ref 39 | * KDirTree. 40 | * 41 | * For each entry automatically a @ref KFileInfo or @ref KDirInfo will be 42 | * created and added to the parent @ref KDirInfo. For each directory a new 43 | * @ref KDirReadJob will be created and added to the @ref KDirTree 's job 44 | * queue. 45 | * 46 | * Notice: This class contains pure virtuals - you cannot use it 47 | * directly. Derive your own class from it or use one of 48 | * @ref KLocalDirReadJob or @ref KioDirReadJob. 49 | * 50 | * @short Abstract base class for directory reading. 51 | **/ 52 | class KDirReadJob { 53 | public: 54 | /** 55 | * Constructor. 56 | * 57 | * This does not read anything yet. Call read() for that. 58 | **/ 59 | KDirReadJob(KDirTree *tree, KDirInfo *dir = 0); 60 | 61 | /** 62 | * Destructor. 63 | **/ 64 | virtual ~KDirReadJob(); 65 | 66 | /** 67 | * Read the next couple of items from the directory. 68 | * Call finished() when there is nothing more to read. 69 | * 70 | * Derived classes should overwrite this method or startReading(). 71 | * This default implementation calls startReading() if it has not been 72 | * called yet. 73 | **/ 74 | virtual void read(); 75 | 76 | /** 77 | * Returns the corresponding @ref KDirInfo item. 78 | * Caution: This may be 0. 79 | **/ 80 | virtual KDirInfo *dir() { return _dir; } 81 | 82 | /** 83 | * Set the corresponding @ref KDirInfo item. 84 | **/ 85 | virtual void setDir(KDirInfo *dir); 86 | 87 | /** 88 | * Return the job queue this job is in or 0 if it isn't queued. 89 | **/ 90 | KDirReadJobQueue *queue() const { return _queue; } 91 | 92 | /** 93 | * Set the job queue this job is in. 94 | **/ 95 | void setQueue(KDirReadJobQueue *queue) { _queue = queue; } 96 | 97 | protected: 98 | /** 99 | * Initialize reading. 100 | * 101 | * Derived classes should overwrite this method or read(). 102 | **/ 103 | virtual void startReading() {} 104 | 105 | /** 106 | * Notification that a new child has been added. 107 | * 108 | * Derived classes are required to call this whenever a new child is 109 | * added so this notification can be passed up to the @ref KDirTree 110 | * which in turn emits a corresponding signal. 111 | **/ 112 | void childAdded(KFileInfo *newChild); 113 | 114 | /** 115 | * Notification that a child is about to be deleted. 116 | * 117 | * Derived classes are required to call this just before a child is 118 | * deleted so this notification can be passed up to the @ref KDirTree 119 | * which in turn emits a corresponding signal. 120 | * 121 | * Derived classes are not required to handle child deletion at all, 122 | * but if they do, calling this method is required. 123 | **/ 124 | void deletingChild(KFileInfo *deletedChild); 125 | 126 | /** 127 | * Send job finished notification to the associated tree. 128 | * This will delete this job. 129 | **/ 130 | void finished(); 131 | 132 | KDirTree *_tree; 133 | KDirInfo *_dir; 134 | KDirReadJobQueue *_queue; 135 | bool _started; 136 | 137 | }; // class KDirReadJob 138 | 139 | /** 140 | * Wrapper class between KDirReadJob and QObject 141 | **/ 142 | class KObjDirReadJob : public QObject, public KDirReadJob { 143 | Q_OBJECT 144 | 145 | public: 146 | KObjDirReadJob(KDirTree *tree, KDirInfo *dir = 0) 147 | : QObject(), KDirReadJob(tree, dir){}; 148 | virtual ~KObjDirReadJob() {} 149 | 150 | protected slots: 151 | 152 | void slotChildAdded(KFileInfo *child) { childAdded(child); } 153 | void slotDeletingChild(KFileInfo *child) { deletingChild(child); } 154 | void slotFinished() { finished(); } 155 | 156 | }; // KObjDirReadJob 157 | 158 | /** 159 | * Impementation of the abstract @ref KDirReadJob class that reads a local 160 | * directory. 161 | * 162 | * This will use lstat() system calls rather than KDE's network transparent 163 | * directory services since lstat() unlike the KDE services can obtain 164 | * information about the device (i.e. file system) a file or directory 165 | * resides on. This is important if you wish to limit directory scans to 166 | * one file system - which is most desirable when that one file system runs 167 | * out of space. 168 | * 169 | * @short Directory reader that reads one local directory. 170 | **/ 171 | class KLocalDirReadJob : public KDirReadJob { 172 | public: 173 | /** 174 | * Constructor. 175 | **/ 176 | KLocalDirReadJob(KDirTree *tree, KDirInfo *dir); 177 | 178 | /** 179 | * Destructor. 180 | **/ 181 | virtual ~KLocalDirReadJob(); 182 | 183 | /** 184 | * Obtain information about the URL specified and create a new @ref 185 | * KFileInfo or a @ref KDirInfo (whatever is appropriate) from that 186 | * information. Use @ref KFileInfo::isDirInfo() to find out which. 187 | * Returns 0 if such information cannot be obtained (i.e. the 188 | * appropriate stat() call fails). 189 | **/ 190 | static KFileInfo *stat(const QUrl &url, KDirInfo *parent = nullptr); 191 | 192 | protected: 193 | /** 194 | * Read the directory. Prior to this nothing happens. 195 | * 196 | * Inherited and reimplemented from @ref KDirReadJob. 197 | **/ 198 | void startReading() override; 199 | 200 | DIR *_diskDir; 201 | 202 | }; // KLocalDirReadJob 203 | 204 | /** 205 | * Generic impementation of the abstract @ref KDirReadJob class, using 206 | * KDE's network transparent KIO methods. 207 | * 208 | * This is much more generic than @ref KLocalDirReadJob since it supports 209 | * protocols like 'ftp', 'http', 'smb', 'tar' etc., too. Its only drawback 210 | * is that is cannot be prevented from crossing file system boundaries - 211 | * which makes it pretty useless for figuring out the cause of a 'file 212 | * system full' error. 213 | * 214 | * @short Generic directory reader that reads one directory, remote or local. 215 | **/ 216 | class KioDirReadJob : public KObjDirReadJob { 217 | Q_OBJECT 218 | 219 | public: 220 | /** 221 | * Constructor. 222 | **/ 223 | KioDirReadJob(KDirTree *tree, KDirInfo *dir); 224 | 225 | /** 226 | * Destructor. 227 | **/ 228 | virtual ~KioDirReadJob(); 229 | 230 | /** 231 | * Obtain information about the URL specified and create a new @ref 232 | * KFileInfo or a @ref KDirInfo (whatever is appropriate) from that 233 | * information. Use @ref KFileInfo::isDirInfo() to find out which. 234 | * Returns 0 if such information cannot be obtained (i.e. the 235 | * appropriate stat() call fails). 236 | **/ 237 | static KFileInfo *stat(const QUrl &url, KDirInfo *parent = 0); 238 | 239 | /** 240 | * Obtain the owner of the URL specified. 241 | * 242 | * This is a moderately expensive operation since it involves a network 243 | * transparent stat() call. 244 | **/ 245 | static QString owner(QUrl url); 246 | 247 | protected slots: 248 | /** 249 | * Receive directory entries from a KIO job. 250 | **/ 251 | void entries(KIO::Job *job, const KIO::UDSEntryList &entryList); 252 | 253 | /** 254 | * KIO job is finished. 255 | **/ 256 | void finished(KIO::Job *job); 257 | 258 | protected: 259 | /** 260 | * Start reading the directory. Prior to this nothing happens. 261 | * 262 | * Inherited and reimplemented from @ref KDirReadJob. 263 | **/ 264 | void startReading() override; 265 | 266 | KIO::ListJob *_job; 267 | 268 | }; // KioDirReadJob 269 | 270 | class KCacheReadJob : public KObjDirReadJob { 271 | Q_OBJECT 272 | 273 | public: 274 | /** 275 | * Constructor for a cache reader that is already open. 276 | * 277 | * The KCacheReadJob takes over ownership of the KCacheReader. In 278 | * particular, the KCacheReader will be destroyed with 'delete' when 279 | * the read job is done. 280 | * 281 | * If 'parent' is 0, the content of the cache file will replace all 282 | * current tree items. 283 | **/ 284 | KCacheReadJob(KDirTree *tree, KDirInfo *parent, KCacheReader *reader); 285 | 286 | /** 287 | * Constructor that uses a cache file that is not open yet. 288 | * 289 | * If 'parent' is 0, the content of the cache file will replace all 290 | * current tree items. 291 | **/ 292 | KCacheReadJob(KDirTree *tree, KDirInfo *parent, const QString &cacheFileName); 293 | 294 | /** 295 | * Destructor. 296 | **/ 297 | virtual ~KCacheReadJob(); 298 | 299 | /** 300 | * Start reading the cache. Prior to this nothing happens. 301 | * 302 | * Inherited and reimplemented from @ref KDirReadJob. 303 | **/ 304 | void read() override; 305 | 306 | /** 307 | * Return the associated cache reader. 308 | **/ 309 | KCacheReader *reader() const { return _reader; } 310 | 311 | protected: 312 | /** 313 | * Initializations common for all constructors. 314 | **/ 315 | void init(); 316 | 317 | KCacheReader *_reader; 318 | 319 | }; // class KCacheReadJob 320 | 321 | /** 322 | * Queue for read jobs 323 | * 324 | * Handles time-sliced reading automatically. 325 | **/ 326 | class KDirReadJobQueue : public QObject { 327 | Q_OBJECT 328 | 329 | public: 330 | /** 331 | * Constructor. 332 | **/ 333 | KDirReadJobQueue(); 334 | 335 | /** 336 | * Destructor. 337 | **/ 338 | virtual ~KDirReadJobQueue(); 339 | 340 | /** 341 | * Add a job to the end of the queue. Begin time-sliced reading if not 342 | * in progress yet. 343 | **/ 344 | void enqueue(KDirReadJob *job); 345 | 346 | /** 347 | * Remove the head of the queue and return it. 348 | **/ 349 | KDirReadJob *dequeue(); 350 | 351 | /** 352 | * Get the head of the queue (the next job that is due for processing). 353 | **/ 354 | KDirReadJob *head() const { return _queue.first(); } 355 | 356 | /** 357 | * Count the number of pending jobs in the queue. 358 | **/ 359 | int count() const { return _queue.count(); } 360 | 361 | /** 362 | * Check if the queue is empty. 363 | **/ 364 | bool isEmpty() const { return _queue.isEmpty(); } 365 | 366 | /** 367 | * Clear the queue: Remove all pending jobs from the queue and destroy them. 368 | **/ 369 | void clear(); 370 | 371 | /** 372 | * Abort all jobs in the queue. 373 | **/ 374 | void abort(); 375 | 376 | /** 377 | * Delete all jobs for a subtree. 378 | **/ 379 | void killAll(KDirInfo *subtree); 380 | 381 | /** 382 | * Notification that a job is finished. 383 | * This takes that job out of the queue and deletes it. 384 | * Read jobs are required to call this when they are finished. 385 | **/ 386 | void jobFinishedNotify(KDirReadJob *job); 387 | 388 | signals: 389 | 390 | /** 391 | * Emitted when job reading starts, i.e. when a new job is inserted 392 | * into a queue that was empty 393 | **/ 394 | void startingReading(); 395 | 396 | /** 397 | * Emitted when reading is finished, i.e. when the last read job of the 398 | * queue is finished. 399 | **/ 400 | void finished(); 401 | 402 | protected slots: 403 | 404 | /** 405 | * Time-sliced work procedure to be performed while the application is 406 | * in the main loop: Read some directory entries, but relinquish 407 | * control back to the application so it can maintain some 408 | * responsiveness. This method uses a timer of minimal duration to 409 | * activate itself as soon as there are no more user events to 410 | * process. Call this only once directly after inserting a read job 411 | * into the job queue. 412 | **/ 413 | void timeSlicedRead(); 414 | 415 | protected: 416 | QList _queue; 417 | QTimer _timer; 418 | }; 419 | 420 | } // namespace KDirStat 421 | 422 | -------------------------------------------------------------------------------- /src/kdirtreecache.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Summary: KDirStat cache reader / writer 3 | * License: LGPL - See file COPYING.LIB for details. 4 | * Author: Stefan Hundhammer 5 | * Joshua Hodosh 6 | */ 7 | 8 | #include "kdirtree.h" 9 | #include "kdirtreecache.h" 10 | #include "kexcluderules.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define KB 1024 18 | #define MB (1024 * 1024) 19 | #define GB (1024 * 1024 * 1024) 20 | 21 | using namespace KDirStat; 22 | 23 | KCacheWriter::KCacheWriter(const QString &fileName, KDirTree *tree) { 24 | _ok = writeCache(fileName, tree); 25 | } 26 | 27 | KCacheWriter::~KCacheWriter() { 28 | // NOP 29 | } 30 | 31 | bool KCacheWriter::writeCache(const QString &fileName, KDirTree *tree) { 32 | if (!tree || !tree->root()) 33 | return false; 34 | 35 | gzFile cache = gzopen(fileName.toLocal8Bit(), "w"); 36 | 37 | if (cache == 0) { 38 | qCritical() << "Can't open " << fileName << ": " << strerror(errno) << Qt::endl; 39 | return false; 40 | } 41 | 42 | // FIXME !!! XXX 43 | const char *version = "4.0"; 44 | 45 | gzprintf(cache, "[kdirstat %s cache file]\n", version); 46 | gzprintf(cache, "# Do not edit!\n" 47 | "#\n" 48 | "# Type\tpath\t\tsize\tmtime\t\t\n" 49 | "\n"); 50 | 51 | writeTree(cache, tree->root()); 52 | gzclose(cache); 53 | 54 | return true; 55 | } 56 | 57 | void KCacheWriter::writeTree(gzFile cache, KFileInfo *item) { 58 | if (!item) 59 | return; 60 | 61 | // Write entry for this item 62 | if (!item->isDotEntry()) 63 | writeItem(cache, item); 64 | 65 | // Write file children 66 | if (item->dotEntry()) 67 | writeTree(cache, item->dotEntry()); 68 | 69 | // Recurse through subdirectories 70 | for(size_t i = 0; i < item->numChildren(); i++) 71 | writeTree(cache, item->child(i)); 72 | } 73 | 74 | void KCacheWriter::writeItem(gzFile cache, KFileInfo *item) { 75 | if (!item) 76 | return; 77 | 78 | // Write file type 79 | 80 | const char *file_type = ""; 81 | if (item->isFile()) 82 | file_type = "F"; 83 | else if (item->isDir()) 84 | file_type = "D"; 85 | else if (item->isSymLink()) 86 | file_type = "L"; 87 | else if (item->isBlockDevice()) 88 | file_type = "BlockDev"; 89 | else if (item->isCharDevice()) 90 | file_type = "CharDev"; 91 | else if (item->isFifo()) 92 | file_type = "FIFO"; 93 | else if (item->isSocket()) 94 | file_type = "Socket"; 95 | 96 | gzprintf(cache, "%s", file_type); 97 | 98 | // Write name 99 | 100 | if (item->isDirInfo() && !item->isDotEntry()) { 101 | // Use absolute path 102 | 103 | gzprintf(cache, " %s", 104 | QUrl::toPercentEncoding(item->url(), "/").constData()); 105 | } else { 106 | // Use relative path 107 | 108 | gzprintf(cache, "\t%s", QUrl::toPercentEncoding(item->name()).constData()); 109 | } 110 | 111 | // Write size 112 | 113 | gzprintf(cache, "\t%s", formatSize(item->byteSize()).toLocal8Bit().constData()); 114 | 115 | // Write mtime 116 | 117 | gzprintf(cache, "\t0x%lx", (unsigned long)item->mtime()); 118 | 119 | // Optional fields 120 | 121 | gzprintf(cache, "\tblocks: %lld", item->blocks()); 122 | 123 | if (item->isFile() && item->links() > 1) 124 | gzprintf(cache, "\tlinks: %u", (unsigned)item->links()); 125 | 126 | gzputc(cache, '\n'); 127 | } 128 | 129 | QString KCacheWriter::formatSize(KFileSize size) { 130 | if (size >= GB && size % GB == 0) { 131 | return QString("%1G").arg(size / GB); 132 | } 133 | 134 | if (size >= MB && size % MB == 0) { 135 | return QString("%1M").arg(size / MB); 136 | } 137 | 138 | if (size >= KB && size % KB == 0) { 139 | return QString("%1K").arg(size / KB); 140 | } 141 | 142 | return QString::number(size); 143 | } 144 | 145 | KCacheReader::KCacheReader(const QString &fileName, KDirTree *tree, 146 | KDirInfo *parent) 147 | : QObject() { 148 | _fileName = fileName; 149 | _buffer[0] = 0; 150 | _line = _buffer; 151 | _lineNo = 0; 152 | _ok = true; 153 | _tree = tree; 154 | _toplevel = parent; 155 | _lastDir = 0; 156 | _lastExcludedDir = 0; 157 | 158 | _cache = gzopen(fileName.toLocal8Bit(), "r"); 159 | 160 | if (_cache == 0) { 161 | qCritical() << "Can't open " << fileName << ": " << strerror(errno); 162 | _ok = false; 163 | emit error(); 164 | return; 165 | } 166 | 167 | // qDebug() << "Opening " << fileName << " OK" << endl; 168 | checkHeader(); 169 | } 170 | 171 | static void setStateRecursive(KDirInfo * root) { 172 | // Once the whole cache file is read set the state of each KDirInfo to 173 | // finished so the tree view can start fetching the tree 174 | root->setReadState(KDirFinished); 175 | for(size_t i = 0; i < root->numChildren(); i++) { 176 | KFileInfo * f = root->child(i); 177 | if(f->isDirInfo()) { 178 | setStateRecursive(static_cast(f)); 179 | } 180 | } 181 | } 182 | 183 | KCacheReader::~KCacheReader() { 184 | setStateRecursive(_toplevel); 185 | if (_cache) 186 | gzclose(_cache); 187 | 188 | // qDebug() << "Cache reading finished" << endl; 189 | 190 | if (_toplevel) 191 | _toplevel->finalizeAll(_tree); 192 | 193 | emit finished(); 194 | } 195 | 196 | void KCacheReader::rewind() { 197 | if (_cache) { 198 | gzrewind(_cache); 199 | checkHeader(); // skip cache header 200 | } 201 | } 202 | 203 | bool KCacheReader::read(int maxLines) { 204 | while (!gzeof(_cache) && _ok && (maxLines == 0 || --maxLines > 0)) { 205 | if (readLine()) { 206 | splitLine(); 207 | addItem(); 208 | } 209 | } 210 | 211 | return _ok && !gzeof(_cache); 212 | } 213 | 214 | void KCacheReader::addItem() { 215 | if (fieldsCount() < 4) { 216 | _ok = false; 217 | emit error(); 218 | return; 219 | } 220 | 221 | int n = 0; 222 | char *type = field(n++); 223 | char *raw_path = field(n++); 224 | char *size_str = field(n++); 225 | char *mtime_str = field(n++); 226 | char *blocks_str = 0; 227 | char *links_str = 0; 228 | 229 | while (fieldsCount() > n + 1) { 230 | char *keyword = field(n++); 231 | char *val_str = field(n++); 232 | 233 | if (strcasecmp(keyword, "blocks:") == 0) 234 | blocks_str = val_str; 235 | if (strcasecmp(keyword, "links:") == 0) 236 | links_str = val_str; 237 | } 238 | 239 | // Type 240 | 241 | mode_t mode = S_IFREG; 242 | 243 | if (strcasecmp(type, "F") == 0) 244 | mode = S_IFREG; 245 | else if (strcasecmp(type, "D") == 0) 246 | mode = S_IFDIR; 247 | else if (strcasecmp(type, "L") == 0) 248 | mode = S_IFLNK; 249 | else if (strcasecmp(type, "BlockDev") == 0) 250 | mode = S_IFBLK; 251 | else if (strcasecmp(type, "CharDev") == 0) 252 | mode = S_IFCHR; 253 | else if (strcasecmp(type, "FIFO") == 0) 254 | mode = S_IFIFO; 255 | else if (strcasecmp(type, "Socket") == 0) 256 | mode = S_IFSOCK; 257 | 258 | // Path 259 | 260 | if (*raw_path == '/') 261 | _lastDir = 0; 262 | 263 | // Size 264 | 265 | char *end = 0; 266 | KFileSize size = strtoll(size_str, &end, 10); 267 | 268 | if (end) { 269 | switch (*end) { 270 | case 'K': 271 | size *= KB; 272 | break; 273 | case 'M': 274 | size *= MB; 275 | break; 276 | case 'G': 277 | size *= GB; 278 | break; 279 | default: 280 | break; 281 | } 282 | } 283 | 284 | // MTime 285 | 286 | time_t mtime = strtol(mtime_str, 0, 0); 287 | 288 | // Blocks 289 | 290 | KFileSize blocks = blocks_str ? strtoll(blocks_str, 0, 10) : -1; 291 | 292 | // Links 293 | 294 | int links = links_str ? atoi(links_str) : 1; 295 | 296 | // 297 | // Create a new item 298 | QFileInfo fileInfo(QUrl::fromPercentEncoding(raw_path)); 299 | QString path, name; 300 | if (_tree->root()) { 301 | path = fileInfo.dir().path(); 302 | name = fileInfo.fileName(); 303 | } else { 304 | path = fileInfo.filePath(); 305 | name = path; 306 | } 307 | 308 | if (_lastExcludedDir) { 309 | if (path.startsWith(_lastExcludedDirUrl)) { 310 | // qDebug() << "Excluding " << path << "/" << name << endl; 311 | return; 312 | } 313 | } 314 | 315 | // Find parent in tree 316 | 317 | KDirInfo *parent = _lastDir; 318 | 319 | if (!parent && _tree->root()) { 320 | // Try the easy way first - the starting point of this cache 321 | 322 | if (_toplevel) 323 | parent = dynamic_cast(_toplevel->locate(path)); 324 | 325 | // Fallback: Search the entire tree 326 | 327 | if (!parent) 328 | parent = dynamic_cast(_tree->locate(path)); 329 | 330 | if (!parent) // Still nothing? 331 | { 332 | #if 0 333 | qCritical() << _fileName << ":" << _lineNo << ": " 334 | << "Could not locate parent " << path << endl; 335 | #endif 336 | 337 | return; // Ignore this cache line completely 338 | } 339 | } 340 | 341 | if (strcasecmp(type, "D") == 0) { 342 | // qDebug() << "Creating KDirInfo for " << name << endl; 343 | KDirInfo *dir = new KDirInfo(parent, name, mode, size, mtime); 344 | dir->setReadState(KDirCached); 345 | _lastDir = dir; 346 | 347 | if (parent) 348 | parent->insertChild(dir); 349 | 350 | if (!_tree->root()) { 351 | _tree->setRoot(dir); 352 | _toplevel = dir; 353 | } 354 | 355 | if (!_toplevel) 356 | _toplevel = dir; 357 | 358 | _tree->childAddedNotify(dir); 359 | 360 | if (dir != _toplevel) { 361 | if (KExcludeRules::excludeRules()->match(dir->url())) { 362 | // qDebug() << "Excluding " << name << endl; 363 | dir->setExcluded(); 364 | dir->setReadState(KDirOnRequestOnly); 365 | _tree->sendFinalizeLocal(dir); 366 | dir->finalizeLocal(); 367 | 368 | _lastExcludedDir = dir; 369 | _lastExcludedDirUrl = _lastExcludedDir->url(); 370 | _lastDir = 0; 371 | } 372 | } 373 | } else { 374 | if (parent) { 375 | // qDebug() << "Creating KFileInfo for " << parent->debugUrl() << "/" << 376 | // name << endl; 377 | 378 | KFileInfo *item = 379 | new KFileInfo(parent, name, mode, size, mtime, blocks, links); 380 | parent->insertChild(item); 381 | _tree->childAddedNotify(item); 382 | } else { 383 | qCritical() << _fileName << ":" << _lineNo << ": " 384 | << "No parent for item " << name << Qt::endl; 385 | } 386 | } 387 | } 388 | 389 | bool KCacheReader::eof() { 390 | if (!_ok || !_cache) 391 | return true; 392 | 393 | return gzeof(_cache); 394 | } 395 | 396 | QString KCacheReader::firstDir() { 397 | while (!gzeof(_cache) && _ok) { 398 | if (!readLine()) 399 | return ""; 400 | 401 | splitLine(); 402 | 403 | if (fieldsCount() < 2) 404 | return ""; 405 | 406 | int n = 0; 407 | char *type = field(n++); 408 | char *path = field(n++); 409 | 410 | if (strcasecmp(type, "D") == 0) 411 | return QString(path); 412 | } 413 | 414 | return ""; 415 | } 416 | 417 | bool KCacheReader::checkHeader() { 418 | if (!_ok || !readLine()) 419 | return false; 420 | 421 | // qDebug() << "Checking cache file header" << endl; 422 | QString line(_line); 423 | splitLine(); 424 | 425 | // Check for [kdirstat cache file] 426 | 427 | if (fieldsCount() != 4) 428 | _ok = false; 429 | 430 | if (_ok) { 431 | if (strcmp(field(0), "[kdirstat") != 0 || strcmp(field(2), "cache") != 0 || 432 | strcmp(field(3), "file]") != 0) { 433 | _ok = false; 434 | qCritical() << _fileName << ":" << _lineNo << ": Unknown file format" 435 | << Qt::endl; 436 | } 437 | } 438 | 439 | if (_ok) { 440 | QString version = field(1); 441 | 442 | // currently not checking version number 443 | // for future use 444 | 445 | if (!_ok) 446 | qCritical() << _fileName << ":" << _lineNo 447 | << ": Incompatible cache file version" << Qt::endl; 448 | } 449 | 450 | // qDebug() << "Cache file header check OK: " << _ok << endl; 451 | 452 | if (!_ok) 453 | emit error(); 454 | 455 | return _ok; 456 | } 457 | 458 | bool KCacheReader::readLine() { 459 | if (!_ok || !_cache) 460 | return false; 461 | 462 | _fieldsCount = 0; 463 | 464 | do { 465 | _lineNo++; 466 | 467 | if (!gzgets(_cache, _buffer, MAX_CACHE_LINE_LEN - 1)) { 468 | _buffer[0] = 0; 469 | _line = _buffer; 470 | 471 | if (!gzeof(_cache)) { 472 | _ok = false; 473 | qCritical() << _fileName << ":" << _lineNo << ": Read error" << Qt::endl; 474 | emit error(); 475 | } 476 | 477 | return false; 478 | } 479 | 480 | _line = skipWhiteSpace(_buffer); 481 | killTrailingWhiteSpace(_line); 482 | 483 | // qDebug() << "line[ " << _lineNo << "]: \"" << _line<< "\"" << endl; 484 | 485 | } while (!gzeof(_cache) && (*_line == 0 || // empty line 486 | *_line == '#')); // comment line 487 | 488 | return true; 489 | } 490 | 491 | void KCacheReader::splitLine() { 492 | _fieldsCount = 0; 493 | 494 | if (!_ok || !_line) 495 | return; 496 | 497 | if (*_line == '#') // skip comment lines 498 | *_line = 0; 499 | 500 | char *current = _line; 501 | char *end = _line + strlen(_line); 502 | 503 | while (current && current < end && *current && 504 | _fieldsCount < MAX_FIELDS_PER_LINE - 1) { 505 | _fields[_fieldsCount++] = current; 506 | current = findNextWhiteSpace(current); 507 | 508 | if (current && current < end) { 509 | *current++ = 0; 510 | current = skipWhiteSpace(current); 511 | } 512 | } 513 | } 514 | 515 | char *KCacheReader::field(int no) { 516 | if (no >= 0 && no < _fieldsCount) 517 | return _fields[no]; 518 | else 519 | return 0; 520 | } 521 | 522 | char *KCacheReader::skipWhiteSpace(char *cptr) { 523 | if (cptr == 0) 524 | return 0; 525 | 526 | while (*cptr != 0 && isspace(*cptr)) 527 | cptr++; 528 | 529 | return cptr; 530 | } 531 | 532 | char *KCacheReader::findNextWhiteSpace(char *cptr) { 533 | if (cptr == 0) 534 | return 0; 535 | 536 | while (*cptr != 0 && !isspace(*cptr)) 537 | cptr++; 538 | 539 | return *cptr == 0 ? 0 : cptr; 540 | } 541 | 542 | void KCacheReader::killTrailingWhiteSpace(char *cptr) { 543 | char *start = cptr; 544 | 545 | if (cptr == 0) 546 | return; 547 | 548 | cptr = start + strlen(start) - 1; 549 | 550 | while (cptr >= start && isspace(*cptr)) 551 | *cptr-- = 0; 552 | } 553 | 554 | -------------------------------------------------------------------------------- /src/kdirreadjob.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * License: LGPL - See file COPYING.LIB for details. 3 | * Author: Stefan Hundhammer 4 | * Joshua Hodosh 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "k4dirstat.h" 16 | #include "kdirreadjob.h" 17 | #include "kdirtree.h" 18 | #include "kdirtreecache.h" 19 | #include "kexcluderules.h" 20 | #include 21 | 22 | using namespace KDirStat; 23 | 24 | KDirReadJob::KDirReadJob(KDirTree *tree, KDirInfo *dir) 25 | : _tree(tree), _dir(dir) { 26 | _queue = 0; 27 | _started = false; 28 | 29 | if (_dir) 30 | _dir->readJobAdded(); 31 | } 32 | 33 | KDirReadJob::~KDirReadJob() { 34 | if (_dir) 35 | _dir->readJobFinished(); 36 | } 37 | 38 | /** 39 | * Default implementation - derived classes should overwrite this method or 40 | * startReading() (or both). 41 | **/ 42 | 43 | void KDirReadJob::read() { 44 | if (!_started) { 45 | _started = true; 46 | startReading(); 47 | 48 | // Don't do anything after startReading() - startReading() might call 49 | // finished() which in turn makes the queue destroy this object 50 | } 51 | } 52 | 53 | void KDirReadJob::setDir(KDirInfo *dir) { _dir = dir; } 54 | 55 | void KDirReadJob::finished() { 56 | if (_queue) 57 | _queue->jobFinishedNotify(this); 58 | else 59 | qCritical() << "No job queue for " << _dir << Qt::endl; 60 | } 61 | 62 | void KDirReadJob::childAdded(KFileInfo *newChild) { 63 | _tree->childAddedNotify(newChild); 64 | } 65 | 66 | void KDirReadJob::deletingChild(KFileInfo *deletedChild) { 67 | _tree->deletingChildNotify(deletedChild); 68 | } 69 | 70 | KLocalDirReadJob::KLocalDirReadJob(KDirTree *tree, KDirInfo *dir) 71 | : KDirReadJob(tree, dir), _diskDir(0) {} 72 | 73 | KLocalDirReadJob::~KLocalDirReadJob() {} 74 | 75 | void KLocalDirReadJob::startReading() { 76 | struct dirent *entry; 77 | struct stat statInfo; 78 | QString dirName = _dir->url(); 79 | 80 | if ((_diskDir = opendir(dirName.toLocal8Bit()))) { 81 | _tree->sendProgressInfo(dirName); 82 | _dir->setReadState(KDirReading); 83 | 84 | while ((entry = readdir(_diskDir))) { 85 | QString entryName = entry->d_name; 86 | 87 | if (entryName != "." && entryName != "..") { 88 | QString fullName = dirName + "/" + entryName; 89 | QByteArray rawFullName = dirName.toLocal8Bit() + 90 | QDir::separator().toLatin1() + 91 | QByteArray(entry->d_name); 92 | if (lstat(rawFullName.data(), &statInfo) == 0) // lstat() OK 93 | { 94 | if (S_ISDIR(statInfo.st_mode)) // directory child? 95 | { 96 | KDirInfo *subDir = new KDirInfo(entryName, &statInfo, _dir); 97 | 98 | if (KExcludeRules::excludeRules()->match(fullName)) { 99 | subDir->setExcluded(); 100 | subDir->setReadState(KDirOnRequestOnly); 101 | _tree->sendFinalizeLocal(subDir); 102 | subDir->finalizeLocal(); 103 | } else // No exclude rule matched 104 | { 105 | if (_dir->device() == subDir->device()) // normal case 106 | { 107 | _tree->addJob(new KLocalDirReadJob(_tree, subDir)); 108 | } else // The subdirectory we just found is a mount point. 109 | { 110 | // qDebug() << "Found mount point " << subDir << endl; 111 | subDir->setMountPoint(); 112 | 113 | if (_tree->crossFileSystems()) { 114 | _tree->addJob(new KLocalDirReadJob(_tree, subDir)); 115 | } else { 116 | subDir->setReadState(KDirOnRequestOnly); 117 | _tree->sendFinalizeLocal(subDir); 118 | subDir->finalizeLocal(); 119 | } 120 | } 121 | } 122 | 123 | _dir->insertChild(subDir); 124 | childAdded(subDir); 125 | } else // non-directory child 126 | { 127 | if (entryName == DEFAULT_CACHE_NAME) // .kdirstat.cache.gz found? 128 | { 129 | // 130 | // Read content of this subdirectory from cache file 131 | // 132 | 133 | KCacheReadJob *cacheReadJob = 134 | new KCacheReadJob(_tree, _dir->parent(), fullName); 135 | Q_CHECK_PTR(cacheReadJob); 136 | QString firstDirInCache = cacheReadJob->reader()->firstDir(); 137 | 138 | if (firstDirInCache == 139 | dirName) // Does this cache file match this directory? 140 | { 141 | qDebug() << "Using cache file " << fullName << " for " 142 | << dirName << Qt::endl; 143 | 144 | cacheReadJob->reader() 145 | ->rewind(); // Read offset was moved by firstDir() 146 | _tree->addJob(cacheReadJob); // Job queue will assume ownership 147 | // of cacheReadJob 148 | 149 | // 150 | // Clean up partially read directory content 151 | // 152 | 153 | KDirTree *tree = _tree; // Copy data members to local variables: 154 | KDirInfo *dir = 155 | _dir; // This object will be deleted soon by killAll() 156 | 157 | _queue->killAll(dir); // Will delete this job as well! 158 | // All data members of this object are invalid from here on! 159 | 160 | tree->deleteSubtree(dir); 161 | 162 | return; 163 | } else { 164 | qDebug() << "NOT using cache file " << fullName << " with dir " 165 | << firstDirInCache << " for " << dirName << Qt::endl; 166 | 167 | delete cacheReadJob; 168 | } 169 | } else { 170 | KFileInfo *child = new KFileInfo(entryName, &statInfo, _dir); 171 | _dir->insertChild(child); 172 | childAdded(child); 173 | } 174 | } 175 | } else // lstat() error 176 | { 177 | qWarning() << "lstat(" << fullName << ") failed: " << strerror(errno) 178 | << Qt::endl; 179 | 180 | /* 181 | * Not much we can do when lstat() didn't work; let's at 182 | * least create an (almost empty) entry as a placeholder. 183 | */ 184 | KDirInfo *child = new KDirInfo(_dir); 185 | child->setReadState(KDirError); 186 | _dir->insertChild(child); 187 | childAdded(child); 188 | } 189 | } 190 | } 191 | 192 | closedir(_diskDir); 193 | // qDebug() << "Finished reading " << _dir << endl; 194 | _dir->setReadState(KDirFinished); 195 | _dir->finalizeLocal(); 196 | _tree->sendFinalizeLocal(_dir); 197 | } else { 198 | _dir->setReadState(KDirError); 199 | _dir->finalizeLocal(); 200 | _tree->sendFinalizeLocal(_dir); 201 | // qWarning() << Q_FUNC_INFO << "opendir(" << dirName << ") failed" << endl; 202 | // opendir() doesn't set 'errno' according to POSIX :-( 203 | } 204 | 205 | finished(); 206 | // Don't add anything after finished() since this deletes this job! 207 | } 208 | 209 | KFileInfo *KLocalDirReadJob::stat(const QUrl &url, KDirInfo *parent) { 210 | struct stat statInfo; 211 | 212 | if (lstat(url.path().toLocal8Bit(), &statInfo) == 0) // lstat() OK 213 | { 214 | QString name = parent ? url.fileName() : url.path(); 215 | 216 | if (S_ISDIR(statInfo.st_mode)) // directory? 217 | { 218 | KDirInfo *dir = new KDirInfo(name, &statInfo, parent); 219 | 220 | if (dir && parent && dir->device() != parent->device()) 221 | dir->setMountPoint(); 222 | 223 | return dir; 224 | } else // no directory 225 | return new KFileInfo(name, &statInfo, parent); 226 | } else // lstat() failed 227 | return 0; 228 | } 229 | 230 | KioDirReadJob::KioDirReadJob(KDirTree *tree, KDirInfo *dir) 231 | : KObjDirReadJob(tree, dir) { 232 | _job = 0; 233 | } 234 | 235 | KioDirReadJob::~KioDirReadJob() { 236 | #if 0 237 | if ( _job ) 238 | _job->kill( true ); // quietly 239 | #endif 240 | } 241 | 242 | void KioDirReadJob::startReading() { 243 | QUrl url = QUrl::fromUserInput(_dir->url(), QDir::currentPath(), 244 | QUrl::AssumeLocalFile); 245 | if (!url.isValid()) { 246 | qWarning() << Q_FUNC_INFO << "URL malformed: " << _dir->url() << Qt::endl; 247 | } 248 | 249 | _job = KIO::listDir(url); 250 | 251 | connect(_job, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList &)), this, 252 | SLOT(entries(KIO::Job *, const KIO::UDSEntryList &))); 253 | 254 | connect(_job, SIGNAL(result(KIO::Job *)), this, SLOT(finished(KIO::Job *))); 255 | 256 | connect(_job, SIGNAL(canceled(KIO::Job *)), this, SLOT(finished(KIO::Job *))); 257 | } 258 | 259 | void KioDirReadJob::entries(KIO::Job *job, const KIO::UDSEntryList &entryList) { 260 | NOT_USED(job); 261 | QUrl url(_dir->url()); // Cache this - it's expensive! 262 | 263 | if (!url.isValid()) { 264 | qWarning() << Q_FUNC_INFO << "URL malformed: " << _dir->url() << Qt::endl; 265 | } 266 | 267 | KIO::UDSEntryList::ConstIterator it = entryList.begin(); 268 | 269 | while (it != entryList.end()) { 270 | KFileItem entry(*it, url, 271 | true, // determineMimeTypeOnDemand 272 | true); // URL is parent directory 273 | 274 | if (entry.name() != "." && entry.name() != "..") { 275 | // qDebug() << "Found " << entry.url().url() << endl; 276 | 277 | if (entry.isDir() && // Directory child 278 | !entry.isLink()) // and not a symlink? 279 | { 280 | KDirInfo *subDir = new KDirInfo(&entry, _dir); 281 | _dir->insertChild(subDir); 282 | childAdded(subDir); 283 | 284 | if (KExcludeRules::excludeRules()->match(url.path())) { 285 | subDir->setExcluded(); 286 | subDir->setReadState(KDirOnRequestOnly); 287 | _tree->sendFinalizeLocal(subDir); 288 | subDir->finalizeLocal(); 289 | } else // No exclude rule matched 290 | { 291 | _tree->addJob(new KioDirReadJob(_tree, subDir)); 292 | } 293 | } else // non-directory child 294 | { 295 | KFileInfo *child = new KFileInfo(&entry, _dir); 296 | _dir->insertChild(child); 297 | childAdded(child); 298 | } 299 | } 300 | 301 | ++it; 302 | } 303 | } 304 | 305 | void KioDirReadJob::finished(KIO::Job *job) { 306 | if (job->error()) 307 | _dir->setReadState(KDirError); 308 | else 309 | _dir->setReadState(KDirFinished); 310 | 311 | _tree->sendFinalizeLocal(_dir); 312 | _dir->finalizeLocal(); 313 | _job = 0; // The job deletes itself after this signal! 314 | 315 | KDirReadJob::finished(); 316 | // Don't add anything after finished() since this deletes this job! 317 | } 318 | 319 | KFileInfo *KioDirReadJob::stat(const QUrl &url, KDirInfo *parent) { 320 | KIO::StatJob *job = KIO::stat(url); 321 | if (job->exec()) { 322 | KFileItem entry(job->statResult(), url, 323 | true, // determine MIME type on demand 324 | false); // URL specifies parent directory 325 | 326 | return entry.isDir() ? new KDirInfo(&entry, parent) 327 | : new KFileInfo(&entry, parent); 328 | } else // remote stat() failed 329 | return 0; 330 | } 331 | 332 | QString KioDirReadJob::owner(QUrl url) { 333 | KIO::StatJob *job = KIO::stat(url); 334 | if (job->exec()) { 335 | KFileItem entry(job->statResult(), url, 336 | true, // determine MIME type on demand 337 | false); // URL specifies parent directory 338 | 339 | return entry.user(); 340 | } 341 | 342 | return QString(); 343 | } 344 | 345 | KCacheReadJob::KCacheReadJob(KDirTree *tree, KDirInfo *parent, 346 | KCacheReader *reader) 347 | : KObjDirReadJob(tree, parent), _reader(reader) { 348 | if (_reader) 349 | _reader->rewind(); 350 | 351 | init(); 352 | } 353 | 354 | KCacheReadJob::KCacheReadJob(KDirTree *tree, KDirInfo *parent, 355 | const QString &cacheFileName) 356 | : KObjDirReadJob(tree, parent) { 357 | _reader = new KCacheReader(cacheFileName, tree, parent); 358 | Q_CHECK_PTR(_reader); 359 | 360 | init(); 361 | } 362 | 363 | void KCacheReadJob::init() { 364 | if (_reader) { 365 | if (_reader->ok()) { 366 | connect(_reader, SIGNAL(childAdded(KFileInfo *)), this, 367 | SLOT(slotChildAdded(KFileInfo *))); 368 | } else { 369 | delete _reader; 370 | _reader = 0; 371 | } 372 | } 373 | } 374 | 375 | KCacheReadJob::~KCacheReadJob() { 376 | if (_reader) 377 | delete _reader; 378 | } 379 | 380 | void KCacheReadJob::read() { 381 | /* 382 | * This will be called repeatedly from KDirTree::timeSlicedRead() until 383 | * finished() is called. 384 | */ 385 | 386 | if (!_reader) { 387 | finished(); 388 | return; 389 | } 390 | 391 | // qDebug() << "Reading 1000 cache lines" << endl; 392 | _reader->read(1000); 393 | _tree->sendProgressInfo(""); 394 | 395 | if (_reader->eof() || !_reader->ok()) { 396 | // qDebug() << "Cache reading finished - ok: " << _reader->ok() << endl; 397 | finished(); 398 | } 399 | } 400 | 401 | KDirReadJobQueue::KDirReadJobQueue() : QObject() { 402 | 403 | connect(&_timer, SIGNAL(timeout()), this, SLOT(timeSlicedRead())); 404 | } 405 | 406 | KDirReadJobQueue::~KDirReadJobQueue() { clear(); } 407 | 408 | void KDirReadJobQueue::enqueue(KDirReadJob *job) { 409 | if (job) { 410 | _queue.append(job); 411 | job->setQueue(this); 412 | 413 | if (!_timer.isActive()) { 414 | // qDebug() << "First job queued" << endl; 415 | emit startingReading(); 416 | _timer.start(0); 417 | } 418 | } 419 | } 420 | 421 | KDirReadJob *KDirReadJobQueue::dequeue() { 422 | KDirReadJob *job = _queue.first(); 423 | _queue.removeFirst(); 424 | 425 | if (job) 426 | job->setQueue(0); 427 | 428 | return job; 429 | } 430 | 431 | void KDirReadJobQueue::clear() { 432 | QMutableListIterator i(_queue); 433 | while (i.hasNext()) { 434 | KDirReadJob *job = i.next(); 435 | delete job; 436 | i.remove(); 437 | } 438 | } 439 | 440 | void KDirReadJobQueue::abort() { 441 | while (!_queue.isEmpty()) { 442 | KDirReadJob *job = _queue.first(); 443 | 444 | if (job->dir()) 445 | job->dir()->readJobAborted(); 446 | 447 | _queue.removeFirst(); 448 | delete job; 449 | } 450 | } 451 | 452 | void KDirReadJobQueue::killAll(KDirInfo *subtree) { 453 | if (!subtree) 454 | return; 455 | 456 | QMutableListIterator i(_queue); 457 | while (i.hasNext()) { 458 | KDirReadJob *job = i.next(); 459 | if (job->dir() && job->dir()->isInSubtree(subtree)) { 460 | i.remove(); 461 | delete job; 462 | } else { 463 | } 464 | } 465 | } 466 | 467 | void KDirReadJobQueue::timeSlicedRead() { 468 | if (!_queue.isEmpty()) 469 | _queue.first()->read(); 470 | } 471 | 472 | void KDirReadJobQueue::jobFinishedNotify(KDirReadJob *job) { 473 | // Get rid of the old (finished) job. 474 | 475 | _queue.removeFirst(); 476 | delete job; 477 | 478 | // Look for a new job. 479 | 480 | if (_queue.isEmpty()) // No new job available - we're done. 481 | { 482 | _timer.stop(); 483 | // qDebug() << "No more jobs - finishing" << endl; 484 | emit finished(); 485 | } 486 | } 487 | 488 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | --------------------------------------------------------------------------------