• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal