Go Back   RunUO - Ultima Online Emulation > RunUO > Custom Script Release Archive

Custom Script Release Archive This is a pre-script database archive of what our users had released.

 
 
Thread Tools Display Modes
Old 02-15-2006, 03:56 AM   #1 (permalink)
Forum Expert
 
Join Date: Feb 2003
Location: East Coast USA
Posts: 1,382
Default AutoSave with Archive backups, free disk space verification, and custom gump support.

Sunday, February 19, 2006 -- 2:05:45 AM EST

Major script update. Added toggles so that each feature can be enabled or disabled as necessary.

Features: (All features are optional.)

- Creates protected Archive backups in a seperate directory.

- Displays your custom AutoSave gump before a save, and closes it after.

- Checks for enough free disk space before Archive and Save.


Sunday, February 19, 2006 -- 12:17:53 PM EST - Fixed: Script now accounts for the fact that m_Archive may point to a different drive than the one RunUO is on. ^.^; Fully tested all 4 possible combinations of m_Archive and CHECKFREEDISK settings.

Tuesday, February 21, 2006 -- 4:44:50 AM EST - Added NoIOHour variable. Set to an hour of the day (in 24 hour format) to disallow saves during that time. Set to -1 to disable.

Code:
/*
NoIOHour - If datetime.now.hour == NoIOHour, then the saves will be
skipped. This is so that backup processes have a window of time where
there will not be any disk IO done by AutoSave. 24 hour format. (0=12am,
12=12pm, 23=11pm) It's like happy hour for backup programs, except
without any booze. :>  -1 to disable. (or a fictional hour. =)
*/
		//   No saving will be allowed during this hour.
		// public static int NoIOHour = 6;	// 6 am

		//   '-1' disables
		public static int NoIOHour = -1;	// disabled

AutoSave.cs: (php colored for easy reading. File also attached to this post.)
PHP Code:
/* AutoSave.cs - Modified by Alari (alarihyena@gmail.com)
Tuesday, February 21, 2006 -- 4:44:50 AM EST


Features:  (All features are optional.)

- Creates protected Archive backups in a seperate directory.

- Displays your custom AutoSave gump before a save, and closes it after.

- Checks for enough free disk space before Archive and Save.


Installation:

Rename RunUO\Scripts\Misc\AutoSave.cs to AutoSave.cs.disabled

Copy this AutoSave.cs to RunUO\Scripts\_Custom\AutoSave\AutoSave.cs
 or another directory of your choice under RunUO\Scripts


What is an Archive backup? :

A copy of your Saves directory, made before the Save is allowed to
occur. Unlike the Automatic backups, these Archive backups are not
overwritten by the AutoSave.cs script.

The default is to make one Archive backup every day. You can have the
script save more than one Archive backup per day, see the section on
GetArchiveTimeStamp() below for details.


Why is this useful? :

Imagine a hacker or disgruntled GM wipes your shard, then does '[save'
4-5 times to overwrite the Automatic backups, and you don't have any
other backup. Absurd? This has actually happened, a post on the RunUO
forum inspired me to make this modification.

PLEASE NOTE: This is NOT a replacement for doing regular REAL backups!
Please please PLEASE consider investing in a real backup device! CD/DVD
burners from NewEgg.com are cheap. (No, I don't get a kickback for
mentioning them. ;) However even if you do have a way of doing real
backups, this feature can help save your shard in case a backup was
missed or is corrupt.


What you need to do to use this:

These instructions list each setting that you can customize to change
how this AutoSave works. This header explains the basics of the
settings, and more details may be found at the locations in the file
where they are found. (Also given in this header)

I tried to move as many possible settings to the top of the script for
easier customization, but there are a LOT of possible changes that you
can make to the way this script works.


Settings:

At the beginning of the script:

Uncomment the USESGUMP define if you use a save gump.

Comment out the CHECKFREEDISK define if you do not wish to check for
 enough free disk space before doing a save. (Or possibly if you use
 Mono... Can someone using Mono please test?)

m_Delay - The delay between world saves.

m_Warning - How long before a world save are players warned?

m_Archive - The location to store Archive backups, or null to disable.

m_Notify - The minimum access level notified when the disk is full. 

NoIOHour - No saves will occur during this hour of the day.


Additional modifications:


In Save():

Change the name of 'SaveGump' if yours is called something else.

If you wish your save gump to remain up even after the save has
finished, comment out the second USESGUMP section. 


In m_Backups:
Modify to taste for number of Automatic backups to keep.
 Note: Automatic backups are not Archive backups! Archive backups are
 not overwritten. Automatic backups are.


In GetArchiveTimeStamp():
This controls how Archives are created. There are two versions of
GetArchiveTimeStamp() included, the default one will Archive once a day
while the other (commented out) example one will Archive twice a day.



------------------------------------------------------------------------
Monitor your server disk space usage and clean out your Archive
directory of old archived backups that you no longer want or need to
keep.

These backups will not be automatically overwritten like Automatic
backups are.

And let me tell you nothing sucks like running out of disk space on your
server. (Even with the code that checks for free disk space...)
------------------------------------------------------------------------



That's it! At least, those are the main things to modify in order to
configure this script. You may want to tweak other parts of the script
to customize it.



Note on free disk space checking:

The way the free disk space checks are done, the server should stop
making Archive backups before it stops saving. (Higher 'fudge factor'
setting.) This was done so that there would hopefully be time for admins
to clean out the disk on the server and still have current saves and
automatic backups.

Also, the code to check for free space when doing an Automatic backup
has been commented out. This is because Automatic backups should be
overwriting themselves, thus not really ever increasing the amount of
disk space being used. You can remove the comment marks from the code
to check disk space before an Automatic backup.


*/


// --- SETTINGS ---


// Uncomment this define to use a save gump. If your save gump is not
// called "SaveGump", search and replace that with the name of yours.

// #define USESGUMP


// Comment out this define if you do not wish to check for free disk
// space before a save, or possibly if you are running your server on
// Mono (Can anyone test if kernel32.dll calls work in Mono?)

#define CHECKFREEDISK


// --- END SETTINGS ---


using System;
using System.IO;
using Server;

#if USESGUMP
    // Possible using statements needed for your save gump.
    //  -- See your save gump documentation. --
    // Not sure why all the extras are needed, they should be
    // defined in the save gump itself, but someone said theirs
    // wasn't working without them.

    
using Server.Gumps;
    
using System.Reflection;    //?
    
using System.Collections;    //?
    
using Server.Network;        //?
    
using Server.Items;        //?
    
using Server.Mobiles;        //?
    
using Server.Misc;        //?
    
using Server.Accounting;    //?
#endif


#if CHECKFREEDISK
    
using Server.Scripts.Commands;        // needed for BroadCastMessage
    
using System.Runtime.InteropServices;    // for kernel32.dll
#endif



namespace Server.Misc
{
    public class 
AutoSave Timer
    
{
        
    
// --- SETTINGS ---

        //   Sets the delay between world saves.
        
private static TimeSpan m_Delay TimeSpan.FromMinutes60.0 );

        
    
// Use one of the following m_Warning settings:
        
        //   Sets the warning before a save to 60 seconds.
        
private static TimeSpan m_Warning TimeSpan.FromSeconds60.0 );

        
//   Disables the warning before a save.
        // private static TimeSpan m_Warning = TimeSpan.Zero;


    // Use one of the following m_Archive settings:
/*
I recommend changing the following m_Archive setting to point somewhere
outside the RunUO directory, and making whatever adjustments are
necessary to the number of Automatic backups kept (in m_Backups, below
Save() ) so that your RunUO directory will always fit nicely onto a
CD-R/RW... Of course, if you use DVD-Rs or tape or some such like that,
you don't have to worry about that. ;)
*/
        //   Saves Archive backups to RunUO\Backups\Archive
        
private static string m_Archive Path.CombineCore.BaseDirectory"Backups\\Archive" );

        
//   Saves Archive backups to a specific location.
        // private static string m_Archive = @"C:\Archive";

        //   Disables Archive backups.
        // private static string m_Archive = null;

        
    // This setting controls the minimum access level to be notified
    // if the disk is full.
        
private static AccessLevel m_Notify AccessLevel.Counselor;

/*
NoIOHour - If datetime.now.hour == NoIOHour, then the saves will be
skipped. This is so that backup processes have a window of time where
there will not be any disk IO done by AutoSave. 24 hour format. (0=12am,
12=12pm, 23=11pm) It's like happy hour for backup programs, except
without any booze. :>  -1 to disable. (or a fictional hour. =)
*/
        //   No saving will be allowed during this hour.
        // public static int NoIOHour = 6;    // 6 am

        //   '-1' disables
        
public static int NoIOHour = -1;    // disabled


    // --- END SETTINGS ---


/*
This is about as far down as you should have to modify for casual
adjustment of settings. Of course, feel free to change anything below
here to suit your needs.
*/


#if CHECKFREEDISK
        // Code submitted by Sorious

        
[DllImport("kernel32.dll"SetLastError=trueCharSet=CharSet.Auto)]
        [return: 
MarshalAs(UnmanagedType.Bool)]
           static 
extern bool GetDiskFreeSpaceEx(string lpDirectoryName,
           
out ulong lpFreeBytesAvailable,
           
out ulong lpTotalNumberOfBytes,
           
out ulong lpTotalNumberOfFreeBytes);
#endif

        
public static void Initialize()
        {
            new 
AutoSave().Start();
            
Commands.Register"SetSaves"AccessLevel.Administrator, new CommandEventHandlerSetSaves_OnCommand ) );
        }
        
        private static 
bool m_SavesEnabled true;
        
        public static 
bool SavesEnabled
        
{
            
get{ return m_SavesEnabled; }
            
setm_SavesEnabled value; }
        }
        
        [
Usage"SetSaves <true | false>" )]
        [
Description"Enables or disables automatic shard saving." )]
        public static 
void SetSaves_OnCommandCommandEventArgs e )
        {
            if ( 
e.Length == )
            {
                
m_SavesEnabled e.GetBoolean);
                
e.Mobile.SendMessage"Saves have been {0}."m_SavesEnabled "enabled" "disabled" );
            }
            else
            {
                
e.Mobile.SendMessage"Format: SetSaves <true | false>" );
                
e.Mobile.SendMessage"Saves are currently: {0}"m_SavesEnabled "enabled" "disabled" );
            }
        }
        
        public 
AutoSave() : basem_Delay m_Warningm_Delay )
        {
            
Priority TimerPriority.OneMinute;
        }
        
        protected 
override void OnTick()
        {
            if ( !
m_SavesEnabled || AutoRestart.Restarting )
                return;
            
            if ( 
m_Warning == TimeSpan.Zero )
            {
                
Save();
            }
            else
            {
                
int s = (int)m_Warning.TotalSeconds;
                
int m 60;
                
%= 60;
                
                if ( 
&& )
                    
World.Broadcast0x35true"The world will save in {0} minute{1} and {2} second{3}."m!= "s" ""s!= "s" "" );
                else if ( 
)
                    
World.Broadcast0x35true"The world will save in {0} minute{1}."m!= "s" "" );
                else
                    
World.Broadcast0x35true"The world will save in {0} second{1}."s!= "s" "" );
                
                
Timer.DelayCallm_Warning, new TimerCallbackSave ) );
            }
        }
        
        public static 
void Save()
        {
            if ( 
AutoRestart.Restarting )
                return;

            
// should world.save be allowed to occur at this time?
            
if ( NoIOHour != -)
            {
                
DateTime now DateTime.Now;
            
                if ( 
now.Hour == NoIOHour )
                {
                    
// DEBUG
                    // Console.WriteLine( "AutoSave.cs : NoIOHour : Saving not allowed during hour {0}.", NoIOHour );

                    
return;
                }
            }

#if USESGUMP
            
ArrayList mobs = new ArrayListWorld.Mobiles.Values ); 
            foreach ( 
Mobile m in mobs 
            {
                if ( 
!= null && m is PlayerMobile )
                    
m.SendGump ( new SaveGump() );
            }
#endif

#if CHECKFREEDISK
            
string[] rootdriv Core.BaseDirectory.Split'\\' );
            
string rootdrive rootdriv[0] + "\\";

            
// get size of Saves directory.
            
ulong savesize directorySizePath.CombineCore.BaseDirectory"Saves" ) );

            
// variables for GetDiskFreeSpaceEx.
            
ulong freeBytesAvailable;
            
ulong totalNumberOfBytes;
            
ulong totalNumberOfFreeBytes;

            
bool worked false;

            
worked GetDiskFreeSpaceExrootdrive,
                
out freeBytesAvailable,
                
out totalNumberOfBytes,
                
out totalNumberOfFreeBytes);

            
ulong totalBytes 0;

            if ( 
worked )
                
totalBytes freeBytesAvailable;
            else
                
totalBytes 1000000000;  // 1 gig

            // DEBUG
            //Console.WriteLine( "\n\nrootdrive: {0}", rootdrive );
            //Console.WriteLine( "freeBytesAvailable: {0:n}", freeBytesAvailable );
            //Console.WriteLine( "savesize: {0:n}\n\n", savesize );


        // m_Archive may point to a different drive than the one RunUO is on...


            
ulong atotalBytes 0;
            
string arootdrive "";

            if ( 
m_Archive != null )
            {
                
string[] arootdriv m_Archive.Split'\\' );
                
arootdrive arootdriv[0] + "\\";

                if ( 
arootdrive == rootdrive )
                {
                    
atotalBytes totalBytes;
                }
                else
                {
                    
worked GetDiskFreeSpaceExarootdrive,
                        
out freeBytesAvailable,
                        
out totalNumberOfBytes,
                        
out totalNumberOfFreeBytes);
    
                    if ( 
worked )
                        
atotalBytes freeBytesAvailable;
                    else
                        
atotalBytes 1000000000;  // 1 gig
                
}

                
// DEBUG
                //Console.WriteLine( "arootdrive: {0}", arootdrive );
                //Console.WriteLine( "atotalBytes: {0:n}\n\n", atotalBytes );
            
}


            
// ARCHIVE
            // * for fudge factor
            
if ( m_Archive != null && atotalBytes > ( savesize ) )
            {
#endif
                
if ( m_Archive != null )
                {
                    try{ 
Archive(); }
                    catch { 
Console.WriteLine"AutoSave.cs : Save() : try Archive() :\n Archive attempt failed!" ); }
                }
#if CHECKFREEDISK
            
}
            else if ( 
m_Archive != null )
            {
                
Console.WriteLine"Error! Not enough free disk space left on {0} to Archive!"arootdrive );
                
CommandHandlers.BroadcastMessagem_Notify33String.Format"[AutoSave.cs] Error! Not enough free disk space left on {0} to Archive!"arootdrive ) );
            }


/*            
Something tells me I should not be checking for free disk space during a
backup, since in the majority of cases, backups will be overwriting
existing backups and thus not using any additional space. Also, removing
the old Saves directory may be necessary, and it is Backup() which does
that. Leaving this feature commented out for now. There is a try command
wrapping it, so that should prevent a crash...
*/
            // BACKUP            
            // * for fudge factor
        //    if ( totalBytes > ( savesize * 2 ) )
        //    {
#endif
                
try{ Backup(); }
                catch{ 
Console.WriteLine"AutoSave.cs : Save() : try Backup() :\n Backup attempt failed!" ); }
#if CHECKFREEDISK
        //    }
        //    else
        //    {
        //        Console.WriteLine( "Error! Not enough free disk space left on {0} to Backup!", rootdrive );
        //        CommandHandlers.BroadcastMessage( m_Notify, 33, String.Format( "[AutoSave.cs] Error! Not enough free disk space left on {0} to Backup!", rootdrive ) );
        //    }
            
            
            // SAVE
            // * for fudge factor
            
if ( totalBytes > ( savesize ) )
            {
#endif
                
World.Save();
#if CHECKFREEDISK
            
}
            else
            {
                
Console.WriteLine"Error! Not enough free disk space left on {0} to Save!"rootdrive );
                
CommandHandlers.BroadcastMessagem_Notify33String.Format"[AutoSave.cs] Error! Not enough free disk space left on {0} to Save!"rootdrive ) );
            }
#endif
            
#if USESGUMP
            // Comment these lines out to keep your save
            // gump up after the save has completed.
            
foreach ( Mobile m in mobs 
            {
                if ( 
!= null && m is PlayerMobile )
                    
m.CloseGumptypeofSaveGump ) );
            }
#endif
        
}
        
        
// Original Automatic backup settings.
        // Each line equals one backup.
        /*
        private static string[] m_Backups = new string[]
        {
            "Third Backup",
            "Second Backup",
            "Most Recent"
        };
        */
        
        // I prefer to retain more Automatic backups.
        // Edit this section to fit your needs.
        
        
private static string[] m_Backups = new string[]
        {
            
"9th Backup - Oldest",
            
"8th Backup",
            
"7th Backup",
            
"6th Backup",
            
"5th Backup",
            
"4th Backup",
            
"3rd Backup",
            
"2nd Backup",
            
"1st Backup - Most Recent"
        
};
        
        
        
        private static 
void Backup()
        {
            if ( 
m_Backups.Length == )
                return;
            
            
string root Path.CombineCore.BaseDirectory"Backups\\Automatic" );
            
            if ( !
Directory.Existsroot ) )
                
Directory.CreateDirectoryroot );
            
            
string[] existing Directory.GetDirectoriesroot );
            
            for ( 
int i 0m_Backups.Length; ++)
            {
                
DirectoryInfo dir Matchexistingm_Backups[i] );
                
                if ( 
dir == null )
                    continue;
                
                if ( 
)
                {
                    
string timeStamp FindTimeStampdir.Name );
                    
                    if ( 
timeStamp != null )
                    {
                        try{ 
dir.MoveToFormatDirectoryrootm_Backups[1], timeStamp ) ); }
                        catch{ 
Console.WriteLine"AutoSave.cs : Backup() : try dir.MoveTo failed!" ); }
                    }
                }
                else
                {
                    try{ 
dir.Deletetrue ); }
                    catch{ 
Console.WriteLine"AutoSave.cs : Backup() : try dir.Delete failed!" ); }
                }
            }
            
            
string saves Path.CombineCore.BaseDirectory"Saves" );
            
            if ( 
Directory.Existssaves ) )
                
Directory.MovesavesFormatDirectoryrootm_Backups[m_Backups.Length 1], GetTimeStamp() ) );
        }
        
        private static 
DirectoryInfo Matchstring[] pathsstring match )
        {
            for ( 
int i 0paths.Length; ++)
            {
                
DirectoryInfo info = new DirectoryInfopaths[i] );
                
                if ( 
info.Name.StartsWithmatch ) )
                    return 
info;
            }
            
            return 
null;
        }
        
        private static 
string FormatDirectorystring rootstring namestring timeStamp )
        {
            return 
Path.CombinerootString.Format"{0} ({1})"nametimeStamp ) );
        }
        
        private static 
string FindTimeStampstring input )
        {
            
int start input.IndexOf'(' );
            
            if ( 
start >= )
            {
                
int end input.IndexOf')', ++start );
                
                if ( 
end >= start )
                    return 
input.Substringstartend-start );
            }
            
            return 
null;
        }
        
        private static 
string GetTimeStamp()
        {
            
DateTime now DateTime.Now;
            
            return 
String.Format"{0}-{1}-{2} {3}-{4:D2}-{5:D2}",
            
now.Day,
            
now.Month,
            
now.Year,
            
now.Hour,
            
now.Minute,
            
now.Second
            
);
        }

/*
GetArchiveTimeStamp controls how many Archive backups are made. The
basics of how it works is that Archive will copy the Saves directory to
a directory of the name returned by GetArchiveTimeStamp(), unless that
directory already exists. So changing what GetArchiveTimeStamp() returns
will change how many Archive backups are created.
*/

        // One Archive backup per day
        
private static string GetArchiveTimeStamp()
        {
            
DateTime now DateTime.Now;
            
            return 
String.Format"{0}-{1}-{2}",
            
now.Day,
            
now.Month,
            
now.Year
            
);
        }
        
    
/*
        // you could use this instead if you want to make
        //  two Archive backups per day.
        
        // Two Archive backups per day
        private static string GetArchiveTimeStamp()
        {
            DateTime now = DateTime.Now;
            
            string ampm;
            if ( now.Hour < 12 )
                ampm = "AM";
            else
                ampm = "PM";
            
            return String.Format( "{0}-{1}-{2}-{3}",
            ampm,
            now.Day,
            now.Month,
            now.Year
            );
        }
    */

        
public static void Archive()
        {
            
string root m_Archive;

            if ( 
root == null )   // shouldn't get this far but what the heck...
                
return;
            
            if ( !
Directory.Existsroot ) )
                
Directory.CreateDirectoryroot );
            
            
            
string archiveDir Path.CombinerootGetArchiveTimeStamp() );
            
            
string saves Path.CombineCore.BaseDirectory"Saves" );
            
            
            if ( !
Directory.ExistsarchiveDir ) )
            {
                if ( 
Directory.Existssaves ) )
                {
                    try { 
copyDirectorysavesarchiveDir ); }
                    catch
                    {
                        
Console.WriteLine"AutoSave.cs : Archive() : try copyDirectory :\n Archive copyDirectory failed!" );
                    }
                }
                else
                {
                    
Console.WriteLine"AutoSave.cs : Archive() : Directory.Exists( saves ) :\n Error! Archive did not occur because Saves directory did not exist!" );
                }
            }
        }
        
        
        
// copyDirectory from:
        // http://dotnetjunkies.com/WebLog/sajay/archive/2004/12/03/34763.aspx
        
        // Copy directory structure recursively
        
public static void copyDirectory(string srcstring dst)
        {
            
String[] files;
            if(
dst[dst.Length-1]!=Path.DirectorySeparatorChar)
                
dst+=Path.DirectorySeparatorChar;
            if(!
Directory.Exists(dst)) Directory.CreateDirectory(dst);
            
files=Directory.GetFileSystemEntries(src);
            foreach(
string element in files)
            {
                
// Sub directories
                
if(Directory.Exists(element))
                    
copyDirectory(element,dst+Path.GetFileName(element));
                
// files in directory
                
else
                    
File.Copy(element,dst+Path.GetFileName(element),true);
            }
        }

        
// returns real size of directory in bytes.
        
public static ulong directorySizestring dir )
        {
            
ulong size 0;
            
DirectoryInfo directory = new DirectoryInfodir );
            
            foreach( 
FileInfo file in directory.GetFiles() )
            {
                
size size + (ulong)file.Length;
    &n