1 /*
2  *   File name: FilesystemsWindow.cpp
3  *   Summary:	QDirStat "Mounted Filesystems" window
4  *   License:	GPL V2 - See file LICENSE for details.
5  *
6  *   Author:	Stefan Hundhammer <Stefan.Hundhammer@gmx.de>
7  */
8 
9 
10 #include <QFileIconProvider>
11 
12 #include "FilesystemsWindow.h"
13 #include "MountPoints.h"
14 #include "SettingsHelpers.h"
15 #include "HeaderTweaker.h"
16 #include "PanelMessage.h"
17 #include "FormatUtil.h"
18 #include "Logger.h"
19 #include "Exception.h"
20 
21 #define WARN_PERCENT	10.0
22 
23 using namespace QDirStat;
24 
25 
FilesystemsWindow(QWidget * parent)26 FilesystemsWindow::FilesystemsWindow( QWidget * parent ):
27     QDialog( parent ),
28     _ui( new Ui::FilesystemsWindow )
29 {
30     CHECK_NEW( _ui );
31     _ui->setupUi( this );
32     MountPoints::reload();
33     initWidgets();
34     readWindowSettings( this, "FilesystemsWindow" );
35 }
36 
37 
~FilesystemsWindow()38 FilesystemsWindow::~FilesystemsWindow()
39 {
40     writeWindowSettings( this, "FilesystemsWindow" );
41     delete _ui;
42 }
43 
44 
populate()45 void FilesystemsWindow::populate()
46 {
47     QFileIconProvider iconProvider;
48     clear();
49 
50     foreach ( MountPoint * mountPoint, MountPoints::normalMountPoints() )
51     {
52 	CHECK_PTR( mountPoint);
53 
54         if ( mountPoint->isUnmountedAutofs() )
55             continue;
56 
57 	FilesystemItem * item = new FilesystemItem( mountPoint, _ui->fsTree );
58 	CHECK_NEW( item );
59 
60 	QIcon icon = iconProvider.icon( mountPoint->isNetworkMount() ?
61 					 QFileIconProvider::Network :
62 					 QFileIconProvider::Drive      );
63 	item->setIcon( 0, icon );
64     }
65 
66     if ( MountPoints::hasBtrfs() )
67 	showBtrfsFreeSizeWarning();
68 }
69 
70 
showBtrfsFreeSizeWarning()71 void FilesystemsWindow::showBtrfsFreeSizeWarning()
72 {
73     PanelMessage * msg = new PanelMessage( _ui->messagePanel );
74     CHECK_NEW( msg );
75 
76     msg->setHeading( tr( "Btrfs free and used size information are misleading!" ) );
77     msg->setText( tr( "Snapshots and copy-on-write may consume additional disk space." ) );
78     msg->setDetailsUrl( "https://github.com/shundhammer/qdirstat/blob/master/doc/Btrfs-Free-Size.md" );
79     _ui->messagePanel->add( msg );
80 }
81 
82 
refresh()83 void FilesystemsWindow::refresh()
84 {
85     MountPoints::reload();
86     populate();
87 }
88 
89 
reject()90 void FilesystemsWindow::reject()
91 {
92     deleteLater();
93 }
94 
95 
clear()96 void FilesystemsWindow::clear()
97 {
98     _ui->fsTree->clear();
99     _ui->messagePanel->clear();
100 }
101 
102 
initWidgets()103 void FilesystemsWindow::initWidgets()
104 {
105     QStringList headers;
106     headers << tr( "Device" )
107 	    << tr( "Mount Point" )
108 	    << tr( "Type" );
109 
110     if ( MountPoints::hasSizeInfo() )
111     {
112 	headers << tr( "Size"	  )
113 		<< tr( "Used"	  )
114 		<< tr( "Reserved" )
115 		<< tr( "Free"	  )
116 		<< tr( "Free %"	  );
117     }
118 
119     _ui->fsTree->setHeaderLabels( headers );
120     _ui->fsTree->header()->setStretchLastSection( false );
121 
122     // Center the column headers
123 
124     QTreeWidgetItem * hItem = _ui->fsTree->headerItem();
125 
126     for ( int col = 0; col < headers.size(); ++col )
127 	hItem->setTextAlignment( col, Qt::AlignHCenter );
128 
129     hItem->setToolTip( FS_ReservedSizeCol, tr( "Reserved for root" ) );
130     hItem->setToolTip( FS_FreeSizeCol,	   tr( "Free for unprivileged users" ) );
131 
132     HeaderTweaker::resizeToContents( _ui->fsTree->header() );
133     _ui->fsTree->sortItems( FS_DeviceCol, Qt::AscendingOrder );
134     enableActions();
135 
136     connect( _ui->refreshButton, SIGNAL( clicked() ),
137 	     this,		 SLOT  ( refresh() ) );
138 
139     connect( _ui->readButton,	 SIGNAL( clicked() ),
140 	     this,		 SLOT  ( readSelectedFilesystem() ) );
141 
142     connect( _ui->fsTree,	 SIGNAL( itemSelectionChanged() ),
143 	     this,		 SLOT  ( enableActions()	) );
144 }
145 
146 
enableActions()147 void FilesystemsWindow::enableActions()
148 {
149     _ui->readButton->setEnabled( ! selectedPath().isEmpty() );
150 }
151 
152 
readSelectedFilesystem()153 void FilesystemsWindow::readSelectedFilesystem()
154 {
155     QString path = selectedPath();
156 
157     if ( ! path.isEmpty() )
158     {
159 	logDebug() << "Read " << path << endl;
160 	emit readFilesystem( path );
161     }
162 }
163 
164 
selectedPath() const165 QString FilesystemsWindow::selectedPath() const
166 {
167     QString result;
168     QList<QTreeWidgetItem *> sel = _ui->fsTree->selectedItems();
169 
170     if ( ! sel.isEmpty() )
171     {
172 	FilesystemItem * item = dynamic_cast<FilesystemItem *>( sel.first() );
173 
174 	if ( item )
175 	    result = item->mountPath();
176     }
177 
178     return result;
179 }
180 
181 
182 
183 
FilesystemItem(MountPoint * mountPoint,QTreeWidget * parent)184 FilesystemItem::FilesystemItem( MountPoint  * mountPoint,
185 				QTreeWidget * parent ):
186     QTreeWidgetItem( parent ),
187     _device	    ( mountPoint->device()	    ),
188     _mountPath	    ( mountPoint->path()	    ),
189     _fsType	    ( mountPoint->filesystemType()  ),
190     _totalSize	    ( mountPoint->totalSize()	    ),
191     _usedSize	    ( mountPoint->usedSize()	    ),
192     _reservedSize   ( mountPoint->reservedSize()    ),
193     _freeSize	    ( mountPoint->freeSizeForUser() ),
194     _isNetworkMount ( mountPoint->isNetworkMount()  ),
195     _isReadOnly	    ( mountPoint->isReadOnly()	    )
196 {
197     QString blanks = QString( 4, ' ' );
198 
199     setText( FS_DeviceCol,    _device + blanks );
200     setText( FS_MountPathCol, _mountPath );
201     setText( FS_TypeCol,      _fsType	 );
202 
203     setTextAlignment( FS_TypeCol, Qt::AlignHCenter );
204 
205     if ( parent->columnCount() >= FS_TotalSizeCol && _totalSize >= 0 )
206     {
207 	blanks = QString( 3, ' ' ); // Enforce left margin
208 
209 	setText( FS_TotalSizeCol, blanks + formatSize( _totalSize	      ) );
210 	setText( FS_UsedSizeCol,  blanks + formatSize( _usedSize	      ) );
211 
212 	if ( _reservedSize > 0 )
213 	    setText( FS_ReservedSizeCol, blanks + formatSize( _reservedSize    ) );
214 
215 	if ( _isReadOnly )
216 	    setText( FS_FreeSizeCol, QObject::tr( "read-only" ) );
217 	else
218 	{
219 	    setText( FS_FreeSizeCol, blanks + formatSize( _freeSize ) );
220 
221 	    float freePercent = 0.0;
222 
223 	    if ( _totalSize > 0 )
224 	    {
225 		freePercent = 100.0 * _freeSize / _totalSize;
226 		setText( FS_FreePercentCol, formatPercent( freePercent ) );
227 
228 		if ( freePercent < WARN_PERCENT )
229 		{
230 		    setForeground( FS_FreeSizeCol,    Qt::red );
231 		    setForeground( FS_FreePercentCol, Qt::red );
232 		}
233 	    }
234 	}
235 
236 	for ( int col = FS_TotalSizeCol; col < parent->columnCount(); ++col )
237 	    setTextAlignment( col, Qt::AlignRight );
238     }
239 }
240 
241 
operator <(const QTreeWidgetItem & rawOther) const242 bool FilesystemItem::operator<( const QTreeWidgetItem & rawOther ) const
243 {
244     const FilesystemItem & other = dynamic_cast<const FilesystemItem &>( rawOther );
245 
246     int col = treeWidget() ? treeWidget()->sortColumn() : FS_DeviceCol;
247 
248     switch ( col )
249     {
250 	case FS_DeviceCol:
251 	    if ( ! isNetworkMount() &&	 other.isNetworkMount() ) return true;
252 	    if ( isNetworkMount()   && ! other.isNetworkMount() ) return false;
253 	    return device() < other.device();
254 
255 	case FS_MountPathCol:		return mountPath()    < other.mountPath();
256 	case FS_TypeCol:		return fsType()	      < other.fsType();
257 	case FS_TotalSizeCol:		return totalSize()    < other.totalSize();
258 	case FS_UsedSizeCol:		return usedSize()     < other.usedSize();
259 	case FS_ReservedSizeCol:	return reservedSize() < other.reservedSize();
260 	case FS_FreePercentCol:
261 	case FS_FreeSizeCol:		return freeSize()      < other.freeSize();
262 	default:			return QTreeWidgetItem::operator<( rawOther );
263     }
264 }
265