QGCUASFileView.cc 12.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
#include "QGCUASFileView.h"
25
#include "uas/FileManager.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
QGCUASFileView::QGCUASFileView(QWidget *parent, FileManager *manager) :
33
    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
            item = item->parent();
        } while (item);
        
87 88 89 90
        _ui.downloadButton->setEnabled(false);
        _downloadInProgress = true;
        _connectDownloadSignals();
        
91
        _manager->streamPath(path, QDir(downloadToHere));
92 93 94
    }
}

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

    _ui.statusText->clear();

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

108 109 110 111 112 113 114
    // 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);
115 116 117 118

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

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

121
    _manager->uploadPath(path, uploadFromHere);
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 154 155 156 157 158 159 160 161 162 163 164
/// @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));
        }
    }
}

165
/// @brief Called when the download associated with the FileManager::downloadPath command completes.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
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
186
    }
187 188
}

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

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

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

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

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

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

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

Don Gagne's avatar
Don Gagne committed
251 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
    // 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
282
        _requestDirectoryList(dir);
Don Gagne's avatar
Don Gagne committed
283 284 285 286 287 288 289
    } 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 {
290
            _ui.listFilesButton->setEnabled(true);
291 292
            _listInProgress = false;
            _disconnectListSignals();
Don Gagne's avatar
Don Gagne committed
293 294 295 296 297 298 299
        }
    }
}

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

305
void QGCUASFileView::_requestDirectoryList(const QString& dir)
Don Gagne's avatar
Don Gagne committed
306
{
307
    _manager->listDirectory(dir);
Don Gagne's avatar
Don Gagne committed
308 309
}

310
/// @brief Connects to the signals associated with the FileManager::downloadPath method. We only leave these signals connected
311 312 313
/// 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
314
{
315 316 317 318 319 320 321 322 323 324 325
    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
326 327
}

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

336
/// @brief Connects to the signals associated with the FileManager::listDirectory method. We only leave these signals connected
337 338 339
/// 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
340
{
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    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
357
}