/* Specs and Algorithm of session snapshot & periodic backup system: Notepad++ quits without asking for saving unsaved file. It restores all the unsaved files and document as the states they left. For existing file (c:\tmp\foo.h) - Open In the next session, Notepad++ 1. load backup\FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) if exist, otherwise load FILENAME (c:\tmp\foo.h). 2. if backup\FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) is loaded, set it dirty (red). 3. if backup\FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) is loaded, last modif timestamp of FILENAME (c:\tmp\foo.h), compare with tracked timestamp (in session.xml). 4. in the case of unequal result, tell user the FILENAME (c:\tmp\foo.h) was modified. ask user if he want to reload FILENAME(c:\tmp\foo.h) - Editing when a file starts being modified, a file will be created with name: FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) the Buffer object will associate with this FILENAME@CREATION_TIMESTAMP file (backup\foo.h@198776). 1. sync: (each 3-5 second) backup file will be saved, if buffer is dirty, and modification is present (a bool on modified notificatin). 2. sync: each save file, or close file, the backup file will be deleted (if buffer is not dirty). 3. before switch off to another tab (or close files on exit), check 1 & 2 (sync with backup). - Close In the current session, Notepad++ 1. track FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) if exist (in session.xml). 2. track last modified timestamp of FILENAME (c:\tmp\foo.h) if FILENAME@CREATION_TIMESTAMP (backup\foo.h@198776) was tracked (in session.xml). For untitled document (new 4) - Open In the next session, Notepad++ 1. open file UNTITLED_NAME@CREATION_TIMESTAMP (backup\new 4@198776) 2. set label as UNTITLED_NAME (new 4) and disk icon as red. - Editing when a untitled document starts being modified, a backup file will be created with name: UNTITLED_NAME@CREATION_TIMESTAMP (backup\new 4@198776) the Buffer object will associate with this UNTITLED_NAME@CREATION_TIMESTAMP file (backup\new 4@198776). 1. sync: (each 3-5 second) backup file will be saved, if buffer is dirty, and modification is present (a bool on modified notificatin). 2. sync: if untitled document is saved, or closed, the backup file will be deleted. 3. before switch off to another tab (or close documents on exit), check 1 & 2 (sync with backup). - CLOSE In the current session, Notepad++ 1. track UNTITLED_NAME@CREATION_TIMESTAMP (backup\new 4@198776) in session.xml. */ bool FileManager::backupCurrentBuffer() { LongRunningOperation op; Buffer* buffer = _pNotepadPlus->getCurrentBuffer(); bool result = false; bool hasModifForSession = false; if (buffer->isDirty()) { if (buffer->isModified()) // buffer dirty and modified, write the backup file { // Synchronization // This method is called from 2 differents place, so synchronization is important HANDLE writeEvent = ::OpenEvent(EVENT_ALL_ACCESS, TRUE, TEXT("nppWrittingEvent")); if (!writeEvent) { // no thread yet, create a event with non-signaled, to block all threads writeEvent = ::CreateEvent(NULL, TRUE, FALSE, TEXT("nppWrittingEvent")); } else { if (::WaitForSingleObject(writeEvent, INFINITE) != WAIT_OBJECT_0) { // problem!!! printStr(TEXT("WaitForSingleObject problem in backupCurrentBuffer()!")); return false; } // unlocled here, set to non-signaled state, to block all threads ::ResetEvent(writeEvent); } UniMode mode = buffer->getUnicodeMode(); if (mode == uniCookie) mode = uni8Bit; //set the mode to ANSI to prevent converter from adding BOM and performing conversions, Scintilla's data can be copied directly Utf8_16_Write UnicodeConvertor; UnicodeConvertor.setEncoding(mode); int encoding = buffer->getEncoding(); generic_string backupFilePath = buffer->getBackupFileName(); if (backupFilePath.empty()) { // Create file backupFilePath = NppParameters::getInstance()->getUserPath(); backupFilePath += TEXT("\\backup\\"); // if "backup" folder doesn't exist, create it. if (!PathFileExists(backupFilePath.c_str())) { ::CreateDirectory(backupFilePath.c_str(), NULL); } backupFilePath += buffer->getFileName(); const int temBufLen = 32; TCHAR tmpbuf[temBufLen]; time_t ltime = time(0); struct tm* today = localtime(<ime); generic_strftime(tmpbuf, temBufLen, TEXT("%Y-%m-%d_%H%M%S"), today); backupFilePath += TEXT("@"); backupFilePath += tmpbuf; // Set created file name in buffer buffer->setBackupFileName(backupFilePath); // Session changes, save it hasModifForSession = true; } TCHAR fullpath[MAX_PATH]; ::GetFullPathName(backupFilePath.c_str(), MAX_PATH, fullpath, NULL); ::GetLongPathName(fullpath, fullpath, MAX_PATH); // Make sure the backup file is not read only DWORD dwFileAttribs = ::GetFileAttributes(fullpath); if (dwFileAttribs & FILE_ATTRIBUTE_READONLY) // if file is read only, remove read only attribute { dwFileAttribs ^= FILE_ATTRIBUTE_READONLY; ::SetFileAttributes(fullpath, dwFileAttribs); } FILE *fp = UnicodeConvertor.fopen(fullpath, TEXT("wb")); if (fp) { int lengthDoc = _pNotepadPlus->_pEditView->getCurrentDocLen(); char* buf = (char*)_pNotepadPlus->_pEditView->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer size_t items_written = 0; if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write { items_written = UnicodeConvertor.fwrite(buf, lengthDoc); if (lengthDoc == 0) items_written = 1; } else { WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); int grabSize; for (int i = 0; i < lengthDoc; i += grabSize) { grabSize = lengthDoc - i; if (grabSize > blockSize) grabSize = blockSize; int newDataLen = 0; int incompleteMultibyteChar = 0; const char *newData = wmc->encode(SC_CP_UTF8, encoding, buf+i, grabSize, &newDataLen, &incompleteMultibyteChar); grabSize -= incompleteMultibyteChar; items_written = UnicodeConvertor.fwrite(newData, newDataLen); } if (lengthDoc == 0) items_written = 1; } UnicodeConvertor.fclose(); // Note that fwrite() doesn't return the number of bytes written, but rather the number of ITEMS. if(items_written == 1) // backup file has been saved { buffer->setModifiedStatus(false); result = true; //all done } } // set to signaled state if (::SetEvent(writeEvent) == NULL) { printStr(TEXT("oups!")); } // printStr(TEXT("Event released!")); ::CloseHandle(writeEvent); } else // buffer dirty but unmodified { result = true; } } else // buffer not dirty, sync: delete the backup file { generic_string backupFilePath = buffer->getBackupFileName(); if (not backupFilePath.empty()) { // delete backup file generic_string file2Delete = buffer->getBackupFileName(); buffer->setBackupFileName(generic_string()); result = (::DeleteFile(file2Delete.c_str()) != 0); // Session changes, save it hasModifForSession = true; } //printStr(TEXT("backup deleted in backupCurrentBuffer")); result = true; // no backup file to delete } //printStr(TEXT("backup sync")); if (result && hasModifForSession) { //printStr(buffer->getBackupFileName().c_str()); _pNotepadPlus->saveCurrentSession(); } return result; }
bool FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool isCopy, generic_string * error_msg) { Buffer * buffer = getBufferByID(id); bool isHidden = false; bool isSys = false; DWORD attrib = 0; TCHAR fullpath[MAX_PATH]; ::GetFullPathName(filename, MAX_PATH, fullpath, NULL); ::GetLongPathName(fullpath, fullpath, MAX_PATH); if (PathFileExists(fullpath)) { attrib = ::GetFileAttributes(fullpath); if (attrib != INVALID_FILE_ATTRIBUTES) { isHidden = (attrib & FILE_ATTRIBUTE_HIDDEN) != 0; if (isHidden) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_HIDDEN); isSys = (attrib & FILE_ATTRIBUTE_SYSTEM) != 0; if (isSys) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_SYSTEM); } } UniMode mode = buffer->getUnicodeMode(); if (mode == uniCookie) mode = uni8Bit; //set the mode to ANSI to prevent converter from adding BOM and performing conversions, Scintilla's data can be copied directly Utf8_16_Write UnicodeConvertor; UnicodeConvertor.setEncoding(mode); int encoding = buffer->getEncoding(); FILE *fp = UnicodeConvertor.fopen(fullpath, TEXT("wb")); if (fp) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document int lengthDoc = _pscratchTilla->getCurrentDocLen(); char* buf = (char*)_pscratchTilla->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer size_t items_written = 0; if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write { items_written = UnicodeConvertor.fwrite(buf, lengthDoc); if (lengthDoc == 0) items_written = 1; } else { WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); int grabSize; for (int i = 0; i < lengthDoc; i += grabSize) { grabSize = lengthDoc - i; if (grabSize > blockSize) grabSize = blockSize; int newDataLen = 0; int incompleteMultibyteChar = 0; const char *newData = wmc->encode(SC_CP_UTF8, encoding, buf+i, grabSize, &newDataLen, &incompleteMultibyteChar); grabSize -= incompleteMultibyteChar; items_written = UnicodeConvertor.fwrite(newData, newDataLen); } if (lengthDoc == 0) items_written = 1; } UnicodeConvertor.fclose(); // Error, we didn't write the entire document to disk. // Note that fwrite() doesn't return the number of bytes written, but rather the number of ITEMS. if(items_written != 1) { if(error_msg != NULL) *error_msg = TEXT("Failed to save file.\nNot enough space on disk to save file?"); return false; } if (isHidden) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_HIDDEN); if (isSys) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_SYSTEM); if (isCopy) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); return true; // All done } buffer->setFileName(fullpath); buffer->setDirty(false); buffer->setStatus(DOC_REGULAR); buffer->checkFileState(); _pscratchTilla->execute(SCI_SETSAVEPOINT); //_pscratchTilla->markSavedLines(); _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); return true; } return false; }
bool FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool isCopy, generic_string * error_msg) { HANDLE writeEvent = ::OpenEvent(EVENT_ALL_ACCESS, TRUE, TEXT("nppWrittingEvent")); if (!writeEvent) { // no thread yet, create a event with non-signaled, to block all threads writeEvent = ::CreateEvent(NULL, TRUE, FALSE, TEXT("nppWrittingEvent")); } else { //printStr(TEXT("Locked. I wait.")); if (::WaitForSingleObject(writeEvent, INFINITE) != WAIT_OBJECT_0) { // problem!!! printStr(TEXT("WaitForSingleObject problem in saveBuffer()!")); return false; } // unlocled here, set to non-signaled state, to block all threads ::ResetEvent(writeEvent); } EventReset reset(writeEvent); // Will reset event in destructor. Buffer* buffer = getBufferByID(id); bool isHidden = false; bool isSys = false; DWORD attrib = 0; TCHAR fullpath[MAX_PATH]; ::GetFullPathName(filename, MAX_PATH, fullpath, NULL); ::GetLongPathName(fullpath, fullpath, MAX_PATH); if (PathFileExists(fullpath)) { attrib = ::GetFileAttributes(fullpath); if (attrib != INVALID_FILE_ATTRIBUTES) { isHidden = (attrib & FILE_ATTRIBUTE_HIDDEN) != 0; if (isHidden) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_HIDDEN); isSys = (attrib & FILE_ATTRIBUTE_SYSTEM) != 0; if (isSys) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_SYSTEM); } } UniMode mode = buffer->getUnicodeMode(); if (mode == uniCookie) mode = uni8Bit; //set the mode to ANSI to prevent converter from adding BOM and performing conversions, Scintilla's data can be copied directly Utf8_16_Write UnicodeConvertor; UnicodeConvertor.setEncoding(mode); int encoding = buffer->getEncoding(); FILE *fp = UnicodeConvertor.fopen(fullpath, TEXT("wb")); if (fp) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document int lengthDoc = _pscratchTilla->getCurrentDocLen(); char* buf = (char*)_pscratchTilla->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer size_t items_written = 0; if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write { items_written = UnicodeConvertor.fwrite(buf, lengthDoc); if (lengthDoc == 0) items_written = 1; } else { WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); int grabSize; for (int i = 0; i < lengthDoc; i += grabSize) { grabSize = lengthDoc - i; if (grabSize > blockSize) grabSize = blockSize; int newDataLen = 0; int incompleteMultibyteChar = 0; const char *newData = wmc->encode(SC_CP_UTF8, encoding, buf+i, grabSize, &newDataLen, &incompleteMultibyteChar); grabSize -= incompleteMultibyteChar; items_written = UnicodeConvertor.fwrite(newData, newDataLen); } if (lengthDoc == 0) items_written = 1; } UnicodeConvertor.fclose(); // Error, we didn't write the entire document to disk. // Note that fwrite() doesn't return the number of bytes written, but rather the number of ITEMS. if(items_written != 1) { if(error_msg != NULL) *error_msg = TEXT("Failed to save file.\nNot enough space on disk to save file?"); // set to signaled state via destructor EventReset. return false; } if (isHidden) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_HIDDEN); if (isSys) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_SYSTEM); if (isCopy) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); /* for saveAs it's not necessary since this action is for the "current" directory, so we let manage in SAVEPOINTREACHED event generic_string backupFilePath = buffer->getBackupFileName(); if (not backupFilePath.empty()) { // delete backup file generic_string file2Delete = buffer->getBackupFileName(); buffer->setBackupFileName(generic_string()); ::DeleteFile(file2Delete.c_str()); } */ // set to signaled state via destructor EventReset. return true; //all done } buffer->setFileName(fullpath); buffer->setDirty(false); buffer->setStatus(DOC_REGULAR); buffer->checkFileState(); _pscratchTilla->execute(SCI_SETSAVEPOINT); //_pscratchTilla->markSavedLines(); _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); generic_string backupFilePath = buffer->getBackupFileName(); if (not backupFilePath.empty()) { // delete backup file buffer->setBackupFileName(generic_string()); ::DeleteFile(backupFilePath.c_str()); } // set to signaled state via destructor EventReset. return true; } // set to signaled state via destructor EventReset. return false; }
bool FileManager::saveBuffer(BufferID id, const char * filename, bool isCopy) { Buffer * buffer = getBufferByID(id); bool isHidden = false; bool isSys = false; DWORD attrib; char fullpath[MAX_PATH]; ::GetFullPathName(filename, MAX_PATH, fullpath, NULL); ::GetLongPathName(fullpath, fullpath, MAX_PATH); if (PathFileExists(fullpath)) { attrib = ::GetFileAttributes(fullpath); if (attrib != INVALID_FILE_ATTRIBUTES) { isHidden = (attrib & FILE_ATTRIBUTE_HIDDEN) != 0; if (isHidden) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_HIDDEN); isSys = (attrib & FILE_ATTRIBUTE_SYSTEM) != 0; if (isSys) ::SetFileAttributes(filename, attrib & ~FILE_ATTRIBUTE_SYSTEM); } } UniMode mode = buffer->getUnicodeMode(); if (mode == uniCookie) mode = uni8Bit; //set the mode to ANSI to prevent converter from adding BOM and performing conversions, Scintilla's data can be copied directly Utf8_16_Write UnicodeConvertor; UnicodeConvertor.setEncoding(mode); FILE *fp = UnicodeConvertor.fopen(fullpath, "wb"); if (fp) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document char data[blockSize + 1]; int lengthDoc = _pscratchTilla->getCurrentDocLen(); for (int i = 0; i < lengthDoc; i += blockSize) { int grabSize = lengthDoc - i; if (grabSize > blockSize) grabSize = blockSize; _pscratchTilla->getText(data, i, i + grabSize); UnicodeConvertor.fwrite(data, grabSize); } UnicodeConvertor.fclose(); if (isHidden) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_HIDDEN); if (isSys) ::SetFileAttributes(fullpath, attrib | FILE_ATTRIBUTE_SYSTEM); if (isCopy) { _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); return true; //all done } buffer->setFileName(fullpath); buffer->setDirty(false); buffer->setStatus(DOC_REGULAR); _pscratchTilla->execute(SCI_SETSAVEPOINT); _pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault); return true; } return false; }