/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.smb.server.notify;
import java.util.Vector;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.server.filesys.DiskDeviceContext;
import org.alfresco.jlan.server.filesys.FileName;
import org.alfresco.jlan.server.filesys.NotifyChange;
import org.alfresco.jlan.smb.PacketType;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.jlan.smb.server.NTTransPacket;
import org.alfresco.jlan.smb.server.SMBSrvPacket;
import org.alfresco.jlan.smb.server.SMBSrvSession;
import org.alfresco.jlan.util.DataPacker;
/**
* Notify Change Handler Class
*
* @author gkspencer
*/
public class NotifyChangeHandler implements Runnable {
// Change notification request list and global filter mask
private NotifyRequestList m_notifyList;
private int m_globalNotifyMask;
// Associated disk device context
private DiskDeviceContext m_diskCtx;
// Change notification processing thread
private Thread m_procThread;
// Change events queue
private NotifyChangeEventList m_eventList;
// Debug output enable
private boolean m_debug = false;
// Shutdown request flag
private boolean m_shutdown;
/**
* Class constructor
*
* @param diskCtx DiskDeviceContext
*/
public NotifyChangeHandler(DiskDeviceContext diskCtx) {
// Save the associated disk context details
m_diskCtx = diskCtx;
// Allocate the events queue
m_eventList = new NotifyChangeEventList();
// Create the processing thread
m_procThread = new Thread(this);
m_procThread.setDaemon(true);
m_procThread.setName("Notify_" + m_diskCtx.getDeviceName());
m_procThread.start();
}
/**
* Add a request to the change notification list
*
* @param req NotifyRequest
*/
public final void addNotifyRequest(NotifyRequest req) {
// Check if the request list has been allocated
if ( m_notifyList == null)
m_notifyList = new NotifyRequestList();
// Add the request to the list
req.setDiskContext(m_diskCtx);
m_notifyList.addRequest(req);
// Regenerate the global notify change filter mask
m_globalNotifyMask = m_notifyList.getGlobalFilter();
}
/**
* Remove a request from the notify change request list
*
* @param req NotifyRequest
*/
public final void removeNotifyRequest(NotifyRequest req) {
removeNotifyRequest(req, true);
}
/**
* Remove a request from the notify change request list
*
* @param req NotifyRequest
* @param updateMask boolean
*/
public final void removeNotifyRequest(NotifyRequest req, boolean updateMask) {
// Check if the request list has been allocated
if ( m_notifyList == null)
return;
// Remove the request from the list
m_notifyList.removeRequest(req);
// Regenerate the global notify change filter mask
if ( updateMask == true)
m_globalNotifyMask = m_notifyList.getGlobalFilter();
}
/**
* Remove all notification requests owned by the specified session
*
* @param sess SMBSrvSession
*/
public final void removeNotifyRequests(SMBSrvSession sess) {
// Remove all requests owned by the session
m_notifyList.removeAllRequestsForSession(sess);
// Recalculate the global notify change filter mask
m_globalNotifyMask = m_notifyList.getGlobalFilter();
}
/**
* Determine if the filter has file name change notification, triggered if a file is created, renamed or deleted
*
* @return boolean
*/
public final boolean hasFileNameChange() {
return hasFilterFlag(NotifyChange.FileName);
}
/**
* Determine if the filter has directory name change notification, triggered if a directory is created or deleted.
*
* @return boolean
*/
public final boolean hasDirectoryNameChange() {
return hasFilterFlag(NotifyChange.DirectoryName);
}
/**
* Determine if the filter has attribute change notification
*
* @return boolean
*/
public final boolean hasAttributeChange() {
return hasFilterFlag(NotifyChange.Attributes);
}
/**
* Determine if the filter has file size change notification
*
* @return boolean
*/
public final boolean hasFileSizeChange() {
return hasFilterFlag(NotifyChange.Size);
}
/**
* Determine if the filter has last write time change notification
*
* @return boolean
*/
public final boolean hasFileWriteTimeChange() {
return hasFilterFlag(NotifyChange.LastWrite);
}
/**
* Determine if the filter has last access time change notification
*
* @return boolean
*/
public final boolean hasFileAccessTimeChange() {
return hasFilterFlag(NotifyChange.LastAccess);
}
/**
* Determine if the filter has creation time change notification
*
* @return boolean
*/
public final boolean hasFileCreateTimeChange() {
return hasFilterFlag(NotifyChange.Creation);
}
/**
* Determine if the filter has the security descriptor change notification
*
* @return boolean
*/
public final boolean hasSecurityDescriptorChange() {
return hasFilterFlag(NotifyChange.Security);
}
/**
* Check if debug output is enabled
*
* @return boolean
*/
public final boolean hasDebug() {
return m_debug;
}
/**
* Return the global notify filter mask
*
* @return int
*/
public final int getGlobalNotifyMask() {
return m_globalNotifyMask;
}
/**
* Return the notify request queue size
*
* @return int
*/
public final int getRequestQueueSize() {
return m_notifyList != null ? m_notifyList.numberOfRequests() : 0;
}
/**
* Check if the change filter has the specified flag enabled
*
* @param flag
* @return boolean
*/
private final boolean hasFilterFlag(int flag) {
return ( m_globalNotifyMask & flag) != 0 ? true : false;
}
/**
* File changed notification
*
* @param action int
* @param path String
*/
public final void notifyFileChanged(int action, String path) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasFileNameChange() == false)
return;
// Queue the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.FileName, action, path, false));
}
/**
* File/directory renamed notification
*
* @param oldName String
* @param newName String
*/
public final void notifyRename(String oldName, String newName) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || (hasFileNameChange() == false && hasDirectoryNameChange() == false))
return;
// Queue the change notification event
queueNotification(new NotifyChangeEvent(NotifyChange.FileName, NotifyChange.ActionRenamedNewName, oldName, newName, false));
}
/**
* Directory changed notification
*
* @param action int
* @param path String
*/
public final void notifyDirectoryChanged(int action, String path) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasDirectoryNameChange() == false)
return;
// Queue the change notification event
queueNotification(new NotifyChangeEvent(NotifyChange.DirectoryName, action, path, true));
}
/**
* Attributes changed notification
*
* @param path String
* @param isdir boolean
*/
public final void notifyAttributesChanged(String path, boolean isdir) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasAttributeChange() == false)
return;
// Queue the change notification event
queueNotification(new NotifyChangeEvent(NotifyChange.Attributes, NotifyChange.ActionModified, path, isdir));
}
/**
* File size changed notification
*
* @param path String
*/
public final void notifyFileSizeChanged(String path) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasFileSizeChange() == false)
return;
// Send the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.Size, NotifyChange.ActionModified, path, false));
}
/**
* Last write time changed notification
*
* @param path String
* @param isdir boolean
*/
public final void notifyLastWriteTimeChanged(String path, boolean isdir) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasFileWriteTimeChange() == false)
return;
// Send the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.LastWrite, NotifyChange.ActionModified, path, isdir));
}
/**
* Last access time changed notification
*
* @param path String
* @param isdir boolean
*/
public final void notifyLastAccessTimeChanged(String path, boolean isdir) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasFileAccessTimeChange() == false)
return;
// Send the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.LastAccess, NotifyChange.ActionModified, path, isdir));
}
/**
* Creation time changed notification
*
* @param path String
* @param isdir boolean
*/
public final void notifyCreationTimeChanged(String path, boolean isdir) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasFileCreateTimeChange() == false)
return;
// Send the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.Creation, NotifyChange.ActionModified, path, isdir));
}
/**
* Security descriptor changed notification
*
* @param path String
* @param isdir boolean
*/
public final void notifySecurityDescriptorChanged(String path, boolean isdir) {
// Check if file change notifications are enabled
if ( getGlobalNotifyMask() == 0 || hasSecurityDescriptorChange() == false)
return;
// Send the change notification
queueNotification(new NotifyChangeEvent(NotifyChange.Security, NotifyChange.ActionModified, path, isdir));
}
/**
* Enable debug output
*
* @param ena boolean
*/
public final void setDebug(boolean ena) {
m_debug = ena;
}
/**
* Shutdown the change notification processing thread
*/
public final void shutdownRequest() {
// Check if the processing thread is valid
if ( m_procThread != null) {
// Set the shutdown flag
m_shutdown = true;
// Wakeup the processing thread
m_procThread.interrupt();
}
}
/**
* Send buffered change notifications for a session
*
* @param req NotifyRequest
* @param evtList NotifyChangeEventList
*/
public final void sendBufferedNotifications(NotifyRequest req, NotifyChangeEventList evtList) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("Send buffered notifications, req=" + req + ", evtList=" + ( evtList != null ? "" + evtList.numberOfEvents() : "null"));
// Initialize the notification request timeout
long tmo = System.currentTimeMillis() + NotifyRequest.DefaultRequestTimeout;
// Allocate the NT transaction packet to send the asynchronous notification
NTTransPacket ntpkt = new NTTransPacket();
// Build the change notification response SMB
ntpkt.setParameterCount(18);
ntpkt.resetBytePointerAlign();
int pos = ntpkt.getPosition();
ntpkt.setNTParameter(1, 0); // total data count
ntpkt.setNTParameter(3, pos - 4); // offset to parameter block
// Check if the notify enum status is set
if ( req.hasNotifyEnum()) {
// Set the parameter block length
ntpkt.setNTParameter(0, 0); // total parameter block count
ntpkt.setNTParameter(2, 0); // parameter block count for this packet
ntpkt.setNTParameter(6, pos - 4); // data block offset
ntpkt.setByteCount();
ntpkt.setCommand(PacketType.NTTransact);
ntpkt.setLongErrorCode(SMBStatus.NTNotifyEnumDir);
ntpkt.setFlags(SMBSrvPacket.FLG_CANONICAL + SMBSrvPacket.FLG_CASELESS);
ntpkt.setFlags2(SMBSrvPacket.FLG2_UNICODE + SMBSrvPacket.FLG2_LONGERRORCODE);
// Set the notification request id to indicate that it has completed
req.setCompleted(true, tmo);
req.setNotifyEnum( false);
// Set the response for the current notify request
ntpkt.setMultiplexId(req.getMultiplexId());
ntpkt.setTreeId(req.getTreeId());
ntpkt.setUserId(req.getUserId());
ntpkt.setProcessId(req.getProcessId());
try {
// Send the response to the current session
if ( req.getSession().sendAsynchResponseSMB(ntpkt, ntpkt.getLength()) == false) {
// Asynchronous request was queued, clone the request packet
ntpkt = new NTTransPacket(ntpkt);
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("Failed to send change notification, " + ex.getMessage());
}
}
else if ( evtList != null) {
// Pack the change notification events
for ( int i = 0; i < evtList.numberOfEvents(); i++) {
// Get the current event from the list
NotifyChangeEvent evt = evtList.getEventAt(i);
// Get the relative file name for the event
String relName = FileName.makeRelativePath(req.getWatchPath(), evt.getFileName());
if ( relName == null)
relName = evt.getShortFileName();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(" Notify evtPath=" + evt.getFileName() + ", reqPath=" + req.getWatchPath() + ", relative=" + relName);
// Pack the notification structure
ntpkt.packInt(0); // offset to next structure
ntpkt.packInt(evt.getAction()); // action
ntpkt.packInt(relName.length() * 2); // file name length
ntpkt.packString(relName, true, false);
// Check if the event is a file/directory rename, if so then add the old file/directory details
if ( evt.getAction() == NotifyChange.ActionRenamedNewName &&
evt.hasOldFileName()) {
// Set the offset from the first structure to this structure
int newPos = DataPacker.longwordAlign(ntpkt.getPosition());
DataPacker.putIntelInt(newPos - pos, ntpkt.getBuffer(), pos);
// Get the old file name
relName = FileName.makeRelativePath(req.getWatchPath(), evt.getOldFileName());
if ( relName == null)
relName = evt.getOldFileName();
// Add the old file/directory name details
ntpkt.packInt(0); // offset to next structure
ntpkt.packInt(NotifyChange.ActionRenamedOldName);
ntpkt.packInt(relName.length() * 2); // file name length
ntpkt.packString(relName, true, false);
}
// Calculate the parameter block length, longword align the buffer position
int prmLen = ntpkt.getPosition() - pos;
ntpkt.alignBytePointer();
pos = (pos + 3) & 0xFFFFFFFC;
// Set the parameter block length
ntpkt.setNTParameter(0, prmLen); // total parameter block count
ntpkt.setNTParameter(2, prmLen); // parameter block count for this packet
ntpkt.setNTParameter(6, ntpkt.getPosition() - 4);
// data block offset
ntpkt.setByteCount();
ntpkt.setCommand(PacketType.NTTransact);
ntpkt.setFlags(SMBSrvPacket.FLG_CANONICAL + SMBSrvPacket.FLG_CASELESS);
ntpkt.setFlags2(SMBSrvPacket.FLG2_UNICODE + SMBSrvPacket.FLG2_LONGERRORCODE);
// Set the notification request id to indicate that it has completed
req.setCompleted(true, tmo);
// Set the response for the current notify request
ntpkt.setMultiplexId(req.getMultiplexId());
ntpkt.setTreeId(req.getTreeId());
ntpkt.setUserId(req.getUserId());
ntpkt.setProcessId(req.getProcessId());
try {
// Send the response to the current session
if ( req.getSession().sendAsynchResponseSMB(ntpkt, ntpkt.getLength()) == false) {
// Asynchronous request was queued, clone the request packet
ntpkt = new NTTransPacket(ntpkt);
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("Failed to send change notification, " + ex.getMessage());
}
}
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("sendBufferedNotifications() done");
}
/**
* Queue a change notification event for processing
*
* @param evt NotifyChangeEvent
*/
protected final void queueNotification(NotifyChangeEvent evt) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("Queue notification event=" + evt.toString());
// Queue the notification event to the main notification handler thread
synchronized ( m_eventList) {
// Add the event to the list
m_eventList.addEvent(evt);
// Notify the processing thread that there are events to process
m_eventList.notifyAll();
}
}
/**
* Send change notifications to sessions with notification enabled that match the change event.
*
* @param evt NotifyChangeEvent
* @return int
*/
protected final int sendChangeNotification(NotifyChangeEvent evt) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("sendChangeNotification event=" + evt);
// Get a list of notification requests that match the type/path
Vector reqList = findMatchingRequests(evt.getFilter(),evt.getFileName(),evt.isDirectory());
if ( reqList == null || reqList.size() == 0)
return 0;
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(" Found " + reqList.size() + " matching change listeners");
// Initialize the notification request timeout
long tmo = System.currentTimeMillis() + NotifyRequest.DefaultRequestTimeout;
// Allocate the NT transaction packet to send the asynchronous notification
NTTransPacket ntpkt = new NTTransPacket();
// Send the notify response to each client in the list
for ( int i = 0; i < reqList.size(); i++) {
// Get the current request
NotifyRequest req = (NotifyRequest) reqList.elementAt(i);
// Build the change notification response SMB
ntpkt.setParameterCount(18);
ntpkt.resetBytePointerAlign();
int pos = ntpkt.getPosition();
ntpkt.setNTParameter(1, 0); // total data count
ntpkt.setNTParameter(3, pos - 4); // offset to parameter block
// Get the path for the event
String relName = evt.getFileName();
if ( relName == null)
relName = evt.getShortFileName();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(" Notify evtPath=" + evt.getFileName() + ", MID=" + req.getMultiplexId() + ", reqPath=" + req.getWatchPath() + ", relative=" + relName);
// Pack the notification structure
ntpkt.packInt(0); // offset to next structure
ntpkt.packInt(evt.getAction()); // action
ntpkt.packInt(relName.length() * 2); // file name length
ntpkt.packString(relName, true, false);
// Check if the event is a file/directory rename, if so then add the old file/directory details
if ( evt.getAction() == NotifyChange.ActionRenamedNewName &&
evt.hasOldFileName()) {
// Set the offset from the first structure to this structure
int newPos = DataPacker.longwordAlign(ntpkt.getPosition());
DataPacker.putIntelInt(newPos - pos, ntpkt.getBuffer(), pos);
// Get the old file name
relName = FileName.makeRelativePath(req.getWatchPath(), evt.getOldFileName());
if ( relName == null)
relName = evt.getOldFileName();
// Add the old file/directory name details
ntpkt.packInt(0); // offset to next structure
ntpkt.packInt(NotifyChange.ActionRenamedOldName);
ntpkt.packInt(relName.length() * 2); // file name length
ntpkt.packString(relName, true, false);
}
// Calculate the parameter block length, longword align the buffer position
int prmLen = ntpkt.getPosition() - pos;
ntpkt.alignBytePointer();
pos = (pos + 3) & 0xFFFFFFFC;
// Set the parameter block length
ntpkt.setNTParameter(0, prmLen); // total parameter block count
ntpkt.setNTParameter(2, prmLen); // parameter block count for this packet
ntpkt.setNTParameter(6, ntpkt.getPosition() - 4);
// data block offset
ntpkt.setByteCount();
int bytCnt = ntpkt.getByteCount();
ntpkt.setCommand(PacketType.NTTransact);
ntpkt.setLongErrorCode(0);
ntpkt.setFlags(SMBSrvPacket.FLG_CANONICAL + SMBSrvPacket.FLG_CASELESS);
ntpkt.setFlags2(SMBSrvPacket.FLG2_UNICODE + SMBSrvPacket.FLG2_LONGERRORCODE);
// Check if the request is already complete
if ( req.isCompleted() == false) {
// Set the notification request id to indicate that it has completed
req.setCompleted(true, tmo);
// Set the response for the current notify request
ntpkt.setMultiplexId(req.getMultiplexId());
ntpkt.setTreeId(req.getTreeId());
ntpkt.setUserId(req.getUserId());
ntpkt.setProcessId(req.getProcessId());
// DEBUG
// ntpkt.DumpPacket();
try {
// Send the response to the current session
if ( req.getSession().sendAsynchResponseSMB(ntpkt, ntpkt.getLength()) == false) {
// Asynchronous request was queued, clone the request packet
ntpkt = new NTTransPacket(ntpkt);
// DEBUG
if ( Debug.EnableInfo && req.getSession().hasDebug(SMBSrvSession.DBG_NOTIFY))
req.getSession().debugPrintln(" Notification request was queued, sess=" + req.getSession().getSessionId() + ", MID=" + req.getMultiplexId());
}
else if ( Debug.EnableInfo && req.getSession().hasDebug(SMBSrvSession.DBG_NOTIFY))
req.getSession().debugPrintln(" Notification request was sent, sess=" + req.getSession().getSessionId() + ", MID=" + req.getMultiplexId());
}
catch (Exception ex) {
Debug.println( ex);
}
}
else {
// Buffer the event so it can be sent when the client resets the notify request
req.addEvent(evt);
// DEBUG
if ( Debug.EnableInfo && req.getSession().hasDebug(SMBSrvSession.DBG_NOTIFY))
req.getSession().debugPrintln("Buffered notify req=" + req + ", event=" + evt + ", sess=" + req.getSession().getSessionId());
}
// Reset the notification pending flag for the session
req.getSession().setNotifyPending(false);
// DEBUG
if ( Debug.EnableInfo && req.getSession().hasDebug(SMBSrvSession.DBG_NOTIFY))
req.getSession().debugPrintln("Asynch notify req=" + req + ", event=" + evt + ", sess=" + req.getSession().getUniqueId());
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("sendChangeNotification() done");
// Return the count of matching requests
return reqList.size();
}
/**
* Find notify requests that match the type and path
*
* @param typ int
* @param path String
* @param isdir boolean
* @return Vector
*/
protected final synchronized Vector findMatchingRequests(int typ, String path, boolean isdir) {
// Create a vector to hold the matching requests
Vector reqList = new Vector();
// Normalise the path string
String matchPath = path.toUpperCase();
// Search for matching requests and remove them from the main request list
int idx = 0;
long curTime = System.currentTimeMillis();
boolean removedReq = false;
while ( idx < m_notifyList.numberOfRequests()) {
// Get the current request
NotifyRequest curReq = m_notifyList.getRequest(idx);
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("findMatchingRequests() req=" + curReq.toString());
// Check if the request has expired
if ( curReq.hasExpired(curTime)) {
// Remove the request from the list
m_notifyList.removeRequestAt(idx);
// DEBUG
if ( Debug.EnableInfo && hasDebug()) {
Debug.println("Removed expired request req=" + curReq.toString());
if ( curReq.getBufferedEventList() != null) {
NotifyChangeEventList bufList = curReq.getBufferedEventList();
Debug.println(" Buffered events = " + bufList.numberOfEvents());
for ( int b = 0; b < bufList.numberOfEvents(); b++)
Debug.println(" " + (b+1) + ": " + bufList.getEventAt(b));
}
}
// Indicate that q request has been removed from the queue, the global filter mask will need
// to be recalculated
removedReq = true;
// Restart the loop
continue;
}
// Check if the request matches the filter
if ( curReq.hasFilter(typ)) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(" hasFilter typ=" + typ + ", watchTree=" + curReq.hasWatchTree() + ", watchPath=" + curReq.getWatchPath() +
", matchPath=" + matchPath + ", isDir=" + isdir);
// Check if the path matches or is a subdirectory and the whole tree is being watched
boolean wantReq = false;
if ( matchPath.length() == 0 && curReq.hasWatchTree())
wantReq = true;
else if ( curReq.hasWatchTree() == true && matchPath.startsWith(curReq.getWatchPath()) == true)
wantReq = true;
else if ( isdir == true && matchPath.compareTo(curReq.getWatchPath()) == 0)
wantReq = true;
else if ( isdir == false) {
// Strip the file name from the path and compare
String[] paths = FileName.splitPath(matchPath);
if ( paths != null && paths[0] != null) {
// Check if the directory part of the path is the directory being watched
if ( curReq.getWatchPath().equalsIgnoreCase(paths[0]))
wantReq = true;
}
}
// Check if the request is required
if ( wantReq == true) {
// For all notify requests in the matching list we set the 'notify pending' state on the associated SMB
// session so that any socket writes on those sessions are synchronized until the change notification
// response has been sent.
curReq.getSession().setNotifyPending(true);
// Add the request to the matching list
reqList.addElement(curReq);
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(" Added request to matching list");
}
}
// Move to the next request in the list
idx++;
}
// If requests were removed from the queue the global filter mask must be recalculated
if ( removedReq == true)
m_globalNotifyMask = m_notifyList.getGlobalFilter();
// Return the matching request list
return reqList;
}
/**
* Asynchronous change notification processing thread
*/
public void run() {
// Loop until shutdown
while ( m_shutdown == false) {
// Wait for some events to process
synchronized ( m_eventList) {
try {
m_eventList.wait();
}
catch (InterruptedException ex) {
}
}
// Check if the shutdown flag has been set
if ( m_shutdown == true)
break;
// Loop until all pending events have been processed
while ( m_eventList.numberOfEvents() > 0) {
// Remove the event at the head of the queue
NotifyChangeEvent evt = null;
synchronized ( m_eventList) {
evt = m_eventList.removeEventAt(0);
}
// Check if the event is valid
if ( evt == null)
break;
try {
// Send out change notifications to clients that match the filter/path
int cnt = sendChangeNotification(evt);
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("Change notify event=" + evt.toString() + ", clients=" + cnt);
}
catch (Throwable ex) {
Debug.println("NotifyChangeHandler thread");
Debug.println(ex);
}
}
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("NotifyChangeHandler thread exit");
}
}