Kate
kateswapfile.cpp
Go to the documentation of this file.
00001 /* This file is part of the Kate project. 00002 * 00003 * Copyright (C) 2010 Dominik Haumann <dhaumann kde org> 00004 * Copyright (C) 2010 Diana-Victoria Tiriplica <diana.tiriplica@gmail.com> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Library General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Library General Public License 00017 * along with this library; see the file COPYING.LIB. If not, write to 00018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include "config.h" 00023 00024 #include "kateswapfile.h" 00025 #include "kateview.h" 00026 #include "kateconfig.h" 00027 00028 #include <kde_file.h> 00029 00030 #include <QFileInfo> 00031 #include <QDir> 00032 #include <QApplication> 00033 00034 // swap file version header 00035 const static char * const swapFileVersionString = "Kate Swap File - Version 1.0"; 00036 00037 // tokens for swap files 00038 const static qint8 EA_StartEditing = 'S'; 00039 const static qint8 EA_FinishEditing = 'E'; 00040 const static qint8 EA_WrapLine = 'W'; 00041 const static qint8 EA_UnwrapLine = 'U'; 00042 const static qint8 EA_InsertText = 'I'; 00043 const static qint8 EA_RemoveText = 'R'; 00044 00045 namespace Kate { 00046 00047 QTimer* SwapFile::s_timer = 0; 00048 00049 SwapFile::SwapFile(KateDocument *document) 00050 : QObject(document) 00051 , m_document(document) 00052 , m_trackingEnabled(false) 00053 , m_recovered(false) 00054 , m_modified(false) 00055 { 00056 // fixed version of serialisation 00057 m_stream.setVersion (QDataStream::Qt_4_6); 00058 00059 // conect the timer 00060 connect(syncTimer(), SIGNAL(timeout()), this, SLOT(writeFileToDisk()), Qt::DirectConnection); 00061 00062 // connecting the signals 00063 connect(&m_document->buffer(), SIGNAL(saved(const QString &)), this, SLOT(fileSaved(const QString&))); 00064 connect(&m_document->buffer(), SIGNAL(loaded(const QString &, bool)), this, SLOT(fileLoaded(const QString&))); 00065 connect(m_document, SIGNAL(configChanged()), this, SLOT(configChanged())); 00066 00067 configChanged(); 00068 } 00069 00070 SwapFile::~SwapFile() 00071 { 00072 removeSwapFile(); 00073 } 00074 00075 void SwapFile::configChanged() 00076 { 00077 setTrackingEnabled(!m_document->config()->swapFileNoSync()); 00078 } 00079 00080 void SwapFile::setTrackingEnabled(bool enable) 00081 { 00082 if (m_trackingEnabled == enable) { 00083 return; 00084 } 00085 00086 m_trackingEnabled = enable; 00087 00088 TextBuffer &buffer = m_document->buffer(); 00089 00090 if (m_trackingEnabled) { 00091 connect(&buffer, SIGNAL(editingStarted()), this, SLOT(startEditing())); 00092 connect(&buffer, SIGNAL(editingFinished()), this, SLOT(finishEditing())); 00093 00094 connect(&buffer, SIGNAL(lineWrapped(const KTextEditor::Cursor&)), this, SLOT(wrapLine(const KTextEditor::Cursor&))); 00095 connect(&buffer, SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); 00096 connect(&buffer, SIGNAL(textInserted(const KTextEditor::Cursor &, const QString &)), this, SLOT(insertText(const KTextEditor::Cursor &, const QString &))); 00097 connect(&buffer, SIGNAL(textRemoved(const KTextEditor::Range &, const QString &)), this, SLOT(removeText(const KTextEditor::Range &))); 00098 } else { 00099 disconnect(&buffer, SIGNAL(editingStarted()), this, SLOT(startEditing())); 00100 disconnect(&buffer, SIGNAL(editingFinished()), this, SLOT(finishEditing())); 00101 00102 disconnect(&buffer, SIGNAL(lineWrapped(const KTextEditor::Cursor&)), this, SLOT(wrapLine(const KTextEditor::Cursor&))); 00103 disconnect(&buffer, SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); 00104 disconnect(&buffer, SIGNAL(textInserted(const KTextEditor::Cursor &, const QString &)), this, SLOT(insertText(const KTextEditor::Cursor &, const QString &))); 00105 disconnect(&buffer, SIGNAL(textRemoved(const KTextEditor::Range &, const QString &)), this, SLOT(removeText(const KTextEditor::Range &))); 00106 } 00107 } 00108 00109 void SwapFile::fileClosed () 00110 { 00111 // remove old swap file, file is now closed 00112 removeSwapFile(); 00113 00114 // purge filename 00115 updateFileName(); 00116 } 00117 00118 void SwapFile::fileLoaded(const QString&) 00119 { 00120 // look for swap file 00121 if (!updateFileName()) 00122 return; 00123 00124 if (!m_swapfile.exists()) 00125 { 00126 kDebug (13020) << "No swap file"; 00127 return; 00128 } 00129 00130 if (!QFileInfo(m_swapfile).isReadable()) 00131 { 00132 kWarning( 13020 ) << "Can't open swap file (missing permissions)"; 00133 return; 00134 } 00135 00136 // emit signal in case the document has more views 00137 emit swapFileFound(); 00138 } 00139 00140 void SwapFile::recover() 00141 { 00142 // if isOpen() returns true, the swap file likely changed already (appended data) 00143 // Example: The document was falsely marked as writable and the user changed 00144 // text even though the recover bar was visible. In this case, a replay of 00145 // the swap file across wrong document content would happen -> certainly wrong 00146 if (m_swapfile.isOpen()) { 00147 kWarning( 13020 ) << "Attempt to recover an already modified document. Aborting"; 00148 emit swapFileBroken(); 00149 return; 00150 } 00151 00152 // if the file doesn't exist, abort (user might have deleted it, or use two editor instances) 00153 if (!m_swapfile.open(QIODevice::ReadOnly)) 00154 { 00155 kWarning( 13020 ) << "Can't open swap file"; 00156 emit swapFileHandled(); 00157 return; 00158 } 00159 00160 // remember that the file has recovered 00161 m_recovered = true; 00162 00163 // open data stream 00164 m_stream.setDevice(&m_swapfile); 00165 00166 // replay the swap file 00167 recover(m_stream); 00168 00169 // close swap file 00170 m_stream.setDevice(0); 00171 m_swapfile.close(); 00172 00173 // emit signal in case the document has more views 00174 emit swapFileHandled(); 00175 } 00176 00177 bool SwapFile::recover(QDataStream& stream) 00178 { 00179 // read and check header 00180 QByteArray header; 00181 stream >> header; 00182 if (header != swapFileVersionString) 00183 { 00184 stream.setDevice (0); 00185 m_swapfile.close (); 00186 kWarning( 13020 ) << "Can't open swap file, wrong version"; 00187 return false; 00188 } 00189 00190 // disconnect current signals 00191 setTrackingEnabled(false); 00192 00193 // replay swapfile 00194 bool editStarted = false; 00195 bool brokenSwapFile = false; 00196 while (!stream.atEnd()) { 00197 if (brokenSwapFile) 00198 break; 00199 00200 qint8 type; 00201 stream >> type; 00202 switch (type) { 00203 case EA_StartEditing: { 00204 m_document->editStart(); 00205 editStarted = true; 00206 break; 00207 } 00208 case EA_FinishEditing: { 00209 m_document->editEnd(); 00210 editStarted = false; 00211 break; 00212 } 00213 case EA_WrapLine: { 00214 if (!editStarted) { 00215 brokenSwapFile = true; 00216 break; 00217 } 00218 00219 int line = 0, column = 0; 00220 stream >> line >> column; 00221 00222 // emulate buffer unwrapLine with document 00223 m_document->editWrapLine(line, column, true); 00224 break; 00225 } 00226 case EA_UnwrapLine: { 00227 if (!editStarted) { 00228 brokenSwapFile = true; 00229 break; 00230 } 00231 00232 int line = 0; 00233 stream >> line; 00234 00235 // assert valid line 00236 Q_ASSERT (line > 0); 00237 00238 // emulate buffer unwrapLine with document 00239 m_document->editUnWrapLine(line - 1, true, 0); 00240 break; 00241 } 00242 case EA_InsertText: { 00243 if (!editStarted) { 00244 brokenSwapFile = true; 00245 break; 00246 } 00247 00248 int line, column; 00249 QByteArray text; 00250 stream >> line >> column >> text; 00251 m_document->insertText(KTextEditor::Cursor(line, column), QString::fromUtf8 (text.data (), text.size())); 00252 break; 00253 } 00254 case EA_RemoveText: { 00255 if (!editStarted) { 00256 brokenSwapFile = true; 00257 break; 00258 } 00259 00260 int line, startColumn, endColumn; 00261 stream >> line >> startColumn >> endColumn; 00262 m_document->removeText (KTextEditor::Range(KTextEditor::Cursor(line, startColumn), KTextEditor::Cursor(line, endColumn))); 00263 break; 00264 } 00265 default: { 00266 kWarning( 13020 ) << "Unknown type:" << type; 00267 } 00268 } 00269 } 00270 00271 // balance editStart and editEnd 00272 if (editStarted) { 00273 brokenSwapFile = true; 00274 m_document->editEnd(); 00275 } 00276 00277 // warn the user if the swap file is not complete 00278 if (brokenSwapFile) { 00279 kWarning ( 13020 ) << "Some data might be lost"; 00280 emit swapFileBroken(); 00281 } 00282 00283 // reconnect the signals 00284 setTrackingEnabled(true); 00285 00286 return true; 00287 } 00288 00289 void SwapFile::fileSaved(const QString&) 00290 { 00291 m_modified = false; 00292 00293 // remove old swap file (e.g. if a file A was "saved as" B) 00294 removeSwapFile(); 00295 00296 // set the name for the new swap file 00297 updateFileName(); 00298 } 00299 00300 void SwapFile::startEditing () 00301 { 00302 // no swap file, no work 00303 if (m_swapfile.fileName().isEmpty()) 00304 return; 00305 00306 // if swap file doesn't exists, open it in WriteOnly mode 00307 // if it does, append the data to the existing swap file, 00308 // in case you recover and start edititng again 00309 if (!m_swapfile.exists()) { 00310 // TODO set file as read-only 00311 m_swapfile.open(QIODevice::WriteOnly); 00312 m_stream.setDevice(&m_swapfile); 00313 00314 // write file header 00315 m_stream << QByteArray (swapFileVersionString); 00316 } else if (m_stream.device() == 0) { 00317 m_swapfile.open(QIODevice::Append); 00318 m_stream.setDevice(&m_swapfile); 00319 } 00320 00321 // format: qint8 00322 m_stream << EA_StartEditing; 00323 } 00324 00325 void SwapFile::finishEditing () 00326 { 00327 // skip if not open 00328 if (!m_swapfile.isOpen ()) 00329 return; 00330 00331 // write the file to the disk every 15 seconds 00332 if (!syncTimer()->isActive()) 00333 syncTimer()->start(15000); 00334 00335 // format: qint8 00336 m_stream << EA_FinishEditing; 00337 m_swapfile.flush(); 00338 } 00339 00340 void SwapFile::wrapLine (const KTextEditor::Cursor &position) 00341 { 00342 // skip if not open 00343 if (!m_swapfile.isOpen ()) 00344 return; 00345 00346 // format: qint8, int, int 00347 m_stream << EA_WrapLine << position.line() << position.column(); 00348 00349 m_modified = true; 00350 } 00351 00352 void SwapFile::unwrapLine (int line) 00353 { 00354 // skip if not open 00355 if (!m_swapfile.isOpen ()) 00356 return; 00357 00358 // format: qint8, int 00359 m_stream << EA_UnwrapLine << line; 00360 00361 m_modified = true; 00362 } 00363 00364 void SwapFile::insertText (const KTextEditor::Cursor &position, const QString &text) 00365 { 00366 // skip if not open 00367 if (!m_swapfile.isOpen ()) 00368 return; 00369 00370 // format: qint8, int, int, bytearray 00371 m_stream << EA_InsertText << position.line() << position.column() << text.toUtf8 (); 00372 00373 m_modified = true; 00374 } 00375 00376 void SwapFile::removeText (const KTextEditor::Range &range) 00377 { 00378 // skip if not open 00379 if (!m_swapfile.isOpen ()) 00380 return; 00381 00382 // format: qint8, int, int, int 00383 Q_ASSERT (range.start().line() == range.end().line()); 00384 m_stream << EA_RemoveText 00385 << range.start().line() << range.start().column() 00386 << range.end().column(); 00387 00388 m_modified = true; 00389 } 00390 00391 bool SwapFile::shouldRecover() const 00392 { 00393 // should not recover if the file has already recovered in another view 00394 if (m_recovered) 00395 return false; 00396 00397 return !m_swapfile.fileName().isEmpty() && m_swapfile.exists() && m_stream.device() == 0; 00398 } 00399 00400 void SwapFile::discard() 00401 { 00402 removeSwapFile(); 00403 emit swapFileHandled(); 00404 } 00405 00406 void SwapFile::removeSwapFile() 00407 { 00408 if (!m_swapfile.fileName().isEmpty() && m_swapfile.exists()) { 00409 m_stream.setDevice(0); 00410 m_swapfile.close(); 00411 m_swapfile.remove(); 00412 } 00413 } 00414 00415 bool SwapFile::updateFileName() 00416 { 00417 // first clear filename 00418 m_swapfile.setFileName (""); 00419 00420 // get the new path 00421 QString path = fileName(); 00422 if (path.isNull()) 00423 return false; 00424 00425 m_swapfile.setFileName(path); 00426 return true; 00427 } 00428 00429 QString SwapFile::fileName() 00430 { 00431 const KUrl &url = m_document->url(); 00432 if (url.isEmpty() || !url.isLocalFile()) 00433 return QString(); 00434 00435 QString path = url.toLocalFile(); 00436 int poz = path.lastIndexOf(QDir::separator()); 00437 path.insert(poz+1, "."); 00438 path.append(".kate-swp"); 00439 00440 return path; 00441 } 00442 00443 QTimer* SwapFile::syncTimer() 00444 { 00445 if (s_timer == 0) { 00446 s_timer = new QTimer(QApplication::instance()); 00447 s_timer->setSingleShot(true); 00448 } 00449 00450 return s_timer; 00451 } 00452 00453 void SwapFile::writeFileToDisk() 00454 { 00455 if (m_modified) { 00456 m_modified = false; 00457 00458 #ifndef Q_OS_WIN 00459 // ensure that the file is written to disk 00460 #ifdef HAVE_FDATASYNC 00461 fdatasync (m_swapfile.handle()); 00462 #else 00463 fsync (m_swapfile.handle()); 00464 #endif 00465 #endif 00466 } 00467 } 00468 00469 } 00470 00471 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference