New feature: Log Mornitoring (tail -f)

This feature allows users to monitor log files' writing, as Unix "tail -f"
command.
Here are the conditions of monitoring:
1. file to monitor should exist.
2. file will be set as readonly during monitoring.
3. each update will scroll to the last line.
This commit is contained in:
Don HO 2016-04-24 11:29:05 +08:00
parent 50c7e228ff
commit 2ff03fe250
14 changed files with 235 additions and 177 deletions

View File

@ -58,7 +58,7 @@ enum tb_stat {tb_saved, tb_unsaved, tb_ro};
#define NPP_INTERNAL_FUCTION_STR TEXT("Notepad++::InternalFunction")
int docTabIconIDs[] = {IDI_SAVED_ICON, IDI_UNSAVED_ICON, IDI_READONLY_ICON};
int docTabIconIDs[] = {IDI_SAVED_ICON, IDI_UNSAVED_ICON, IDI_READONLY_ICON, IDI_MONITORING_ICON};
ToolBarButtonUnit toolBarIcons[] = {
{IDM_FILE_NEW, IDI_NEW_OFF_ICON, IDI_NEW_ON_ICON, IDI_NEW_OFF_ICON, IDR_FILENEW},
@ -112,6 +112,7 @@ ToolBarButtonUnit toolBarIcons[] = {
{IDM_VIEW_DOC_MAP, IDI_VIEW_UD_DLG_OFF_ICON, IDI_VIEW_UD_DLG_ON_ICON, IDI_VIEW_UD_DLG_OFF_ICON, IDR_DOCMAP},
{IDM_VIEW_FUNC_LIST, IDI_VIEW_UD_DLG_OFF_ICON, IDI_VIEW_UD_DLG_ON_ICON, IDI_VIEW_UD_DLG_OFF_ICON, IDR_FUNC_LIST},
{IDM_VIEW_FILEBROWSER, IDI_VIEW_UD_DLG_OFF_ICON, IDI_VIEW_UD_DLG_ON_ICON, IDI_VIEW_UD_DLG_OFF_ICON, IDR_FILEBROWSER},
{IDM_VIEW_MONITORING, IDI_VIEW_UD_DLG_OFF_ICON, IDI_VIEW_UD_DLG_ON_ICON, IDI_VIEW_UD_DLG_OFF_ICON, IDR_FILEMONITORING},
//-------------------------------------------------------------------------------------//
{0, IDI_SEPARATOR_ICON, IDI_SEPARATOR_ICON, IDI_SEPARATOR_ICON, IDI_SEPARATOR_ICON},
@ -1923,6 +1924,11 @@ void Notepad_plus::checkDocState()
if (_pAnsiCharPanel)
_pAnsiCharPanel->switchEncoding();
enableCommand(IDM_VIEW_MONITORING, not curBuf->isUntitled(), MENU | TOOLBAR);
enableCommand(IDM_EDIT_SETREADONLY, not curBuf->isMonitoringOn(), MENU);
checkMenuItem(IDM_VIEW_MONITORING, curBuf->isMonitoringOn());
_toolBar.setCheck(IDM_VIEW_MONITORING, curBuf->isMonitoringOn());
}
void Notepad_plus::checkUndoState()
@ -3521,19 +3527,25 @@ void Notepad_plus::docGotoAnotherEditView(FileTransferMode mode)
showView(viewToGo);
}
bool monitoringWasOn = false;
//Close the document if we transfered the document instead of cloning it
if (mode == TransferMove)
{
Buffer *buf = MainFileManager->getBufferByID(current);
monitoringWasOn = buf->isMonitoringOn();
//just close the activate document, since thats the one we moved (no search)
doClose(_pEditView->getCurrentBufferID(), currentView());
/*
if (noOpenedDoc())
::SendMessage(_pPublicInterface->getHSelf(), WM_CLOSE, 0, 0);
*/
} // else it was cone, so leave it
//Activate the other view since thats where the document went
switchEditViewTo(viewToGo);
if (monitoringWasOn)
{
command(IDM_VIEW_MONITORING);
}
}
bool Notepad_plus::activateBuffer(BufferID id, int whichOne)

View File

@ -186,7 +186,6 @@ struct VisibleGUIConf final
}
};
class FileDialog;
class Notepad_plus_Window;
class AnsiCharPanel;
@ -643,8 +642,15 @@ private:
}
static DWORD WINAPI backupDocument(void *params);
//static DWORD WINAPI monitorFileOnChange(void * params);
//static DWORD WINAPI monitorDirectoryOnChange(void * params);
static DWORD WINAPI monitorFileOnChange(void * params);
struct MonitorInfo final {
MonitorInfo(Buffer *buf, ScintillaEditView *mainEditorView, ScintillaEditView *subEditorView) :
_buffer(buf), _mainEditorView(mainEditorView), _subEditorView(subEditorView) {};
Buffer *_buffer = nullptr;
ScintillaEditView *_mainEditorView = nullptr;
ScintillaEditView *_subEditorView = nullptr;
};
};

File diff suppressed because one or more lines are too long

View File

@ -1754,6 +1754,48 @@ void Notepad_plus::command(int id)
}
break;
case IDM_VIEW_MONITORING:
{
static HANDLE hThread = nullptr;
Buffer * curBuf = _pEditView->getCurrentBuffer();
if (curBuf->isMonitoringOn())
{
curBuf->stopMonitoring();
::CloseHandle(hThread);
hThread = nullptr;
checkMenuItem(IDM_VIEW_MONITORING, false);
_toolBar.setCheck(IDM_VIEW_MONITORING, false);
curBuf->setUserReadOnly(false);
}
else
{
const TCHAR *longFileName = curBuf->getFullPathName();
if (::PathFileExists(longFileName))
{
if (curBuf->isDirty())
{
::MessageBox(_pPublicInterface->getHSelf(), TEXT("The document is dirty. Please save the modification before monitoring it."), TEXT("Monitoring problem"), MB_OK);
}
else
{
curBuf->startMonitoring(); // monitoring firstly for making monitoring icon
curBuf->setUserReadOnly(true);
MonitorInfo *monitorInfo = new MonitorInfo(curBuf, &_mainEditView, &_subEditView);
hThread = ::CreateThread(NULL, 0, monitorFileOnChange, (void *)monitorInfo, 0, NULL); // will be deallocated while quitting thread
checkMenuItem(IDM_VIEW_MONITORING, true);
_toolBar.setCheck(IDM_VIEW_MONITORING, true);
}
}
else
{
::MessageBox(_pPublicInterface->getHSelf(), TEXT("The file should exist to be monitored."), TEXT("Monitoring problem"), MB_OK);
}
}
break;
}
case IDM_EXECUTE:
{
bool isFirstTime = !_runDlg.isCreated();

View File

@ -33,121 +33,97 @@
#include "EncodingMapper.h"
#include "VerticalFileSwitcher.h"
#include "functionListPanel.h"
#include "ReadDirectoryChanges.h"
#include <tchar.h>
using namespace std;
/*
struct monitorFileParams {
WCHAR _fullFilePath[MAX_PATH];
};
DWORD WINAPI Notepad_plus::monitorFileOnChange(void * params)
{
monitorFileParams *mfp = (monitorFileParams *)params;
MonitorInfo *monitorInfo = (MonitorInfo *)params;
Buffer *buf = monitorInfo->_buffer;
ScintillaEditView *mainEditorView = monitorInfo->_mainEditorView;
ScintillaEditView *subEditorView = monitorInfo->_subEditorView;
//Le répertoire à surveiller :
const TCHAR *fullFileName = (const TCHAR *)buf->getFullPathName();
//The folder to watch :
WCHAR folderToMonitor[MAX_PATH];
//::MessageBoxW(NULL, mfp->_fullFilePath, TEXT("PATH AFTER thread"), MB_OK);
lstrcpy(folderToMonitor, mfp->_fullFilePath);
lstrcpy(folderToMonitor, fullFileName);
//MessageBox(NULL, fullFileName, TEXT("fullFileName"), MB_OK);
::PathRemoveFileSpecW(folderToMonitor);
//::PathRemoveFileSpecW(folderToMonitor);
//MessageBox(NULL, folderToMonitor, TEXT("folderToMonitor"), MB_OK);
HANDLE hDirectory = ::CreateFile(folderToMonitor,
FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
const DWORD dwNotificationFlags = FILE_NOTIFY_CHANGE_LAST_WRITE;
// buffer qui va récuppérer les informations de mise à jour
const int MAX_BUFFER = 1024;
BYTE buffer[MAX_BUFFER];
DWORD nombreDeByteRetournes = 0;
// Create the monitor and add directory to watch.
CReadDirectoryChanges changes;
changes.AddDirectory(folderToMonitor, true, dwNotificationFlags);
bool cond = true;
while (cond)
HANDLE changeHandles[] = { buf->getMonitoringEvent(), changes.GetWaitHandle() };
bool toBeContinued = true;
while (toBeContinued)
{
::Sleep(1000);
// surveille un changement dans le répertoire : il attend tant que rien ne se passe : cest synchrone.
BOOL res = ReadDirectoryChangesW(hDirectory, buffer, MAX_BUFFER,
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE, &nombreDeByteRetournes, NULL, NULL);
if (res == FALSE)
continue;
// puis on transforme le buffer pour être lisible.
FILE_NOTIFY_INFORMATION *notifyInfo = (FILE_NOTIFY_INFORMATION *)buffer;
wchar_t fn[MAX_PATH];
memset(fn, 0, MAX_PATH*sizeof(wchar_t));
if (notifyInfo->Action != FILE_ACTION_MODIFIED)
continue;
// affiche le fichier qui a été modifié.
if (notifyInfo->FileNameLength <= 0)
continue;
TCHAR str2Display[512];
generic_strncpy(fn, notifyInfo->FileName, notifyInfo->FileNameLength / sizeof(wchar_t));
generic_sprintf(str2Display, TEXT("offset : %d\raction : %d\rfn len : %d\rfn : %s"), notifyInfo->NextEntryOffset, notifyInfo->Action, notifyInfo->FileNameLength, notifyInfo->FileName);
// on peut vérifier avec ceci :
//printInt(notifyInfo->NextEntryOffset);
//printInt(notifyInfo->FileNameLength);
MessageBox(NULL, str2Display, TEXT("name"), MB_OK);
}
return TRUE;
}
DWORD WINAPI Notepad_plus::monitorDirectoryOnChange(void * params)
DWORD waitStatus = ::WaitForMultipleObjects(_countof(changeHandles), changeHandles, FALSE, INFINITE);
switch (waitStatus)
{
monitorFileParams *mfp = (monitorFileParams *)params;
case WAIT_OBJECT_0 + 0:
// Mutex was signaled. User removes this folder or file browser is closed
toBeContinued = false;
break;
//Le répertoire à surveiller :
WCHAR folderToMonitor[MAX_PATH];
//::MessageBoxW(NULL, mfp->_fullFilePath, TEXT("PATH AFTER thread"), MB_OK);
lstrcpy(folderToMonitor, mfp->_fullFilePath);
//::PathRemoveFileSpecW(folderToMonitor);
HANDLE hDirectory = ::CreateFile(folderToMonitor,
FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
// buffer qui va récuppérer les informations de mise à jour
const int MAX_BUFFER = 1024;
BYTE buffer[MAX_BUFFER];
DWORD nombreDeByteRetournes = 0;
bool cond = true;
while (cond)
case WAIT_OBJECT_0 + 1:
// We've received a notification in the queue.
{
::Sleep(1000);
// surveille un changement dans le répertoire : il attend tant que rien ne se passe : cest synchrone.
ReadDirectoryChangesW(hDirectory, buffer, MAX_BUFFER,
TRUE, FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &nombreDeByteRetournes, NULL, NULL);
// puis on transforme le buffer pour être lisible.
FILE_NOTIFY_INFORMATION *notifyInfo = (FILE_NOTIFY_INFORMATION *)buffer;
DWORD dwAction;
CStringW wstrFilename;
if (changes.CheckOverflow())
printStr(L"Queue overflowed.");
else
{
changes.Pop(dwAction, wstrFilename);
wchar_t fn[MAX_PATH];
memset(fn, 0, MAX_PATH*sizeof(wchar_t));
if (dwAction == FILE_ACTION_MODIFIED && lstrcmp(fullFileName, wstrFilename.GetString()) == 0)
{
MainFileManager->reloadBuffer(buf->getID());
buf->updateTimeStamp();
//if (notifyInfo->Action != FILE_ACTION_MODIFIED)
if (notifyInfo->Action != FILE_ACTION_ADDED && notifyInfo->Action != FILE_ACTION_REMOVED && notifyInfo->Action != FILE_ACTION_RENAMED_OLD_NAME && notifyInfo->Action != FILE_ACTION_RENAMED_NEW_NAME)
continue;
// affiche le fichier qui a été modifié.
//if (notifyInfo->FileNameLength <= 0)
//continue;
generic_strncpy(fn, notifyInfo->FileName, notifyInfo->FileNameLength / sizeof(wchar_t));
// on peut vérifier avec ceci :
//printInt(notifyInfo->FileNameLength);
MessageBox(NULL, fn, TEXT("name"), MB_OK);
// not only test main view
if (buf == mainEditorView->getCurrentBuffer())
{
int lastLineToShow = mainEditorView->execute(SCI_GETLINECOUNT);
mainEditorView->scroll(0, lastLineToShow);
}
return TRUE;
// but also test sub-view, because the buffer could be clonned
if (buf == subEditorView->getCurrentBuffer())
{
int lastLineToShow = subEditorView->execute(SCI_GETLINECOUNT);
subEditorView->scroll(0, lastLineToShow);
}
}
}
}
break;
case WAIT_IO_COMPLETION:
// Nothing to do.
break;
}
}
// Just for sample purposes. The destructor will
// call Terminate() automatically.
changes.Terminate();
//MessageBox(NULL, TEXT("FREEDOM !!!"), TEXT("out"), MB_OK);
delete monitorInfo;
return EXIT_SUCCESS;
}
*/
BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, bool isReadOnly, int encoding, const TCHAR *backupFileName, time_t fileNameTimestamp)
{
@ -368,20 +344,6 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
_pluginsManager.notify(&scnN);
if (_pFileSwitcherPanel)
_pFileSwitcherPanel->newItem(buf, currentView());
/*
if (::PathFileExists(longFileName))
{
// Thread to
monitorFileParams *params = new monitorFileParams;
lstrcpy(params->_fullFilePath, longFileName);
//::MessageBoxW(NULL, params._fullFilePath, TEXT("PATH b4 thread"), MB_OK);
//HANDLE hThread = ::CreateThread(NULL, 0, monitorFileOnChange, params, 0, NULL);
HANDLE hThread = ::CreateThread(NULL, 0, monitorFileOnChange, params, 0, NULL);
::CloseHandle(hThread);
}
*/
}
else
{
@ -671,6 +633,12 @@ void Notepad_plus::doClose(BufferID id, int whichOne, bool doDeleteBackup)
int nrDocs = whichOne==MAIN_VIEW?(_mainDocTab.nbItem()):(_subDocTab.nbItem());
if (buf->isMonitoringOn())
{
// turn off monitoring
command(IDM_VIEW_MONITORING);
}
//Do all the works
bool isBufRemoved = removeBufferFromView(id, whichOne);
BufferID hiddenBufferID = BUFFER_INVALID;

View File

@ -454,7 +454,7 @@ BOOL Notepad_plus::notify(SCNotification *notification)
_tabPopupMenu.checkItem(IDM_EDIT_SETREADONLY, isUserReadOnly);
bool isSysReadOnly = buf->getFileReadOnly();
_tabPopupMenu.enableItem(IDM_EDIT_SETREADONLY, !isSysReadOnly);
_tabPopupMenu.enableItem(IDM_EDIT_SETREADONLY, not isSysReadOnly && not buf->isMonitoringOn());
_tabPopupMenu.enableItem(IDM_EDIT_CLEARREADONLY, isSysReadOnly);
bool isFileExisting = PathFileExists(buf->getFullPathName()) != FALSE;

View File

@ -337,10 +337,25 @@ public:
_isLoadedDirty = val;
}
void startMonitoring() {
_isMonitoringOn = true;
_eventHandle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
};
private:
HANDLE getMonitoringEvent() const {
return _eventHandle;
};
void stopMonitoring() {
_isMonitoringOn = false;
::SetEvent(_eventHandle);
::CloseHandle(_eventHandle);
};
bool isMonitoringOn() const { return _isMonitoringOn; };
void updateTimeStamp();
private:
int indexOfReference(const ScintillaEditView * identifier) const;
void setStatus(DocFileStatus status)
@ -395,4 +410,8 @@ private:
generic_string _backupFileName;
bool _isModified = false;
bool _isLoadedDirty = false; // it's the indicator for finding buffer's initial state
// For the monitoring
HANDLE _eventHandle = nullptr;
bool _isMonitoringOn = false;
};

View File

@ -145,7 +145,11 @@ void DocTabView::bufferUpdated(Buffer * buffer, int mask)
{
tie.mask |= TCIF_IMAGE;
tie.iImage = buffer->isDirty()?UNSAVED_IMG_INDEX:SAVED_IMG_INDEX;
if (buffer->isReadOnly())
if (buffer->isMonitoringOn())
{
tie.iImage = MONITORING_IMG_INDEX;
}
else if (buffer->isReadOnly())
{
tie.iImage = REDONLY_IMG_INDEX;
}

View File

@ -40,6 +40,7 @@
const int SAVED_IMG_INDEX = 0;
const int UNSAVED_IMG_INDEX = 1;
const int REDONLY_IMG_INDEX = 2;
const int MONITORING_IMG_INDEX = 3;
class DocTabView : public TabBarPlus
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

View File

@ -333,6 +333,7 @@
#define IDM_VIEW_TAB9 (IDM_VIEW + 94)
#define IDM_VIEW_TAB_NEXT (IDM_VIEW + 95)
#define IDM_VIEW_TAB_PREV (IDM_VIEW + 96)
#define IDM_VIEW_MONITORING (IDM_VIEW + 97)
#define IDM_VIEW_GOTO_ANOTHER_VIEW 10001
#define IDM_VIEW_CLONE_TO_ANOTHER_VIEW 10002

View File

@ -131,6 +131,7 @@
#define IDI_UNSAVED_ICON 502
#define IDI_READONLY_ICON 503
#define IDI_FIND_RESULT_ICON 504
#define IDI_MONITORING_ICON 505
#define IDI_PROJECT_WORKSPACE 601
#define IDI_PROJECT_WORKSPACEDIRTY 602
@ -192,7 +193,6 @@
#define IDR_CLOSETAB_INACT 1531
#define IDR_CLOSETAB_HOVER 1532
#define IDR_CLOSETAB_PUSH 1533
#define IDR_FUNC_LIST_ICO 1534
#define IDR_DOCMAP_ICO 1535
#define IDR_PROJECTPANEL_ICO 1536
@ -200,6 +200,8 @@
#define IDR_ASCIIPANEL_ICO 1538
#define IDR_DOCSWITCHER_ICO 1539
#define IDR_FILEBROWSER_ICO 1540
#define IDR_FILEMONITORING 1541
#define ID_MACRO 20000
#define ID_MACRO_LIMIT 20200