1#!/usr/bin/env python
2
3#############################################################################
4##
5## Copyright (C) 2017 Hans-Peter Jansen <hpj@urpla.net>
6## Copyright (C) 2016 The Qt Company Ltd.
7## Copyright (C) 2016 Ivan Komissarov
8##
9## This file is part of the examples of the Qt Toolkit.
10##
11## $QT_BEGIN_LICENSE:BSD$
12## Commercial License Usage
13## Licensees holding valid commercial Qt licenses may use this file in
14## accordance with the commercial license agreement provided with the
15## Software or, alternatively, in accordance with the terms contained in
16## a written agreement between you and The Qt Company. For licensing terms
17## and conditions see https:#www.qt.io/terms-conditions. For further
18## information use the contact form at https:#www.qt.io/contact-us.
19##
20## BSD License Usage
21## Alternatively, you may use self file under the terms of the BSD license
22## as follows:
23##
24## "Redistribution and use in source and binary forms, with or without
25## modification, are permitted provided that the following conditions are
26## met:
27##   * Redistributions of source code must retain the above copyright
28##     notice, self list of conditions and the following disclaimer.
29##   * Redistributions in binary form must reproduce the above copyright
30##     notice, self list of conditions and the following disclaimer in
31##     the documentation and/or other materials provided with the
32##     distribution.
33##   * Neither the name of The Qt Company Ltd nor the names of its
34##     contributors may be used to endorse or promote products derived
35##     from self software without specific prior written permission.
36##
37##
38## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
39## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
40## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
41## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
42## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
46## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
47## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
48## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
49##
50## $QT_END_LICENSE$
51##
52#############################################################################
53
54
55import math
56
57from PyQt5.QtCore import QAbstractTableModel, QByteArray, QDir, QStorageInfo, Qt
58from PyQt5.QtWidgets import QAbstractItemView, QApplication, QTreeView
59
60
61def sizeToString(size):
62    if size <= 0:
63        return "0 b"
64    decimals = 2
65    units = ["b", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
66    power = int(math.log(size, 1024))
67    try:
68        unit = units[power]
69    except IndexError:
70        unit = units[-1]
71        power = len(units) - 1
72    if power == 0:
73        decimals = 0
74    normsize = size / math.pow(1024, power)
75    #: this should expand to "1.23 GB"
76    return "%0.*f %s" % (decimals, normsize, unit)
77
78
79class StorageModel(QAbstractTableModel):
80    ColumnRootPath, ColumnName, ColumnDevice, ColumnFileSystemName, \
81    ColumnTotal, ColumnFree, ColumnAvailable, ColumnIsReady, \
82    ColumnIsReadOnly, ColumnIsValid, ColumnCount = range(11)
83
84    columnFuncMap = {
85        ColumnRootPath: lambda volume: QDir.toNativeSeparators(volume.rootPath()),
86        ColumnName: lambda volume: volume.name(),
87        ColumnDevice: lambda volume: volume.device(),
88        ColumnFileSystemName: lambda volume: volume.fileSystemType(),
89        ColumnTotal: lambda volume: sizeToString(volume.bytesTotal()),
90        ColumnFree: lambda volume: sizeToString(volume.bytesFree()),
91        ColumnAvailable: lambda volume: sizeToString(volume.bytesAvailable()),
92        ColumnIsReady: lambda volume: volume.isReady(),
93        ColumnIsReadOnly: lambda volume: volume.isReadOnly(),
94        ColumnIsValid: lambda volume: volume.isValid(),
95    }
96
97    columnNameMap = {
98        ColumnRootPath: "Root path",
99        ColumnName: "Volume Name",
100        ColumnDevice: "Device",
101        ColumnFileSystemName: "File system",
102        ColumnTotal: "Total",
103        ColumnFree: "Free",
104        ColumnAvailable: "Available",
105        ColumnIsReady: "Ready",
106        ColumnIsReadOnly: "Read-only",
107        ColumnIsValid: "Valid",
108    }
109
110    def __init__(self, parent = None):
111        super(StorageModel, self).__init__(parent)
112        self.volumes = QStorageInfo.mountedVolumes()
113
114    def columnCount(self, parent = None):
115        return self.ColumnCount
116
117    def rowCount(self, parent):
118        if parent.isValid():
119            return 0
120        return len(self.volumes)
121
122    def data(self, index, role):
123        if not index.isValid():
124            return None
125        if role == Qt.DisplayRole:
126            volume = self.volumes[index.row()]
127            func = self.columnFuncMap.get(index.column())
128            if func is not None:
129                return func(volume)
130
131        elif role == Qt.ToolTipRole:
132            volume = self.volumes[index.row()]
133            tooltip = []
134            for column in range(self.ColumnCount):
135                label = self.columnNameMap.get(column)
136                value = self.columnFuncMap[column](volume)
137                if isinstance(value, QByteArray):
138                    value = str(bytes(value).decode('utf-8'))
139                tooltip.append("{0}: {1}".format(label, value))
140            return "\n".join(tooltip)
141
142    def headerData(self, section, orientation, role):
143        if orientation != Qt.Horizontal:
144            return None
145        if role != Qt.DisplayRole:
146            return None
147        return self.columnNameMap.get(section)
148
149
150def main(args):
151    app = QApplication (args)
152    view = QTreeView()
153    view.setModel(StorageModel(view))
154    view.resize(640, 480)
155    view.setSelectionBehavior(QAbstractItemView.SelectRows)
156    for column in range(view.model().columnCount()):
157        view.resizeColumnToContents(column)
158    view.show()
159    return app.exec_()
160
161
162if __name__ == '__main__':
163    import sys
164    main(sys.argv)
165