File indexing completed on 2024-04-21 05:51:25

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "SaveHistoryTask.h"
0009 
0010 #include <QApplication>
0011 #include <QFileDialog>
0012 #include <QTextStream>
0013 
0014 #include <KConfig>
0015 #include <KConfigGroup>
0016 #include <KIO/TransferJob>
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 #include <KSharedConfig>
0020 
0021 #include "Emulation.h"
0022 #include "session/SessionManager.h"
0023 
0024 #include "../decoders/HTMLDecoder.h"
0025 #include "../decoders/PlainTextDecoder.h"
0026 #include "colorscheme/ColorScheme.h"
0027 #include "colorscheme/ColorSchemeManager.h"
0028 
0029 namespace Konsole
0030 {
0031 QString SaveHistoryTask::_saveDialogRecentURL;
0032 
0033 SaveHistoryTask::SaveHistoryTask(QObject *parent)
0034     : SessionTask(parent)
0035 {
0036 }
0037 
0038 SaveHistoryTask::~SaveHistoryTask() = default;
0039 
0040 void SaveHistoryTask::execute()
0041 {
0042     // TODO - think about the UI when saving multiple history sessions, if there are more than two or
0043     //        three then providing a URL for each one will be tedious
0044 
0045     // TODO - show a warning ( preferably passive ) if saving the history output fails
0046     QFileDialog *dialog = new QFileDialog(QApplication::activeWindow());
0047     dialog->setAcceptMode(QFileDialog::AcceptSave);
0048 
0049     QStringList mimeTypes{QStringLiteral("text/plain"), QStringLiteral("text/html")};
0050     dialog->setMimeTypeFilters(mimeTypes);
0051 
0052     KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig();
0053     auto group = konsoleConfig->group(QStringLiteral("SaveHistory Settings"));
0054 
0055     if (_saveDialogRecentURL.isEmpty()) {
0056         const auto list = group.readPathEntry("Recent URLs", QStringList());
0057         if (list.isEmpty()) {
0058             dialog->setDirectory(QDir::homePath());
0059         } else {
0060             dialog->setDirectoryUrl(QUrl(list.at(0)));
0061         }
0062     } else {
0063         dialog->setDirectoryUrl(QUrl(_saveDialogRecentURL));
0064     }
0065 
0066     // iterate over each session in the task and display a dialog to allow the user to choose where
0067     // to save that session's history.
0068     // then start a KIO job to transfer the data from the history to the chosen URL
0069     const QList<QPointer<Session>> sessionsList = sessions();
0070     for (const auto &session : sessionsList) {
0071         dialog->setWindowTitle(i18n("Save Output From %1", session->title(Session::NameRole)));
0072 
0073         int result = dialog->exec();
0074 
0075         if (result != QDialog::Accepted) {
0076             continue;
0077         }
0078 
0079         QUrl url = (dialog->selectedUrls()).at(0);
0080 
0081         if (!url.isValid()) {
0082             // UI:  Can we make this friendlier?
0083             KMessageBox::error(nullptr, i18n("%1 is an invalid URL, the output could not be saved.", url.url()));
0084             continue;
0085         }
0086 
0087         // Save selected URL for next time
0088         _saveDialogRecentURL = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toString();
0089         group.writePathEntry("Recent URLs", _saveDialogRecentURL);
0090 
0091         KIO::TransferJob *job = KIO::put(url,
0092                                          -1, // no special permissions
0093                                          // overwrite existing files
0094                                          // do not resume an existing transfer
0095                                          // show progress information only for remote
0096                                          // URLs
0097                                          KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags)
0098                                          // a better solution would be to show progress
0099                                          // information after a certain period of time
0100                                          // instead, since the overall speed of transfer
0101                                          // depends on factors other than just the protocol
0102                                          // used
0103         );
0104 
0105         SaveJob jobInfo;
0106         jobInfo.session = session;
0107         jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem
0108         // lastLineFetched is used to keep track of how much of the history
0109         // has already been sent, and where the next request should continue
0110         // from.
0111         // this is set to -1 to indicate the job has just been started
0112 
0113         if (((dialog->selectedNameFilter()).contains(QLatin1String("html"), Qt::CaseInsensitive))
0114             || ((dialog->selectedFiles()).at(0).endsWith(QLatin1String("html"), Qt::CaseInsensitive))) {
0115             Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
0116             const auto schemeName = profile->colorScheme();
0117             const auto scheme = ColorSchemeManager::instance()->findColorScheme(schemeName);
0118             QColor colorTable[TABLE_COLORS];
0119             if (scheme) {
0120                 scheme->getColorTable(colorTable);
0121             } else {
0122                 std::copy_n(ColorScheme::defaultTable, TABLE_COLORS, colorTable);
0123             }
0124 
0125             jobInfo.decoder = new HTMLDecoder(colorTable, profile->font());
0126         } else {
0127             jobInfo.decoder = new PlainTextDecoder();
0128         }
0129 
0130         _jobSession.insert(job, jobInfo);
0131 
0132         connect(job, &KIO::TransferJob::dataReq, this, &Konsole::SaveHistoryTask::jobDataRequested);
0133         connect(job, &KIO::TransferJob::result, this, &Konsole::SaveHistoryTask::jobResult);
0134     }
0135 
0136     dialog->deleteLater();
0137 }
0138 
0139 void SaveHistoryTask::jobDataRequested(KIO::Job *job, QByteArray &data)
0140 {
0141     // TODO - Report progress information for the job
0142 
0143     // PERFORMANCE:  Do some tests and tweak this value to get faster saving
0144     const int LINES_PER_REQUEST = 500;
0145 
0146     SaveJob &info = _jobSession[job];
0147 
0148     // transfer LINES_PER_REQUEST lines from the session's history
0149     // to the save location
0150     if (!info.session.isNull()) {
0151         // note:  when retrieving lines from the emulation,
0152         // the first line is at index 0.
0153 
0154         int sessionLines = info.session->emulation()->lineCount();
0155 
0156         if (sessionLines - 1 == info.lastLineFetched) {
0157             return; // if there is no more data to transfer then stop the job
0158         }
0159 
0160         int copyUpToLine = qMin(info.lastLineFetched + LINES_PER_REQUEST, sessionLines - 1);
0161 
0162         QTextStream stream(&data, QIODevice::ReadWrite);
0163         info.decoder->begin(&stream);
0164         info.session->emulation()->writeToStream(info.decoder, info.lastLineFetched + 1, copyUpToLine);
0165         info.decoder->end();
0166 
0167         info.lastLineFetched = copyUpToLine;
0168     }
0169 }
0170 void SaveHistoryTask::jobResult(KJob *job)
0171 {
0172     if (job->error() != 0) {
0173         KMessageBox::error(nullptr, i18n("A problem occurred when saving the output.\n%1", job->errorString()));
0174     }
0175 
0176     TerminalCharacterDecoder *decoder = _jobSession[job].decoder;
0177 
0178     _jobSession.remove(job);
0179 
0180     delete decoder;
0181 
0182     // notify the world that the task is done
0183     Q_EMIT completed(true);
0184 
0185     if (autoDelete()) {
0186         deleteLater();
0187     }
0188 }
0189 
0190 }
0191 
0192 #include "moc_SaveHistoryTask.cpp"