QGCUASFileView.cc 12.6 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
    _manager(manager),
    _listInProgress(false),
36 37
    _downloadInProgress(false),
    _uploadInProgress(false)
38
{
Don Gagne's avatar
Don Gagne committed
39
    _ui.setupUi(this);
40 41 42
    
    // Progress bar is only visible while a download is in progress
    _ui.progressBar->setVisible(false);
43

44 45 46 47 48
    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
49
    Q_ASSERT(success);
50
    success = connect(_ui.downloadButton, SIGNAL(clicked()), this, SLOT(_downloadFile()));
Don Gagne's avatar
Don Gagne committed
51
    Q_ASSERT(success);
52 53
    success = connect(_ui.uploadButton, SIGNAL(clicked()), this, SLOT(_uploadFile()));
    Q_ASSERT(success);
Don Gagne's avatar
Don Gagne committed
54 55 56
    success = connect(_ui.treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(_currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
    Q_ASSERT(success);
}
Lorenz Meier's avatar
Lorenz Meier committed
57

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

96 97 98 99 100 101 102
/// @brief uploads a file into the currently selected directory the tree view
void QGCUASFileView::_uploadFile(void)
{
    Q_ASSERT(!_uploadInProgress);

    _ui.statusText->clear();

103
    // get and check directory from list view
104 105 106 107 108
    QTreeWidgetItem* item = _ui.treeWidget->currentItem();
    if (item && item->type() != _typeDir) {
        return;
    }

109 110 111 112 113 114 115
    // Find complete path for upload directory
    QString path;
    do {
        QString name = item->text(0).split("\t")[0];    // Strip off file sizes
        path.prepend("/" + name);
        item = item->parent();
    } while (item);
116 117 118 119

    QString uploadFromHere = QGCFileDialog::getOpenFileName(this, tr("Upload File"),
                                                               QDir::homePath());

120 121
    qDebug() << "Upload: " << uploadFromHere << "to path" << path;

122
    _manager->uploadPath(path, uploadFromHere);
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
/// @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
187
    }
188 189
}

190
/// @brief Refreshes the directory list tree.
Don Gagne's avatar
Don Gagne committed
191
void QGCUASFileView::_refreshTree(void)
192
{
193 194
    Q_ASSERT(!_listInProgress);
    
195
    _ui.treeWidget->clear();
196
    _ui.statusText->clear();
Don Gagne's avatar
Don Gagne committed
197 198 199 200 201 202

    _walkIndexStack.clear();
    _walkItemStack.clear();
    _walkIndexStack.append(0);
    _walkItemStack.append(_ui.treeWidget->invisibleRootItem());
    
203 204
    // Don't queue up more than once
    _ui.listFilesButton->setEnabled(false);
205 206
    _listInProgress = true;
    _connectListSignals();
Don Gagne's avatar
Don Gagne committed
207

Don Gagne's avatar
Don Gagne committed
208
    _requestDirectoryList("/");
209
}
Lorenz Meier's avatar
Lorenz Meier committed
210

211 212
/// @brief Adds the specified directory entry to the tree view.
void QGCUASFileView::_listEntryReceived(const QString& entry)
Lorenz Meier's avatar
Lorenz Meier committed
213
{
214 215
    Q_ASSERT(_listInProgress);
    
Don Gagne's avatar
Don Gagne committed
216
    int type;
217
    if (entry.startsWith("F")) {
Don Gagne's avatar
Don Gagne committed
218
        type = _typeFile;
219
    } else if (entry.startsWith("D")) {
Don Gagne's avatar
Don Gagne committed
220
        type = _typeDir;
221
        if (entry == "D." || entry == "D..") {
Don Gagne's avatar
Don Gagne committed
222 223 224 225
            return;
        }
    } else {
        Q_ASSERT(false);
226
        return; // Silence maybe-unitialized on type
Don Gagne's avatar
Don Gagne committed
227 228 229
    }

    QTreeWidgetItem* item;
230
    item = new QTreeWidgetItem(_walkItemStack.last(), type);
Don Gagne's avatar
Don Gagne committed
231 232
    Q_CHECK_PTR(item);
    
233
    item->setText(0, entry.right(entry.size() - 1));
Lorenz Meier's avatar
Lorenz Meier committed
234 235
}

236 237 238
/// @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
239
{
240 241 242 243 244 245
    if (_listInProgress) {
        _ui.listFilesButton->setEnabled(true);
        _listInProgress = false;
        _disconnectListSignals();
        _ui.statusText->setText(tr("Error: ") + msg);
    }
Don Gagne's avatar
Don Gagne committed
246 247 248 249
}

void QGCUASFileView::_listComplete(void)
{
250 251
    Q_ASSERT(_listInProgress);

Don Gagne's avatar
Don Gagne committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
    // 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
283
        _requestDirectoryList(dir);
Don Gagne's avatar
Don Gagne committed
284 285 286 287 288 289 290
    } 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 {
291
            _ui.listFilesButton->setEnabled(true);
292 293
            _listInProgress = false;
            _disconnectListSignals();
Don Gagne's avatar
Don Gagne committed
294 295 296 297 298 299 300
        }
    }
}

void QGCUASFileView::_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
    Q_UNUSED(previous);
301
    // FIXME: Should not enable when downloading
302
    _ui.downloadButton->setEnabled(current ? (current->type() == _typeFile) : false);
303
    _ui.uploadButton->setEnabled(current ? (current->type() == _typeDir) : false);
Lorenz Meier's avatar
Lorenz Meier committed
304
}
Don Gagne's avatar
Don Gagne committed
305

306
void QGCUASFileView::_requestDirectoryList(const QString& dir)
Don Gagne's avatar
Don Gagne committed
307
{
308 309
    qDebug() << "List:" << dir;
    _manager->listDirectory(dir);
Don Gagne's avatar
Don Gagne committed
310 311
}

312 313 314 315
/// @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
316
{
317 318 319 320 321 322 323 324 325 326 327
    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
328 329
}

330
void QGCUASFileView::_disconnectDownloadSignals(void)
Don Gagne's avatar
Don Gagne committed
331
{
332 333 334 335
    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
336 337
}

338 339 340 341
/// @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
342
{
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    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
359
}