// Stats.cs
// Copyright (C) 2002 Matt Zyzik (www.FileScope.com)
// 
// 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Timers;
using System.Windows.Forms;
using FileScope.Gnutella2;
using LibControlLogic.Error;

namespace FileScope
{

    #region file info

    /// <summary>
    /// Used in fileList to store all necessary information about each file.
    /// </summary>
    public class FileObject
    {
        //full file path and name
        //size of file in bytes
        public uint b;
        //file index
        public int fileIndex;
        public string lcaseFileName;
        public string location;
        //md4 hash value for the file
        public byte[] md4;
        //sha1 hash value for the file
        public string sha1; //in base32
        public byte[] sha1bytes;
        //temporary variable
        public int tempOne;
    }

    /// <summary>
    /// Used in fileList to store all necessary information about each file.
    /// zzoi 23Feb 2009 the zanzi file object includes a string field for
    /// the xml to return as result
    /// </summary>
    public class ZFileObject
    {
        //full file path and name
        //size of file in bytes
        public uint b;
        //file index
        public int fileIndex;
        public string lcaseFileName;
        public string location;
        //md4 hash value for the file
        public byte[] md4;
        //sha1 hash value for the file
        public string sha1; //in base32
        public byte[] sha1bytes;
        //temporary variable
        public int tempOne;
        //zzoi 23Feb 2009 the zanzi results xml
        public string zresult;

    }

    /// <summary>
    /// Used in lastFileSet to store all necessary information used to recover
    /// previously generated hash values.
    /// </summary>
    [Serializable]
    public class FileObject2
    {
        public uint bytes;
        public byte[] md4;
        public string sha1;
        public byte[] sha1bytes;
    }

    #endregion

    //connection type
    public enum EnumConnectionType
    {
        dialUp,
        broadband
    }

    /// <summary>
    /// Class for storing the states, stats, and settings for FileScope.
    /// </summary>
    public class Stats
    {
        //FileScope version
        //stores all of FileScope settings
        public static ArrayList blockedChatHosts = new ArrayList();
        public static ArrayList fileList = new ArrayList();
        public static FileSystemWatcher[] fsws;
        public static SortedList gnutella2WebCache = new SortedList();
        public static ArrayList gnutellaHosts = new ArrayList();
        //queue that stores index values of sockets that just received data for processing
        public static Queue gnutellaQueue = new Queue(110);
        public static Hashtable lastFileSet = new Hashtable();
        public static FileScopeSettings settings = new FileScopeSettings();
        //list of all shared directories
        public static ArrayList shareList = new ArrayList();
        //file system watchers for each shared directory
        //timer to check to see if all shared directories are still existent
        public static GoodTimer tmrCheckDirs = new GoodTimer(5000);
        //carry out any 1 second chores
        public static GoodTimer tmrChores = new GoodTimer(1000);
        public static string version = "0.2.2";
        //list of all shared files

        public static void InitializeVariables()
        {
            Updated.Gnutella2.enableDeflate = true;
            if (settings.ultrapeerCapable && !settings.firewall && settings.allowUltrapeer)
                Updated.Gnutella2.ultrapeer = true;
            FileType.FillExt();
            //Updated.Gnutella.lastConnectionCount = 0;
            Updated.Gnutella2.lastConnectionCount = 0;
            //Updated.OpenNap.lastConnectionCount = 0;
            //Updated.EDonkey.lastConnectionCount = 0;
            Updated.uploads = 0;
            Updated.uploadsNow = 0;
            Updated.downloadsNow = 0;
            Updated.trueSpeed = -1;
            Updated.numChats = 0;
            Updated.closing = false;
            Updated.everIncoming = false;
            Updated.udpIncoming = false;


            try
            {
                //part of once startup\
                IPHostEntry ipadre = Dns.Resolve(Dns.GetHostName());
                settings.ipAddress = ipadre.AddressList[0].ToString();

                Updated.myIPA = Endian.GetIPAddress(Endian.BigEndianIP(settings.ipAddress), 0);
            }
            catch
            {
                Debug.WriteLine("couldn't set myIPA in Stats.InitializeVariables");
                Updated.myIPA = null;
            }
            Updated.timestamp = 50;
//			Updated.Gnutella.numPings = 0;
//			Updated.Gnutella.numPongs = 0;
//			Updated.Gnutella.numQueries = 0;
//			Updated.Gnutella.numQueryHits = 0;
//			Updated.Gnutella.numPushes = 0;
            Updated.Gnutella2.numPI = 0;
            Updated.Gnutella2.numPO = 0;
            Updated.Gnutella2.numLNI = 0;
            Updated.Gnutella2.numKHL = 0;
            Updated.Gnutella2.numQ2 = 0;
            Updated.Gnutella2.numQA = 0;
            Updated.Gnutella2.numQH2 = 0;
            Updated.Gnutella2.numPUSH = 0;
            Updated.Gnutella2.numQHT = 0;
            Updated.Gnutella2.numQKR = 0;
            Updated.Gnutella2.numQKA = 0;
            G2Data.SetupFuncTable();
            Updated.le = BitConverter.IsLittleEndian;
            tmrCheckDirs.AddEvent(tmrCheckDirs_Tick);
            tmrCheckDirs.Start();
            tmrChores.AddEvent(tmrChores_Tick);
            tmrChores.Start();
        }

        /// <summary>
        /// Return the speed of this connection.
        /// </summary>
        public static int GetSpeed()
        {
            if (Updated.trueSpeed != -1)
                return Updated.trueSpeed;
            else
                switch (settings.connectionType)
                {
                    case EnumConnectionType.dialUp:
                        return 5;
//					case EnumConnectionType.cable:
//						return 100;
//					case EnumConnectionType.t1:
//						return 1000;
//					case EnumConnectionType.t3:
//						return 50000;
                    default:
                        return 5;
                }
        }

        #region Nested type: FileScopeSettings

        /// <summary>
        /// Class for all of the settings in FileScope.
        /// starting point for settings only relevant to g2 and server
        /// some settings are relevant to the connected client and set by them
        /// </summary>
        [Serializable]
        public class FileScopeSettings
        {
            //Admin global settings for this server
            public volatile bool allowChats; //allow incoming chats?
            public volatile bool allowUltrapeer; //allow ultrapeer
            public volatile bool autoGnutella2; //automatically connect to gnutella2 on startup

            //client user settings for server
            public volatile bool clearDl; //clear completed downloads
            public volatile bool clearUp; //clear completed uploads
            public volatile EnumConnectionType connectionType; //EnumConnectionType will take care of this
            public volatile string dlDirectory; //download directory
            public volatile bool firewall; //are we behind a firewall?
            public volatile int gConnectionsToKeep; //gnutella connections to keep when ultrapeer
            public volatile string ipAddress; //public IP Address
            public volatile int maxUploads; //the maximum simultaneous uploads
            public volatile byte[] myGUID = new byte[16]; //permanent GUID for this computer
            public volatile int port; //port for incoming connections

            public volatile float transSplitPerc; //% of the transfers control the splitter is at
            public volatile bool ultrapeerCapable; //fulfilled gnutella ultrapeer requirements
            public volatile bool updateNotify; //notify user of new updates
        }

        #endregion

        #region Nested type: LoadSave

        /// <summary>
        /// Class for loading and saving stuff.
        /// </summary>
        public class LoadSave
        {
            public static bool hashEngineAborted;
            public static bool suppressCopy = true;
            private static bool supUp;

            public static bool suppressUpdate
            {
                get { return supUp; }
                set
                {
                    supUp = value;
                    if (value)
                        StopFSWs();
                    else
                        StartFSWs();
                }
            }

            public static void LoadShares()
            {
                FileStream fStream = new FileStream(Utils.GetCurrentPath("shares.fscp"), FileMode.Open, FileAccess.Read);
                try
                {
                    BinaryFormatter crip = new BinaryFormatter();
                    shareList = (ArrayList) crip.Deserialize(fStream);
                    fStream.Close();
                    UpdateShares();
                }
                catch
                {
                    fStream.Close();
                    Debug.WriteLine("LoadShares");
                    try
                    {
                        File.Delete(Utils.GetCurrentPath("shares.fscp"));
                    }
                    catch
                    {
                        Debug.WriteLine("Deleting shares.fscp");
                    }
                    Application.Exit();
                }
            }

            public static void SaveShares()
            {
                BinaryFormatter crip = new BinaryFormatter();
                FileStream fStream = new FileStream(Utils.GetCurrentPath("shares.fscp"), FileMode.Create,
                                                    FileAccess.Write);
                crip.Serialize(fStream, shareList);
                fStream.Close();
            }

            public static void LoadHosts()
            {
                try
                {
                    //FileStream fStream = new FileStream(Utils.GetCurrentPath("gnuthosts.fscp"), FileMode.Open, FileAccess.Read);
                    FileStream fStream2 = new FileStream(Utils.GetCurrentPath("gnut2hosts.fscp"), FileMode.Open,
                                                         FileAccess.Read);
                    FileStream fStream3 = new FileStream(Utils.GetCurrentPath("gnut2cache.fscp"), FileMode.Open,
                                                         FileAccess.Read);
                    FileStream fStream4 = new FileStream(Utils.GetCurrentPath("gnut2keys.fscp"), FileMode.Open,
                                                         FileAccess.Read);

                    try
                    {
                        BinaryFormatter crip = new BinaryFormatter();
                        //gnutella first
                        //					lock(gnutellaHosts)
                        //					{
                        //						gnutellaHosts = (ArrayList)crip.Deserialize(fStream);
                        //						fStream.Close();
                        //					}

                        //gnutella2
                        lock (HostCache.recentHubs)
                        {
                            HostCache.recentHubs = (SortedList) crip.Deserialize(fStream2);
                            fStream2.Close();
                            //we've been gone probably for a while
                            foreach (HubInfo hi in HostCache.recentHubs.Values)
                                hi.timeKnown += 650;
                        }
                        lock (HostCache.hubCache)
                        {
                            HostCache.hubCache = (SortedList) crip.Deserialize(fStream3);
                            fStream3.Close();
                            foreach (HubInfo hi in HostCache.hubCache.Values)
                                hi.timeKnown += 650;
                        }
                        lock (HostCache.sentQueryKeys)
                        {
                            HostCache.sentQueryKeys = (Hashtable) crip.Deserialize(fStream4);
                            fStream4.Close();
                            foreach (QKeyInfo qki in HostCache.sentQueryKeys.Values)
                                qki.timeKnown += 650;
                        }

                        //					//opennap
                        //					if(File.Exists(Utils.GetCurrentPath("nap.wsx")))
                        //						LoadOpenNapWSXFile(Utils.GetCurrentPath("nap.wsx"));
                        //
                        //					//edonkey
                        //					if(File.Exists(Utils.GetCurrentPath("donkey.met")))
                        //						LoadEDonkeyMETFile(Utils.GetCurrentPath("donkey.met"));
                    }
                    catch (Exception e)
                    {
                        //fStream.Close();
                        fStream2.Close();
                        fStream3.Close();
                        fStream4.Close();
                        Debug.WriteLine("LoadHosts " + e.Message);
                        Debug.WriteLine(e.StackTrace);
                        try
                        {
                            File.Delete(Utils.GetCurrentPath("gnuthosts.fscp"));
                        }
                        catch
                        {
                            Debug.WriteLine("Deleting gnuthosts.fscp");
                        }
                        HashEngine.Stop();
                        Application.Exit();
                    }
                }

                catch (Exception  ex)
                {
                     new clsExeption(ex);
                }
            }


            public static void SaveHosts()
            {
//				//gnutella first
                BinaryFormatter crip = new BinaryFormatter();
//				lock(gnutellaHosts)
//				{
//					FileStream fStream = new FileStream(Utils.GetCurrentPath("gnuthosts.fscp"), FileMode.Create, FileAccess.Write);
//					crip.Serialize(fStream, gnutellaHosts);
//					fStream.Close();
//				}

                //g2
                lock (HostCache.recentHubs)
                {
                    FileStream fStream2 = new FileStream(Utils.GetCurrentPath("gnut2hosts.fscp"), FileMode.Create,
                                                         FileAccess.Write);
                    crip.Serialize(fStream2, HostCache.recentHubs);
                    fStream2.Close();
                }
                lock (HostCache.hubCache)
                {
                    FileStream fStream3 = new FileStream(Utils.GetCurrentPath("gnut2cache.fscp"), FileMode.Create,
                                                         FileAccess.Write);
                    crip.Serialize(fStream3, HostCache.hubCache);
                    fStream3.Close();
                }
                lock (HostCache.sentQueryKeys)
                {
                    FileStream fStream4 = new FileStream(Utils.GetCurrentPath("gnut2keys.fscp"), FileMode.Create,
                                                         FileAccess.Write);
                    crip.Serialize(fStream4, HostCache.sentQueryKeys);
                    fStream4.Close();
                }

//				//opennap
//				StreamWriter wStream = new StreamWriter(Utils.GetCurrentPath("nap.wsx"));
//				wStream.WriteLine("# I am excessively smart");
//				wStream.WriteLine();
//				for(int x = 0; x < opennapHosts.Count; x++)
//					wStream.WriteLine("A: " + opennapHosts.GetKey(x).ToString());
//				wStream.Close();

//				//edonkey
//				FileStream fs = new FileStream(Utils.GetCurrentPath("donkey.met"), FileMode.Create, FileAccess.Write);
//				BinaryWriter bw = new BinaryWriter(fs);
//				bw.Write((byte)0xE0);
//				bw.Write(edonkeyHosts.Count);
//				for(int x = 0; x < Stats.edonkeyHosts.Count; x++)
//				{
//					string address = (string)edonkeyHosts.GetKeyList()[x];
//					EDonkeyServerInfo edsi = (EDonkeyServerInfo)edonkeyHosts.GetValueList()[x];
//					bw.Write(Endian.BigEndianIP(address.Substring(0, address.IndexOf(":"))));
//					int pos = address.IndexOf(":")+1;
//					bw.Write(Convert.ToUInt16(address.Substring(pos, address.Length-pos)));
//					bw.Write((uint)5);
//					//strings first: server name, server description
//					bw.Write((byte)0x02);
//					bw.Write((ushort)1);
//					bw.Write((byte)0x01);
//					byte[] bytesPayload = System.Text.Encoding.ASCII.GetBytes(edsi.servName);
//					bw.Write((ushort)bytesPayload.Length);
//					bw.Write(bytesPayload);
//					bw.Write((byte)0x02);
//					bw.Write((ushort)1);
//					bw.Write((byte)0x0B);
//					bytesPayload = System.Text.Encoding.ASCII.GetBytes(edsi.servDesc);
//					bw.Write((ushort)bytesPayload.Length);
//					bw.Write(bytesPayload);
//					//ints next: max # users, current # users, # files
//					bw.Write((byte)0x03);
//					bw.Write((ushort)1);
//					bw.Write((byte)0x87);
//					bw.Write(edsi.maxUsers);
//					bw.Write((byte)0x03);
//					bw.Write((ushort)5);
//					bw.Write(System.Text.Encoding.ASCII.GetBytes("users"));
//					bw.Write(edsi.curUsers);
//					bw.Write((byte)0x03);
//					bw.Write((ushort)5);
//					bw.Write(System.Text.Encoding.ASCII.GetBytes("files"));
//					bw.Write(edsi.curFiles);
//				}
                //bw.Close();
                //fs.Close();
            }

            public static void LoadWebCache()
            {
                //FileStream fStream = new FileStream(Utils.GetCurrentPath("webcache.fscp"), FileMode.Open, FileAccess.Read);
                FileStream fStream2 = new FileStream(Utils.GetCurrentPath("webcache2.fscp"), FileMode.Open,
                                                     FileAccess.Read);
                try
                {
                    BinaryFormatter crip = new BinaryFormatter();
//					gnutellaWebCache = (SortedList)crip.Deserialize(fStream);
//					fStream.Close();
                    gnutella2WebCache = (SortedList) crip.Deserialize(fStream2);
                    fStream2.Close();
                }
                catch
                {
                    //fStream.Close();
                    fStream2.Close();
                    Debug.WriteLine("LoadWebCache");
                    try
                    {
                        File.Delete(Utils.GetCurrentPath("webcache.fscp"));
                    }
                    catch
                    {
                        Debug.WriteLine("Deleting webcache.fscp");
                    }
                    try
                    {
                        File.Delete(Utils.GetCurrentPath("webcache2.fscp"));
                    }
                    catch
                    {
                        Debug.WriteLine("Deleting webcache2.fscp");
                    }
                    HashEngine.Stop();
                    Application.Exit();
                }
            }

            public static void SaveWebCache()
            {
                BinaryFormatter crip = new BinaryFormatter();
                //FileStream fStream = new FileStream(Utils.GetCurrentPath("webcache.fscp"), FileMode.Create, FileAccess.Write);
                FileStream fStream2 = new FileStream(Utils.GetCurrentPath("webcache2.fscp"), FileMode.Create,
                                                     FileAccess.Write);
                //crip.Serialize(fStream, gnutellaWebCache);
                crip.Serialize(fStream2, gnutella2WebCache);
                //fStream.Close();
                fStream2.Close();
            }

            public static void LoadBlockedChatHosts()
            {
                FileStream fStream = new FileStream(Utils.GetCurrentPath("chatblocks.fscp"), FileMode.Open,
                                                    FileAccess.Read);
                try
                {
                    BinaryFormatter crip = new BinaryFormatter();
                    blockedChatHosts = (ArrayList) crip.Deserialize(fStream);
                    fStream.Close();
                }
                catch
                {
                    fStream.Close();
                    Debug.WriteLine("LoadBlockedChatHosts");
                    try
                    {
                        File.Delete(Utils.GetCurrentPath("chatblocks.fscp"));
                    }
                    catch
                    {
                        Debug.WriteLine("Deleting chatblocks.fscp");
                    }
                    HashEngine.Stop();
                    Application.Exit();
                }
            }

            public static void SaveBlockedChatHosts()
            {
                BinaryFormatter crip = new BinaryFormatter();
                FileStream fStream = new FileStream(Utils.GetCurrentPath("chatblocks.fscp"), FileMode.Create,
                                                    FileAccess.Write);
                crip.Serialize(fStream, blockedChatHosts);
                fStream.Close();
            }

            public static void LoadLastFileSet()
            {
                if (File.Exists(Utils.GetCurrentPath("hashlist.fscp")))
                {
                    FileStream fStream = new FileStream(Utils.GetCurrentPath("hashlist.fscp"), FileMode.Open,
                                                        FileAccess.Read);
                    try
                    {
                        BinaryFormatter crip = new BinaryFormatter();
                        lastFileSet = (Hashtable) crip.Deserialize(fStream);
                        fStream.Close();
                    }
                    catch
                    {
                        fStream.Close();
                        Debug.WriteLine("LoadLastFileSet");
                        try
                        {
                            File.Delete(Utils.GetCurrentPath("hashlist.fscp"));
                        }
                        catch
                        {
                            Debug.WriteLine("Deleting hashlist.fscp");
                        }
                        HashEngine.Stop();
                        Application.Exit();
                    }
                }
            }

            public static void SaveLastFileSet()
            {
                //copy everything first
                CopyHashes();

                //normal procedure
                BinaryFormatter crip = new BinaryFormatter();
                FileStream fStream = new FileStream(Utils.GetCurrentPath("hashlist.fscp"), FileMode.Create,
                                                    FileAccess.Write);
                crip.Serialize(fStream, lastFileSet);
                fStream.Close();
            }

            //this is used to stop running UpdateShares for an amount of time

            /// <summary>
            /// This function should be called any time the share list is changed.
            /// It should also be called if a change in files occurs (deletion, creation, etc.).
            /// It will scan all of the shared directories and record filenames.
            /// Then it will create an appropriate query routing table.
            /// </summary>
            public static void UpdateShares()
            {
                if (suppressUpdate)
                    return;

                //dispose/disable any current file system watchers
                StopFSWs();

                //stop hashing for now
                HashEngine.Stop();
                while (HashEngine.IsAlive())
                    Thread.Sleep(20);

                CopyHashes();

                //run the rest of the routine
                //Gnutella.QueryRouteTable.PrepareTable();
                QueryRouteTable.PrepareTable();
                lock (fileList)
                    fileList.Clear();
                Updated.filesShared = 0;
                Updated.kbShared = 0;
                if (shareList.Count > 0)
                    lock (shareList)
                    {
                        //loop through each shared directory
                        foreach (object item in shareList)
                        {
                            try
                            {
                                //add all of the files in the directory to fileList
                                string[] files = Directory.GetFiles(item.ToString());
                                //loop through each file
                                foreach (string file in files)
                                {
                                    FileInfo inf = new FileInfo(file);
                                    FileObject fileProps = new FileObject();
                                    //record path and filesize
                                    fileProps.location = file;
                                    fileProps.lcaseFileName = Path.GetFileName(fileProps.location).ToLower();
                                    fileProps.b = (uint) inf.Length;
                                    fileProps.fileIndex = Updated.filesShared;
                                    fileProps.md4 = null;
                                    fileProps.sha1bytes = null;
                                    fileProps.sha1 = "";
                                    //update variables
                                    Updated.filesShared++;
                                    Updated.kbShared += Convert.ToInt32(Math.Round((double) fileProps.b/1024));
                                    //add the class object to the ArrayList
                                    lock (fileList)
                                        fileList.Add(fileProps);
                                    //Gnutella.QueryRouteTable.Add(Path.GetFileName(fileProps.location));
                                    QueryRouteTable.AddWords(Path.GetFileName(fileProps.location));
                                }
                            }
                            catch
                            {
                                Debug.WriteLine("directory doesn't exist");
                            }
                        }
                    }
                //Gnutella.QueryRouteTable.CreatePatches();
                //Gnutella.QueryRouteTable.SendQRTables();
                QueryRouteTable.SendQRT();

                //let the GUI know that we altered the shared directories / files
                GUIBridge.ChangeShared();

                //start file system watchers
                StartFSWs();

                //restart what we ended before
                HashEngine.Start();

                //update the HomePage
                GUIBridge.RefreshHomePageLibrary();
            }

            /// <summary>
            /// Here we're going to take a shortcut.
            /// We don't want to redundantly generate sha1 values again and waste cpu.
            /// We'll copy the hashes we already have and then HashEngine will take care of them.
            /// </summary>
            public static void CopyHashes()
            {
                if (!suppressCopy)
                {
                    try
                    {
                        lock (fileList)
                        {
                            if (!hashEngineAborted)
                                lastFileSet.Clear();
                            //find files that have hashes already calculated
                            foreach (FileObject fo in fileList)
                                if (fo.sha1 != "")
                                {
                                    FileObject2 fo2 = new FileObject2();
                                    fo2.bytes = fo.b;
                                    fo2.md4 = fo.md4;
                                    fo2.sha1 = fo.sha1;
                                    fo2.sha1bytes = fo.sha1bytes;
                                    if (hashEngineAborted)
                                    {
                                        if (lastFileSet.ContainsKey(fo.location))
                                            lastFileSet[fo.location] = fo2;
                                        else
                                            lastFileSet.Add(fo.location, fo2);
                                    }
                                    else
                                        lastFileSet.Add(fo.location, fo2);
                                }
                        }
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine("Stats CopyHashes: " + e.Message);
                    }
                    if (hashEngineAborted)
                        hashEngineAborted = false;
                }
                else
                    suppressCopy = false;
            }
        }

        #endregion

        #region  filesystenwatch and related funtions

        /// <summary>
        /// Turn on the file system watchers.
        /// </summary>
        private static void StartFSWs()
        {
            if (shareList.Count > 0)
            {
                //instantiate each watcher
                fsws = new FileSystemWatcher[shareList.Count];
                //setup everything accordingly
                for (int x = 0; x < shareList.Count; x++)
                {
                    try
                    {
                        fsws[x] = new FileSystemWatcher(shareList[x].ToString());
                        fsws[x].NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite |
                                               NotifyFilters.FileName | NotifyFilters.DirectoryName;
                        fsws[x].Created += OnChanged;
                        fsws[x].Deleted += OnChanged;
                        fsws[x].Renamed += OnRenamed;
                        fsws[x].Error += OnError;
                        fsws[x].EnableRaisingEvents = true;
                    }
                    catch
                    {
                        Debug.WriteLine("Stats UpdateShares system watchers");
                    }
                }
            }
        }

        /// <summary>
        /// Turn off the file system watchers.
        /// </summary>
        public static void StopFSWs()
        {
            if (fsws != null)
                if (fsws.Length > 0)
                    foreach (FileSystemWatcher watcher in fsws)
                    {
                        try
                        {
                            watcher.EnableRaisingEvents = false;
                            watcher.Created -= OnChanged;
                            watcher.Deleted -= OnChanged;
                            watcher.Renamed -= OnRenamed;
                            watcher.Error -= OnError;
                            watcher.Dispose();
                        }
                        catch
                        {
                            Debug.WriteLine("Stats UpdateShares watcher dispose");
                        }
                    }
        }

        /// <summary>
        /// A shared directory was probably deleted.
        /// </summary>
        private static void OnError(object sender, ErrorEventArgs e)
        {
            LoadSave.UpdateShares();
        }

        /// <summary>
        /// A file/directory was added or deleted.
        /// </summary>
        private static void OnChanged(object source, FileSystemEventArgs e)
        {
            LoadSave.UpdateShares();
        }

        /// <summary>
        /// A file/directory was renamed.
        /// </summary>
        private static void OnRenamed(object source, RenamedEventArgs e)
        {
            LoadSave.UpdateShares();
        }

        private static void tmrCheckDirs_Tick(object sender, ElapsedEventArgs e)
        {
            bool changeMade = false;
            lock (shareList)
            {
                if (shareList.Count > 0)
                    for (int x = shareList.Count - 1; x >= 0; x--)
                        if (!Directory.Exists((string) shareList[x]))
                        {
                            changeMade = true;
                            shareList.RemoveAt(x);
                        }
            }
            if (changeMade)
                LoadSave.UpdateShares();
        }

        private static void tmrChores_Tick(object sender, ElapsedEventArgs e)
        {
            if (Updated.timestamp == int.MaxValue)
            {
                Updated.timestamp = int.MinValue;
                return;
            }
            Updated.timestamp++;
        }

        #endregion

        #region Nested type: Updated

        /// <summary>
        /// Updated statistics during runtime.
        /// </summary>
        public class Updated
        {
            public static volatile bool closing; //are we closing FileScope?
            public static volatile int downloadsNow2; //# of files currently downloading
            public static volatile bool everIncoming; //accepted an incoming tcp connection?
            public static volatile int filesShared; //# of files you're sharing
            public static volatile int inNetworkBandwidth; //b/s inward network bandwidth
            public static volatile int inTransferBandwidth; //b/s inward transfers bandwidth
            public static volatile int kbShared; //kilobytes you're sharing
            public static volatile bool le = true; //little or big endian
            public static volatile IPAddress myIPA; //our ip address
            public static volatile int numChats; //number of active chats
            public static volatile bool opened; //did we fully open FileScope?
            public static volatile int outNetworkBandwidth; //b/s outward network bandwidth
            public static volatile int outTransferBandwidth; //b/s outward transfers bandwidth
            public static volatile int timestamp;
            public static volatile int trueSpeed; //"real" speed of connection
            public static volatile bool udpIncoming; //ever received incoming udp packets
            public static volatile int uploads; //# of files you've uploaded
            public static volatile int uploadsNow2; //# of files currently uploading

            public static int uploadsNow
            {
                get { return uploadsNow2; }
                set
                {
                    uploadsNow2 = value;
                    GUIBridge.RefreshHomePageTransfers();
                }
            }

            public static int downloadsNow
            {
                get { return downloadsNow2; }
                set
                {
                    downloadsNow2 = value;
                    GUIBridge.RefreshHomePageTransfers();
                }
            }

            #region Nested type: Gnutella2

            public class Gnutella2
            {
                public static volatile bool enableDeflate;
                                            //determines whether or not deflate should be used between g2 connections

                public static volatile int lastConnectionCount2; //stores the number of connections
                public static volatile int numKHL;
                public static volatile int numLNI;

                public static volatile int numPI;
                public static volatile int numPO;
                public static volatile int numPUSH;
                public static volatile int numQ2;
                public static volatile int numQA;
                public static volatile int numQH2;
                public static volatile int numQHT;
                public static volatile int numQKA;
                public static volatile int numQKR;
                public static volatile bool ultrapeer; //are we an ultrapeer now or not?

                public static int lastConnectionCount
                {
                    get { return lastConnectionCount2; }
                    set
                    {
                        bool sameCount = (lastConnectionCount2 == value);
                        lastConnectionCount2 = value;
                        if (!sameCount)
                            GUIBridge.RefreshHomePageNetworks();
                    }
                }
            }

            #endregion
        }

        #endregion
    }
}