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
#include "QGCUASFileView.h"
#include "uas/QGCUASFileManager.h"
Don Gagne's avatar
Don Gagne committed
26
#include "QGCFileDialog.h"
27

Lorenz Meier's avatar
Lorenz Meier committed
28 29
#include <QFileDialog>
#include <QDir>
30
#include <QDebug>
Lorenz Meier's avatar
Lorenz Meier committed
31

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

43 44 45 46 47
    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
48
    Q_ASSERT(success);
49
    success = connect(_ui.downloadButton, SIGNAL(clicked()), this, SLOT(_downloadFile()));
Don Gagne's avatar
Don Gagne committed
50 51 52 53
    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
54

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

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

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

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

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

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

203 204 205
/// @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
206
{
207 208 209 210 211 212
    if (_listInProgress) {
        _ui.listFilesButton->setEnabled(true);
        _listInProgress = false;
        _disconnectListSignals();
        _ui.statusText->setText(tr("Error: ") + msg);
    }
Don Gagne's avatar
Don Gagne committed
213 214 215 216
}

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

Don Gagne's avatar
Don Gagne committed
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 249
    // 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
250
        _requestDirectoryList(dir);
Don Gagne's avatar
Don Gagne committed
251 252 253 254 255 256 257
    } 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 {
258
            _ui.listFilesButton->setEnabled(true);
259 260
            _listInProgress = false;
            _disconnectListSignals();
Don Gagne's avatar
Don Gagne committed
261 262 263 264 265 266 267
        }
    }
}

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

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

278 279 280 281
/// @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
282
{
283 284 285 286 287 288 289 290 291 292 293
    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
294 295
}

296
void QGCUASFileView::_disconnectDownloadSignals(void)
Don Gagne's avatar
Don Gagne committed
297
{
298 299 300 301
    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
302 303
}

304 305 306 307
/// @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
308
{
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    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
325
}