QGCUASFileView.cc 11.5 KB
Newer Older
Don Gagne's avatar
Don Gagne committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*=====================================================================
 
 QGroundControl Open Source Ground Control Station
 
 (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 
 This file is part of the QGROUNDCONTROL project
 
 QGROUNDCONTROL is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 QGROUNDCONTROL is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
 
 ======================================================================*/

24 25 26
#include "QGCUASFileView.h"
#include "uas/QGCUASFileManager.h"

Lorenz Meier's avatar
Lorenz Meier committed
27 28
#include <QFileDialog>
#include <QDir>
Don Gagne's avatar
Don Gagne committed
29
#include <QMessageBox>
Lorenz Meier's avatar
Lorenz Meier committed
30

31 32
QGCUASFileView::QGCUASFileView(QWidget *parent, QGCUASFileManager *manager) :
    QWidget(parent),
33 34 35
    _manager(manager),
    _listInProgress(false),
    _downloadInProgress(false)
36
{
Don Gagne's avatar
Don Gagne committed
37
    _ui.setupUi(this);
38 39 40
    
    // Progress bar is only visible while a download is in progress
    _ui.progressBar->setVisible(false);
41

42 43 44 45 46
    bool success;
    Q_UNUSED(success);    // Silence retail unused variable error
    
    // Connect UI signals
    success = connect(_ui.listFilesButton, SIGNAL(clicked()), this, SLOT(_refreshTree()));
Don Gagne's avatar
Don Gagne committed
47
    Q_ASSERT(success);
48
    success = connect(_ui.downloadButton, SIGNAL(clicked()), this, SLOT(_downloadFile()));
Don Gagne's avatar
Don Gagne committed
49 50 51 52
    Q_ASSERT(success);
    success = connect(_ui.treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(_currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
    Q_ASSERT(success);
}
Lorenz Meier's avatar
Lorenz Meier committed
53

54 55
/// @brief Downloads the file currently selected in the tree view
void QGCUASFileView::_downloadFile(void)
Don Gagne's avatar
Don Gagne committed
56
{
57 58 59 60 61 62 63 64 65
    Q_ASSERT(!_downloadInProgress);
    
    _ui.statusText->clear();
    
    QString downloadToHere = QFileDialog::getExistingDirectory(this, tr("Download Directory"),
                                                               QDir::homePath(),
                                                               QFileDialog::ShowDirsOnly
                                                               | QFileDialog::DontResolveSymlinks);
    
Don Gagne's avatar
Don Gagne committed
66 67 68 69
    // And now download to this location
    QString path;
    QTreeWidgetItem* item = _ui.treeWidget->currentItem();
    if (item && item->type() == _typeFile) {
70
        _downloadFilename.clear();
Don Gagne's avatar
Don Gagne committed
71
        do {
72
            QString name = item->text(0).split("\t")[0];    // Strip off file sizes
73 74 75 76 77 78
            
            // If this is the file name and not a directory keep track of the download file name
            if (_downloadFilename.isEmpty()) {
                _downloadFilename = name;
            }
            
79
            path.prepend("/" + name);
Don Gagne's avatar
Don Gagne committed
80 81 82 83
            item = item->parent();
        } while (item);
        qDebug() << "Download: " << path;
        
84 85 86 87 88 89 90 91 92 93 94 95 96 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
        _ui.downloadButton->setEnabled(false);
        _downloadInProgress = true;
        _connectDownloadSignals();
        
        _manager->downloadPath(path, QDir(downloadToHere));
    }
}

/// @brief Called when length of file being downloaded is known.
void QGCUASFileView::_downloadLength(unsigned int length)
{
    Q_ASSERT(_downloadInProgress);
    
    // Setup the progress bar
    QProgressBar* bar = _ui.progressBar;
    bar->setMinimum(0);
    bar->setMaximum(length);
    bar->setValue(0);
    bar->setVisible(true);
    
    _ui.downloadButton->setEnabled(true);
    _downloadStartTime.start();
    
    _ui.statusText->setText(tr("Downloading: %1").arg(_downloadFilename));
}

/// @brief Called to update the progress of the download.
///     @param bytesReceived Current count of bytes received for current download
void QGCUASFileView::_downloadProgress(unsigned int bytesReceived)
{
    static uint lastSecsReported = 0;
    
    Q_ASSERT(_downloadInProgress);
    
    _ui.progressBar->setValue(bytesReceived);
    
    // Calculate and display download rate. Only update once per second.
    uint kbReceived = bytesReceived / 1024;
    uint secs = _downloadStartTime.elapsed() / 1000;
    if (secs != 0) {
        uint kbPerSec = kbReceived / secs;
        if (kbPerSec != 0 && secs != lastSecsReported) {
            lastSecsReported = secs;
            _ui.statusText->setText(tr("Downloading: %1 %2 KB/sec").arg(_downloadFilename).arg(kbPerSec));
        }
    }
}

/// @brief Called when the download associated with the QGCUASFileManager::downloadPath command completes.
void QGCUASFileView::_downloadComplete(void)
{
    Q_ASSERT(_downloadInProgress);
    _ui.downloadButton->setEnabled(true);
    _ui.progressBar->setVisible(false);
    _downloadInProgress = false;
    _disconnectDownloadSignals();
    _ui.statusText->setText(tr("Download complete: %1").arg(_downloadFilename));
}

/// @brief Called when an error occurs during a download.
///     @param msg Error message
void QGCUASFileView::_downloadErrorMessage(const QString& msg)
{
    if (_downloadInProgress) {
        _ui.downloadButton->setEnabled(true);
        _ui.progressBar->setVisible(false);
        _downloadInProgress = false;
        _disconnectDownloadSignals();
        _ui.statusText->setText(tr("Error: ") + msg);
Don Gagne's avatar
Don Gagne committed
153
    }
154 155
}

156
/// @brief Refreshes the directory list tree.
Don Gagne's avatar
Don Gagne committed
157
void QGCUASFileView::_refreshTree(void)
158
{
159 160
    Q_ASSERT(!_listInProgress);
    
161
    _ui.treeWidget->clear();
162
    _ui.statusText->clear();
Don Gagne's avatar
Don Gagne committed
163 164 165 166 167 168

    _walkIndexStack.clear();
    _walkItemStack.clear();
    _walkIndexStack.append(0);
    _walkItemStack.append(_ui.treeWidget->invisibleRootItem());
    
169 170
    // Don't queue up more than once
    _ui.listFilesButton->setEnabled(false);
171 172
    _listInProgress = true;
    _connectListSignals();
Don Gagne's avatar
Don Gagne committed
173

Don Gagne's avatar
Don Gagne committed
174
    _requestDirectoryList("/");
175
}
Lorenz Meier's avatar
Lorenz Meier committed
176

177 178
/// @brief Adds the specified directory entry to the tree view.
void QGCUASFileView::_listEntryReceived(const QString& entry)
Lorenz Meier's avatar
Lorenz Meier committed
179
{
180 181
    Q_ASSERT(_listInProgress);
    
Don Gagne's avatar
Don Gagne committed
182
    int type;
183
    if (entry.startsWith("F")) {
Don Gagne's avatar
Don Gagne committed
184
        type = _typeFile;
185
    } else if (entry.startsWith("D")) {
Don Gagne's avatar
Don Gagne committed
186
        type = _typeDir;
187
        if (entry == "D." || entry == "D..") {
Don Gagne's avatar
Don Gagne committed
188 189 190 191
            return;
        }
    } else {
        Q_ASSERT(false);
192
        return; // Silence maybe-unitialized on type
Don Gagne's avatar
Don Gagne committed
193 194 195
    }

    QTreeWidgetItem* item;
196
    item = new QTreeWidgetItem(_walkItemStack.last(), type);
Don Gagne's avatar
Don Gagne committed
197 198
    Q_CHECK_PTR(item);
    
199
    item->setText(0, entry.right(entry.size() - 1));
Lorenz Meier's avatar
Lorenz Meier committed
200 201
}

202 203 204
/// @brief Called when an error occurs during a directory listing.
///     @param msg Error message
void QGCUASFileView::_listErrorMessage(const QString& msg)
Lorenz Meier's avatar
Lorenz Meier committed
205
{
206 207 208 209 210 211
    if (_listInProgress) {
        _ui.listFilesButton->setEnabled(true);
        _listInProgress = false;
        _disconnectListSignals();
        _ui.statusText->setText(tr("Error: ") + msg);
    }
Don Gagne's avatar
Don Gagne committed
212 213 214 215
}

void QGCUASFileView::_listComplete(void)
{
216 217
    Q_ASSERT(_listInProgress);

Don Gagne's avatar
Don Gagne committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    // Walk the current items, traversing down into directories
    
Again:
    int walkIndex = _walkIndexStack.last();
    QTreeWidgetItem* parentItem = _walkItemStack.last();
    QTreeWidgetItem* childItem = parentItem->child(walkIndex);

    // Loop until we hit a directory
    while (childItem && childItem->type() != _typeDir) {
        // Move to next index at current level
        _walkIndexStack.last() = ++walkIndex;
        childItem = parentItem->child(walkIndex);
    }
    
    if (childItem) {
        // Process this item
        QString text = childItem->text(0);
        
        // Move to the next item for processing at this level
        _walkIndexStack.last() = ++walkIndex;
        
        // Push this new directory on the stack
        _walkItemStack.append(childItem);
        _walkIndexStack.append(0);
        
        // Ask for the directory list
        QString dir;
        for (int i=1; i<_walkItemStack.count(); i++) {
            QTreeWidgetItem* item = _walkItemStack[i];
            dir.append("/" + item->text(0));
        }
Don Gagne's avatar
Don Gagne committed
249
        _requestDirectoryList(dir);
Don Gagne's avatar
Don Gagne committed
250 251 252 253 254 255 256
    } else {
        // We have run out of items at the this level, pop the stack and keep going at that level
        _walkIndexStack.removeLast();
        _walkItemStack.removeLast();
        if (_walkIndexStack.count() != 0) {
            goto Again;
        } else {
257
            _ui.listFilesButton->setEnabled(true);
258 259
            _listInProgress = false;
            _disconnectListSignals();
Don Gagne's avatar
Don Gagne committed
260 261 262 263 264 265 266
        }
    }
}

void QGCUASFileView::_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
    Q_UNUSED(previous);
267
    // FIXME: Should not enable when downloading
268
    _ui.downloadButton->setEnabled(current ? (current->type() == _typeFile) : false);
Lorenz Meier's avatar
Lorenz Meier committed
269
}
Don Gagne's avatar
Don Gagne committed
270

271
void QGCUASFileView::_requestDirectoryList(const QString& dir)
Don Gagne's avatar
Don Gagne committed
272
{
273 274
    qDebug() << "List:" << dir;
    _manager->listDirectory(dir);
Don Gagne's avatar
Don Gagne committed
275 276
}

277 278 279 280
/// @brief Connects to the signals associated with the QGCUASFileManager::downloadPath method. We only leave these signals connected
/// while a download because there may be multiple UAS, which in turn means multiple QGCUASFileView instances. We only want the signals
/// connected to the active FileView which is doing the current download.
void QGCUASFileView::_connectDownloadSignals(void)
Don Gagne's avatar
Don Gagne committed
281
{
282 283 284 285 286 287 288 289 290 291 292
    bool success;
    Q_UNUSED(success);    // Silence retail unused variable error

    success = connect(_manager, SIGNAL(downloadFileLength(unsigned int)), this, SLOT(_downloadLength(unsigned int)));
    Q_ASSERT(success);
    success = connect(_manager, SIGNAL(downloadFileProgress(unsigned int)), this, SLOT(_downloadProgress(unsigned int)));
    Q_ASSERT(success);
    success = connect(_manager, SIGNAL(downloadFileComplete(void)), this, SLOT(_downloadComplete(void)));
    Q_ASSERT(success);
    success = connect(_manager, SIGNAL(errorMessage(const QString&)), this, SLOT(_downloadErrorMessage(const QString&)));
    Q_ASSERT(success);
Don Gagne's avatar
Don Gagne committed
293 294
}

295
void QGCUASFileView::_disconnectDownloadSignals(void)
Don Gagne's avatar
Don Gagne committed
296
{
297 298 299 300
    disconnect(_manager, SIGNAL(downloadFileLength(unsigned int)), this, SLOT(_downloadLength(unsigned int)));
    disconnect(_manager, SIGNAL(downloadFileProgress(unsigned int)), this, SLOT(_downloadProgress(unsigned int)));
    disconnect(_manager, SIGNAL(downloadFileComplete(void)), this, SLOT(_downloadComplete(void)));
    disconnect(_manager, SIGNAL(errorMessage(const QString&)), this, SLOT(_downloadErrorMessage(const QString&)));
Don Gagne's avatar
Don Gagne committed
301 302
}

303 304 305 306
/// @brief Connects to the signals associated with the QGCUASFileManager::listDirectory method. We only leave these signals connected
/// while a download because there may be multiple UAS, which in turn means multiple QGCUASFileView instances. We only want the signals
/// connected to the active FileView which is doing the current download.
void QGCUASFileView::_connectListSignals(void)
Don Gagne's avatar
Don Gagne committed
307
{
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    bool success;
    Q_UNUSED(success);    // Silence retail unused variable error
    
    success = connect(_manager, SIGNAL(listEntry(const QString&)), this, SLOT(_listEntryReceived(const QString&)));
    Q_ASSERT(success);
    success = connect(_manager, SIGNAL(listComplete(void)), this, SLOT(_listComplete(void)));
    Q_ASSERT(success);
    success = connect(_manager, SIGNAL(errorMessage(const QString&)), this, SLOT(_listErrorMessage(const QString&)));
    Q_ASSERT(success);
}

void QGCUASFileView::_disconnectListSignals(void)
{
    disconnect(_manager, SIGNAL(listEntry(const QString&)), this, SLOT(_listEntryReceived(const QString&)));
    disconnect(_manager, SIGNAL(listComplete(void)), this, SLOT(_listComplete(void)));
    disconnect(_manager, SIGNAL(errorMessage(const QString&)), this, SLOT(_listErrorMessage(const QString&)));
Don Gagne's avatar
Don Gagne committed
324
}