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"