gt: 		if ( removedReq == true)
context: /*
   * Copyright (C) 2006-2010 Alfresco Software Limited.
   *
   * This file is part of Alfresco
   *
   * Alfresco is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * Alfresco 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 Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
   */
  
  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
  		
  