Recycle Bin


With v2 of Sharepoint, restoring deleted documents was a much sought after elixir. And to that effect were made a number of attempts at coaxing the product to allow us to retain copies of the deleted items. Once such simplistic approach was documented and well presented by Maxin V Karpov and Eric Schoonover in the February 2005 issue of MSDN. The online version can be found here.

Recently me as well as a couple of my colleagues received calls from customers, seeking clarifications on how to implement the sample presented in the article. This called for a defininte reimplementation,  and what an experience it was! Here are the code samples from that implementation.


I will start with the classes that will help implement the Upload and Delete Event Handlers. This is spread over two classes: BaseEventSink(which is an extended version of the file that gets downloaded from the event handler tool kit) and the EventSinkData class


/*=====================================================================


  File:      BaseEventSink.cs


  Summary:   Base class for cached event sinks.


---------------------------------------------------------------------



using System;



using System.Xml;


using System.Runtime.InteropServices;


using System.Security.Principal;


using System.IO;


using System.Diagnostics;


using System.Text;


using System.Collections;


using System.Collections.Specialized;


using Microsoft.SharePoint;


using RecycleBin.SharePoint.Configuration;


using Microsoft.SharePoint.Administration;


 


 


namespace RecycleBin.SharePoint.EventSink


{


    /// <summary>


    /// Base class for cached event sinks.


    /// Handles impersonation and caching event sink classes to handle multiple events on


    /// the same list.


    /// </summary>


      public class BaseEventSink : Microsoft.SharePoint.IListEventSink


      {


           


            public BaseEventSink()


            {


           


            }


 


            /// <summary>


            /// Implementation of the OnEvent method. Windows SharePoint Services will


            /// call this method when an event occurs.


            /// </summary>


            /// <param name="listEvent">The SPListEvent object that describes the event which occured.</param>


            public virtual void OnEvent(Microsoft.SharePoint.SPListEvent listEvent)


            {


                  WriteToFile("Event has been fired, and entered the custom event handler.");


                  WriteToFile("List Event dump:- " + listEvent.ToString());


                  if (listEvent == null)


                  {


                        throw new ArgumentNullException("listEvent", "list event object cannot be null");


                  }


           


                  WriteToFile("Getting the cachedEventSink");


                  BaseEventSink sink = GetCachedEventSink(listEvent);


                  WriteToFile("Got the Cached Event Sink");


 


                  WriteToFile("Starting the impersonation.");


                  WindowsImpersonationContext wip = null;


 


                  //Ensure each sink instance handles only one event at a time.


                  lock(sink)


                  {


                        try


                        {  


                              //Make sure the sink class has the current SPListEvent object


                              sink.EventInfo = listEvent;


                             


                              //Impersonate the appropriate user


                              WriteToFile("Impersonating the administrator");


                              WindowsIdentity id = sink.HandlerIdentity;


                              if (id != null)


                                    wip = id.Impersonate();


 


                              WriteToFile("Impersonation successful");


                              WriteToFile("Transferring the control to the actual event handler.");


                              sink.HandleEvent();


                   


                        }


                        finally


                        {


                              //Cleanup


                              if (wip != null)


                                    wip.Undo();


                              if (m_web != null)


                                    m_web.Close();


                                   


                              //Null out cached SPWeb and SPList objects so that


                              //fresh ones are obtained for the next event.


                              m_web = null;


                              m_list = null;


                        }


                  }


            }


 


            /// <summary>


            /// HandleEvent is called when an event occurs. Child classes should perform the main


            /// event handling actions here.


            /// </summary>


            protected virtual void HandleEvent()


            {


                 


                  //                if (admin.IsCurrentUserGlobalAdmin())                


            {


                  WriteToFile("Entered the EventHandler");


                  WriteToFile("Event fired is of type:- " + EventInfo.Type.ToString());


                  switch(EventInfo.Type)


                  {


                        case SPListEventType.Delete:


                              WriteToFile("Delete event fired.");


                              WriteToFile("Transferring to the Delete handler.");


                              DeleteHandler();


                              break;


                        case SPListEventType.Move:


                              //                            if(!Include()){break;}


                              WriteToFile("Move Event fired.");


                              WriteToFile("Transferring to the Move handler.");


                              MoveHandler();


                              break;


                        case SPListEventType.Update:


                        case SPListEventType.Insert:


                        case SPListEventType.Copy:


                              //                            if(!Include()){break;}


                              WriteToFile("Update/Insert/Copy event fired.");


                              WriteToFile("Transferring to the Update/Insert/Copy handler.");


                              UpdateInsertCopyHandler();


                              break;


                  }


            }


            }


       


            /// <summary>


            /// The SPListEvent object that describes the current event.


            /// </summary>


            protected virtual SPListEvent EventInfo


            {


                  get


                  {


                        return m_ListEvent;


                  }


                  set


                  {


                        m_ListEvent = value;


                        m_web = null;


                        m_list = null;


                        m_fileUrl = null;


                        m_sinkData = null;


                  }


            }


       


            /// <summary>


            /// The SPWeb object for the web site that contains the list


            /// that the event occured on.


            /// </summary>


            protected virtual SPWeb EventWeb


            {


                  get


                  {


                        if (m_web == null)


                        {


                              WriteToFile("Getting the web context.");


                              m_web = m_ListEvent.Site.OpenWeb();


                 


                        }


                        return m_web;


                  }


            }


 


            /// <summary>


            /// The SPList object for the list the event occured on.


            /// </summary>


            protected virtual SPList EventList


            {


                  get


                  {


                        if (m_list == null)


                        {


                              m_list = EventWeb.Lists[EventInfo.ListID];


                        }


                        return m_list;


                  }


            }


 


            /// <summary>


            /// The url of the file the event occured on.


            /// Equals UrlAfter if UrlAfter is not empty. Otherwise equals


            /// UrlBefore.


            /// </summary>


            protected string EventFileUrl


            {


                  get


                  {


                        if (m_fileUrl == null)


                        {


                              string webUrl = m_ListEvent.WebUrl == null ? "<NULL>" : m_ListEvent.WebUrl;


                              string webRelUrl = m_ListEvent.UrlAfter;


                              if (webRelUrl == null || webRelUrl.Length == 0)


                              {


                                    webRelUrl = (m_ListEvent.UrlBefore == null || m_ListEvent.UrlBefore.Length == 0 ?


                                          "<NULL>" : m_ListEvent.UrlBefore);


                              }   


                              m_fileUrl = String.Format("{0}/{1}", webUrl, webRelUrl);


                        }


                        return m_fileUrl;


                  }


            }


       


            protected string Data


            {


                  get


                  {


                        if (m_sinkData == null)


                        {


                              m_sinkData = m_ListEvent.SinkData;


                        }


                        return m_sinkData;


                  }


            }


 


            # region Custom Code


 


            /// <summary>


            /// Converts XML string retrieved from SinkData property into an object


            /// </summary>


            protected EventSinkData Configuration


            {


                  get


                  {


                        try


                        {


                              m_ConfigData = EventSinkData.GetInstance(Data);


                              return m_ConfigData;


                        }


                        catch(Exception ex)


                        {


                              PublishException(ex.ToString());


                        }


                        return null;


                  }


            }//Configuration


 


            /// <summary>


            /// Makes sure that the proper folder structure is in place for copying, moving or inserting a file in to the mirror


            /// document library or the Recycle Bin document library.


            /// </summary>


            /// <param name="web">


            /// Web where the folder structure needs to be built or ensured


            /// </param>


            /// <param name="finalUrl">


            /// Url containing the structure that needs to be built, must contain the trailing "/" if it is not a link to a file.


            /// If it is a link to a file the file name will be stripped and the folder structure ensured.


            /// </param>


            /// <returns>


            /// File path that has been built


            /// </returns>


            protected  static string EnsureParentFolder(SPWeb web, string finalUrl)


            {


                  finalUrl = web.GetFile(finalUrl).Url;


                  int x = finalUrl.LastIndexOf("/");


                  string epf = String.Empty;


                  if(x <= -1)


                        return epf;


                  epf = finalUrl.Substring(0, x);


                  SPFolder folder = web.GetFolder(epf);


                  if(folder.Exists)


                        return epf;


                  SPFolder curFolder = web.RootFolder;


                  string [] folders = epf.Split('/');


                  for(int i = 0; i < folders.Length; i ++)


                  {


                        curFolder = curFolder.SubFolders.Add(folders[i]);


                  }


                  return epf;


            }//EnsureParentFolder


 


            protected  static void PublishException(string errors)


            {


                  if(errors.Length > 0)


                  {


                        if(!EventLog.SourceExists(SOURCENAME))


                              EventLog.CreateEventSource(SOURCENAME, LOGNAME);


                        EventLog el = new EventLog();


                        el.Source = LOGNAME;


                        el.WriteEntry(errors, EventLogEntryType.Error);


                  }


            }


 


            # endregion


 


 


            # region Windows Impersonation Code


 


            /// <summary>


            /// The WindowsIdentity class of the identity the Event Sink should impersonate.


            /// </summary>


            protected virtual WindowsIdentity HandlerIdentity


            {


                  get


                  {


                        m_Identity = CreateIdentity(Configuration.UserName,


                                    Configuration.Domain,


                                    Configuration.Password);


                                         


                              return m_Identity;


                       


                  }


     


     


      }


 


        /// <summary>


        /// Helper method to handle creating a WindowsIdentity object from a username / domain / password.


        /// </summary>


        /// <param name="User">The username of the account to impersonate.</param>


        /// <param name="Domain">The domain of the account to impersonate.</param>


        /// <param name="Password">The password of the account to impersonate.</param>


        /// <returns></returns>


        public static WindowsIdentity CreateIdentity(string User, string Domain, string Password)


        {


            // The Windows NT user token.


            IntPtr tokenHandle = new IntPtr(0);


       


            const int LOGON32_PROVIDER_DEFAULT = 0;


            const int LOGON32_LOGON_NETWORK_CLEARTEXT = 3;


 


            tokenHandle = IntPtr.Zero;


 


            // Call LogonUser to obtain a handle to an access token.


            bool returnValue = LogonUser(User, Domain, Password,


                LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT,


                ref tokenHandle);


            if (false == returnValue)


            {


                int ret = Marshal.GetLastWin32Error();


                throw new Exception("LogonUser failed with error code: " +  ret);


            }


 


 


            //The WindowsIdentity class makes a new copy of the token.


            //It also handles calling CloseHandle for the copy.


            WindowsIdentity id = new WindowsIdentity(tokenHandle);


            CloseHandle(tokenHandle);


            return id;


        }


 


            # endregion


 


        /// <summary>


        /// Get the cached event sink object to handle this event.


        /// </summary>


        /// <returns>The cached IListEventSink object that should handle the current event.</returns>


        private BaseEventSink GetCachedEventSink(SPListEvent evt)


        {


            BaseEventSink sink = null;


           


            //Syncrhonize both reads and writes to the cache.


            //Even though the Hashtable class is threadsafe for reads, we don't want to have


            //multiple sink instances per list. Otherwise, if many events occur on a list


            //right off the bat, you could get many instances that all have to initialize


            //themselves.


            lock(SinkCache.SyncRoot)


            {


                sink = SinkCache[evt.ListID] as BaseEventSink;


           


                //The cached sink is only fit if it is the same type and has the same data


                //as the current sink.


                if (sink == null ||


                    ! sink.GetType().Equals(this.GetType()) ||


                    evt.SinkData != sink.Data)


                {


                    //Update the cache, throw away the old sink if it exists.


                    SinkCache[evt.ListID] = this;


                    sink = this;


                }


            }


            return sink;


        }


 


 


            # region Win32 API calls


       


        [DllImport("advapi32.dll", SetLastError=true)]


        private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,


            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);


 


        [DllImport("kernel32.dll", CharSet=CharSet.Auto)]


        private extern static bool CloseHandle(IntPtr handle);


            # endregion


 


        protected SPListEvent m_ListEvent;


        private SPWeb m_web;


        private SPList m_list;


        private string m_fileUrl;


        private string m_sinkData;


 


            // Custom variables start


            private EventSinkData m_ConfigData;


            protected static Hashtable SinkCache = new Hashtable();


 


            private const string SOURCENAME = "Recycle Bin";


            private const string LOGNAME = "Recyclebin.EventSink";


 


            private string strMirror = "New";


            private string strRecycle = "Recycle Bin";


            private WindowsIdentity m_Identity = null;


            private SPGlobalAdmin admin = new SPGlobalAdmin();


            private WindowsIdentity id;


//          private EventSinkData e = new EventSinkData();


 


            // Custom variables end


       


 


            private void UpdateInsertCopyHandler()


            {    


                  WriteToFile("Entered the UpdateInsertCopy handler.");


                  string fileLocation = EventInfo.UrlAfter;


                  WriteToFile("Got the before location:- " + fileLocation);


//                string newFileLoc = String.Concat(strMirror, "/", fileLocation.Remove(0,5));


                  string newFileLoc = String.Concat(Configuration.MirrorLib, "/", fileLocation.Remove(0,Configuration.MainLib.Length+1));


                  WriteToFile("Got the new location:- " + newFileLoc);


 


 


                  SPFile miscFile = EventWeb.GetFile(fileLocation);


                 


                  try


                  {


                        //EnsureParentFolder(EventWeb, newFileLoc);


//                      miscFile.CopyTo(newFileLoc, true);


//                      SPFolder newFolder = EventWeb.GetFolder(strMirror);


                        WriteToFile("Getting the folder context.");


                        SPFolder newFolder = EventWeb.GetFolder(Configuration.MirrorLib);


                        WriteToFile("Preparing to copy the file.");


                        Byte[] byteArray  = miscFile.OpenBinary();


                        WriteToFile("Copying the file.");


                        newFolder.Files.Add(miscFile.Name,byteArray);


                        WriteToFile("Copy done.");


 


                  }


                  catch(Exception ex)


                  {


                        WriteToFile("Exception occurred:- " + ex.ToString());


                        PublishException(ex.ToString());


                  }


            }//UpdateInsertCopyHandler


 


            private void DeleteHandler()


            {    


                 


                  WriteToFile("Entered Delete handler.");


                  SPFile delFile = EventWeb.GetFile(String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MainLib.Length+1)));


                  WriteToFile("Got the old address. " + String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MainLib.Length+1)));


                  string newFileLoc = String.Concat(Configuration.RecycleBinLib, delFile.Url.Remove(0,Configuration.MirrorLib.Length));


                  WriteToFile("New address:- " + newFileLoc);


 


                  try


                  {


//                      EnsureParentFolder(EventWeb, newFileLoc);


//                      delFile.MoveTo(newFileLoc, true);


                        WriteToFile("Getting the folder context.");


                        SPFolder newFolder = EventWeb.GetFolder(Configuration.RecycleBinLib);


                        WriteToFile("Preparing to copy.");


                        Byte[] byteArray  = delFile.OpenBinary();


                        WriteToFile("Copying the file.");


                        newFolder.Files.Add(delFile.Name,byteArray,true);


                        WriteToFile("Copying complete.");


                  }


                  catch(Exception ex)


                  {


                        WriteToFile("Exception occurred:- " + ex.ToString());


                        PublishException(ex.ToString());


                  }


            }//DeleteHandler


 


            private void MoveHandler()


            {


                  string mirrorName = strMirror;


 


//                SPFile moveFile = EventWeb.GetFile(String.Concat(mirrorName, "/", EventInfo.UrlBefore.Remove(0,5)));


                  SPFile moveFile = EventWeb.GetFile(String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlBefore.Remove(0,Configuration.MirrorLib.Length-1)));


 


                 


//                string newFileLoc = String.Concat(mirrorName, "/", EventInfo.UrlAfter);


                  string newFileLoc = String.Concat(Configuration.MirrorLib, "/", EventInfo.UrlAfter);


                  try


                  {


                        EnsureParentFolder(EventWeb, newFileLoc);


                        moveFile.MoveTo(newFileLoc, true);


                  }


                  catch(Exception ex)


                  {


                        PublishException(ex.ToString());


                  }


                 


            }//MoveHandler


 


            // Logging


            private     static void WriteToFile(String input)


            {


                  try


                  {


                        string filePath=@"C:\sharepoint\Log.txt";


                        FileInfo logFile = new FileInfo(filePath);


                        if(logFile.Exists)


                        {


                              if (logFile.Length >= 100000)


                                    File.Delete(filePath);


                        }


 


                        FileStream fs =   new   FileStream(filePath,FileMode.OpenOrCreate, FileAccess.ReadWrite);


 


                        StreamWriter w = new StreamWriter(fs);


                        w.BaseStream.Seek(0,SeekOrigin.End);


 


                        w.WriteLine(input);


                        w.WriteLine("--------------------------------------------------------------");


                        w.Flush();


                        w.Close();


                  }


                  catch(System.Exception ex)


                  {


                        PublishException(ex.ToString());


                  }


            }


    }


}


 


Continued in the next one due to the length of the post.........

Comments (2)
  1. satyasiva says:

    It is very useful to me  pls send modifications to this.

  2. I was ego surfing today and came upon the following links that related to the "Add A Recycle Bin To Windows SharePoint Services For Easy Document Recovery" artcile that I contributed to last year. http://blogs.msdn.com/harsh/archive/2005/10/17/481621.aspxGreat

Comments are closed.

Skip to main content