Rake

From Second Life Wiki
Revision as of 23:16, 11 June 2012 by Flax Quirina (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

Rake 1.0 is a proof-of-concept texture/sculpt backup system that traverses the entire inventory and downloads all the textures it finds. This has been done before by Second Inventory which tops at a cost of 29$. Compared to Second Inventory, Rake only backs-up textures instead of entire objects. However, in the unforeseeable future other assets will be included. Another significant difference is that Rake does not provide a graphical user-interface with buttons.

Another interesting feature is that Rake not only downloads the textures but it also classifies them in separate directories. People with tidy inventories will be very happy and one can use free tools such as IrfanView to browse your collections of textures.

Rake operates on a one-off basis, it is not meant to run permanently, but rather to be used once to download the entire inventory.

The code relies on the OpenMetaverse Library.

Screenshot

RakePreview.png

Rake uses a recursive breadth-first algorithm to look for assets. Except the usual log-in and grid-selection, the rake-meter at the bottom is an indication of the current operations taking place. The key is a simple indication of what is currently being downloaded:

- stands for a directory,
T stands for texture,
A stands for animations (todo)

Usage

  • Create a new C# console application.
  • Edit the Main.cs file and replace it with the contents below.
  • Create a new class file called Types.cs and replace the contents with the code below.
  • Compile and run (debug mode is fine).
  • Enter your data, wait a few hours...
  • Rake exits when its job is done.
  • Browse to the Debug or Release bin folder and enjoy your textures.

Code

The code consists of two C# files which should be placed in the same directory.

Main.cs

using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using OpenMetaverse;
using System.Linq;

namespace Rake
{
    class Rake
    {
        private static readonly GridClient Client = new GridClient();
        private static string _firstName = string.Empty;
        private static string _lastName = string.Empty;
        private static string _password = string.Empty;
        private static readonly Thread RakeThread = new Thread(ProcessInventory);
        private static readonly List<bool> StabilizeChecks = new List<bool>();
        private static AutoResetEvent _folders = new AutoResetEvent(false);

        private static readonly KeyValuePair<char, string>[] Grids = new[] {
            new KeyValuePair<char, string>('1', "https://login.agni.lindenlab.com/cgi-bin/login.cgi"),
            new KeyValuePair<char, string>('2', "http://os.bio-se.info:9000/"),
            new KeyValuePair<char, string>('3', "Manual override.")
        };
        private static bool _overwriteExisting = true;

        private static readonly List<string> Logo = new List<string> {

        "                   ,v+s.                               ,.\n",
        "                 ,z~   Vs                           ,gW`Vi\n",
        "               _Y`      VWs                    __gmW@@@  ].\n",
        "             g/\\g=YM`    V@WmW@@@@@@@@@@@@@@@@@@@@@@@@@   [\n",
        "            Z+/~  _d 7e.  Y@@@@@~~~~~~V***@@@@@@@@@@@@@b  b\n",
        "               _m@@@Wv.Vm. @@@@@           'VM@@@@@@@@@@Wm*\n",
        "             gW@@@*fT@i '@ Y@@@@              Y@@@@@@@@@Wm_\n",
        "          ,g@@@*~  ,/. ,/` d@@@@              ]@@@@W 'VM@@@Ws\n",
        "       ,_m@@@*`    /Z gf  ,@@@@A*f~~~~~+=s_. g@@@@@P    '*@f'Ms\n",
        "    K~~******==*f~M[\f` ,g@Af~   ,mms.     Y@@@@@Af       'M/\\4W.\n",
        "    !z       i/    '+m@@*~`   ,gW@@@@@s.  i@@@@@|           Vbv*W.\n",
        "    gW|      M.      -'   _g=~~M@@@@@@@@@@@@@@@@@m_          'N-Z@.\n",
        "   i@@W_-.   '*s.     ,_mK`     MK` ]@@@@@@@@*fVN2~           'M\\_W\n",
        "  ,@@@f'*eK__s__2_gmm@*Y!Vtms   ]5__W@@@A*M@@.   'Vc.          'bgVb\n",
        "  W@@P            [,._z\\z   'Vmz` M@@@A`  ]@@[      '+_         -gs@i\n",
        " i@Af             '8M` K   ,_=Y=s  M@P     @@b        '\\s        M~VW\n",
        " Wf ,-'`|~`-.     gf,vf~\\s_/    V  '*      @@@b          Vs      ][~@i\n",
        "i` .' . i  -'c   ,P,_     '      '- !i     @@@@._Y~Ve_.   'N.     b ~[\n",
        "! ]  i~-L-'. '.  ![~V             !  b    vf**MK.    V@D__  Ms    @M@[\n",
        "s ]  !,-7-,` ,v=e b               i  P  ,/      ~N.   '+ '` ]@b   ]_,b\n",
        "@. `,-` !  - P  A !i              /  [  K .     , Y.       ,W@@i  W-i[\n",
        "]W_ '=,_L .-`t  !s Ys            v  i[  'VMf  ,g@mm@m____gW@@@@[  A 4[\n",
        "]@*+=g[ 'i,g=Yc  4@['c   _-     .  ,@[     Yms_d@@@@@@@@@@@@@@@[ ,[ @[\n",
        " @_   ~- '`      ,W[ ]W_v`    v   g@@      ]@@@@@@@@@@@@@@@@@@A  d g@`\n",
        " ]@@D-  __gmmm,_L@@` !A~   ,-'  g@@@!      ]@@~M@@@@@@@@@@@@Af  i[-WP\n",
        "  5_mm@~    i!]@@@A  ds    [,gW@@@A`       ]@@ 'M@@@@@@**f~    ,AsdA\n",
        "   M@@@s    ] ]@@A` g@@@W- `]@@@A~         ]@@  'M@@@@@       ,P-gW`\n",
        "    M@@@b   !['*f  g@@Af`_- ,f~            ]@@.  'M@@@@W.   ,g@WmA`\n",
        "     V@@@W.  'e__Z~!@K_mmz=f`   Wizardry   ]@@b   '@@@@@@Wsg@@@@f\n",
        "      'M@@@W.      iA  '`i        and      ]@@@i   Y@@@@@@@@@@@b.\n",
        "        'M@@@Ws___zP/    Vs    Steamworks  ]@@@@s  '@@@@@@@@@@@@@Ws.\n",
        "          'V@@@@A`        'N_              ]@@@@@@s_@@@@@@@@@@@@@@@A!\n",
        "             ~*Mb. _        'V=e_.         ]@@@@@@@@@@@@@@@@@@*~`\n",
        "                '~*Wmmm_______.____        !@@@@@@@@@@@@*f~~`\n",
        "                     ~V**@@@@@@@@@@Wmgmmm@@W@@@@@A~~~~`\n",
        "                           '~~~********M@@********\n",
        "                                                                      \n",
        "                        \"It's not raPe, it's raKe!\"               \n\n" };

        public static void Main()
        {
            foreach (var row in Logo)
            {
                new List<int>(new int[(Console.WindowWidth - 70) / 2]).ForEach(i => Console.Write(" "));
                Console.Write(row);
            }

            Settings.LOG_LEVEL = Helpers.LogLevel.None;
            Client.Settings.STORE_LAND_PATCHES = true;
            Client.Settings.ALWAYS_DECODE_OBJECTS = true;
            Client.Settings.ALWAYS_REQUEST_OBJECTS = true;
            Client.Settings.SEND_AGENT_UPDATES = true;
            Client.Settings.USE_ASSET_CACHE = false;

            Client.Network.LoginProgress += HandleLoginProgress;
            Client.Appearance.AppearanceSet += HandleAppearanceSet;
            Client.Network.SimConnected += HandleSimulatorConnected;

            Console.Write("[Rake] : First Name : ");
            var line = Console.ReadLine();
            if (line != null) _firstName = line.Trim();
            Console.Write("[Rake] : Last Name : ");
            line = Console.ReadLine();
            if (line != null) _lastName = line.Trim();
            Console.Write("[Rake] : Password : ");
            var info = Console.ReadKey(true);
            while (info.Key != ConsoleKey.Enter)
            {
                if (info.Key != ConsoleKey.Backspace)
                {
                    Console.Write("*");
                    _password += info.KeyChar;
                }
                else if (!string.IsNullOrEmpty(_password))
                {
                    _password = _password.Substring(0, _password.Length - 1);
                    var pos = Console.CursorLeft;
                    Console.SetCursorPosition(pos - 1, Console.CursorTop);
                    Console.Write(" ");
                    Console.SetCursorPosition(pos - 1, Console.CursorTop);
                }
                info = Console.ReadKey(true);
            }
            Console.WriteLine();
        invalid_overwrite:
            Console.Write("[Rake] : Overwrite files? [y/n] : ");
            info = Console.ReadKey(true);
            Console.WriteLine(info.KeyChar);
            switch (info.KeyChar)
            {
                case 'y':
                case 'Y':
                    _overwriteExisting = true;
                    break;
                case 'n':
                case 'N':
                    _overwriteExisting = false;
                    break;
                default:
                    goto invalid_overwrite;
            }
        invalid_grid:
            foreach (var grid in Grids)
            {
                Console.WriteLine("{0}. {1}", grid.Key, grid.Value);
            }
            Console.Write("[Rake] : Select grid: ");
            info = Console.ReadKey(true);
            if (Grids.Count(g => g.Key.Equals(info.KeyChar)) == 0)
            {
                Console.WriteLine();
                goto invalid_grid;
            }
            switch (info.KeyChar)
            {
                case '3':
                    Console.WriteLine();
                    Console.Write("[Rake] : Please type a login URI : ");
                    line = Console.ReadLine();
                    if (line != null) Client.Settings.LOGIN_SERVER = line.Trim();
                    break;
                default:
                    Client.Settings.LOGIN_SERVER = Grids.First(g => g.Key.Equals(info.KeyChar)).Value;
                    break;

            }
            Console.Write(info.KeyChar);
            Console.WriteLine();

            new List<int>(new int[Console.WindowWidth]).ForEach(i => Console.Write("-"));

            var login = new LoginParams(Client, _firstName, _lastName, _password, "[WaS] Rake", "1.0");
            Console.Write("[Rake] : Starting login...");
            Client.Network.BeginLogin(login);
            RakeThread.Start();
        }

        static void HandleSimulatorConnected(object sender, SimConnectedEventArgs e)
        {
            if (!e.Simulator.Connected) return;
            Client.Network.SimConnected -= HandleSimulatorConnected;
            Console.Write("\n[Rake] : Simulator connected...");
            StabilizeChecks.Add(true);
        }

        static void HandleAppearanceSet(object sender, AppearanceSetEventArgs e)
        {
            if (!e.Success) return;
            Console.Write("\n[Rake] : Appearance set...");
            StabilizeChecks.Add(true);
        }
        static void HandleLoginProgress(object sender, LoginProgressEventArgs e)
        {
            if (e.Status == LoginStatus.Success)
            {
                Console.Write("\n[Rake] : Login ok...");
                StabilizeChecks.Add(true);
            }
            switch (e.Status)
            {
                case LoginStatus.Failed:
                    Console.Write("\n[Rake] : Failed Login...\n");
                    break;
            }
        }

        static void ProcessInventory()
        {
            Console.Write("\n[Rake] : Stabilizing, please wait...");

            while (StabilizeChecks.Count < 3)
            {
                switch (Client.Network.LoginStatusCode)
                {
                    case LoginStatus.Failed:
                        return;
                }
                Thread.Sleep(1000);
                Console.Write(".");
            }

            Console.Write("\n[Rake] : Raking: ");
            RakeInventory(Client.Inventory.Store.RootFolder);
            Console.WriteLine();
            new List<int>(new int[Console.WindowWidth]).ForEach(i => Console.Write("-"));
            Console.WriteLine("[Rake] : All operations completed.");
            Client.Network.Logout();
        }

        static void RakeInventory(InventoryBase root)
        {
            Thread.Sleep(Client.Network.CurrentSim.Stats.LastLag);

            _folders = new AutoResetEvent(false);
            Client.Inventory.FolderUpdated += InventoryFolderUpdated;
            Client.Inventory.RequestFolderContents(root.UUID, Client.Self.AgentID, true, true, InventorySortOrder.ByName);
            _folders.WaitOne(60000, false);

            var ic = Client.Inventory.FolderContents(root.UUID, Client.Self.AgentID,
                true, true, InventorySortOrder.ByName, 10000);

            if (ic == null)
            {
                Thread.Sleep(1000);
                RakeInventory(root);
            }

            foreach (var ib in ic)
            {
                var done = new AutoResetEvent(false);
                switch (Types.GetInventoryType(ib))
                {
                    case RakeInventoryType.RakeInventoryTexture:
                        #region Texture Rake
                        var texture = (InventoryTexture)ib;
                        var texturePath = Path.Combine(_firstName + " " + _lastName, "Assets", "Images", Path.GetInvalidFileNameChars().Aggregate(root.Name, (current, c) => current.Replace(c.ToString(), string.Empty)));
                        if (!Directory.Exists(texturePath))
                            Directory.CreateDirectory(texturePath);

                        var normalName = Path.GetInvalidFileNameChars().Aggregate(texture.Name + ".jp2",
                                                                                  (current, c) =>
                                                                                  current.Replace(c.ToString(),
                                                                                                  string.Empty));

                        if (!_overwriteExisting && FindFile(normalName, Path.Combine(_firstName + " " + _lastName, "Assets", "Images"))) {
                            Console.Write(" ");
                            break;
                        }

                        Client.Assets.RequestImage(texture.AssetUUID, ImageType.Normal, (state, asset) =>
                        {
                            if (state == TextureRequestState.Finished)
                            {
                                Console.Write("T");
                                File.WriteAllBytes(Path.Combine(texturePath, normalName), asset.AssetData);
                            }
                            done.Set();
                        }, false);
                        done.WaitOne(60000, false);
                        #endregion Texture Rake
                        break;
                    case RakeInventoryType.RakeInventoryAnimation:
                        #region Animation Rake
                        var animation = (InventoryAnimation)ib;
                        var animationPath = Path.Combine(_firstName + " " + _lastName, "Assets", "Animations", root.Name);
                        if (!Directory.Exists(animationPath))
                            Directory.CreateDirectory(animationPath);

                        Client.Assets.RequestAsset(animation.AssetUUID, AssetType.Animation, true, (download, asset) =>
                        {
                            if (download.Success)
                            {
                                Console.Write("A");
                                File.WriteAllBytes(Path.Combine(animationPath, animation.Name + ".animatn"), asset.AssetData);

                            }
                            done.Set();
                        });
                        done.WaitOne(60000, false);
                        #endregion Animation Rake
                        break;
                    case RakeInventoryType.RakeInventoryFolder:
                        var folder = (InventoryFolder)ib;
                        Console.Write("-");
                        if (ib.Name != "Trash") RakeInventory(folder);
                        break;
                }
            }
        }

        static bool FindFile(string file, string directory)
        {
            try
            {
                foreach (var d in Directory.GetDirectories(directory))
                {
                    if (Directory.GetFiles(d).Any(f => f.Equals(file)))
                    {
                        return true;
                    }
                    FindFile(file, d);
                }
            }
            catch (Exception e)
            {
                return false;
            }
            return false;
        }

        static void InventoryFolderUpdated(object sender, FolderUpdatedEventArgs e)
        {
            if (e.Success)
            {
                _folders.Set();
            }
        }
    }
}

Types.cs

using OpenMetaverse;

namespace Rake
{
    public enum RakeInventoryType { RakeInventoryTexture, RakeInventoryFolder, RakeInventoryAnimation, RakeInventoryNotImplemented };
    public static class Types
    {
        public static RakeInventoryType GetInventoryType(object o)
        {
            if (o is InventoryTexture) return RakeInventoryType.RakeInventoryTexture;
            if (o is InventoryFolder) return RakeInventoryType.RakeInventoryFolder;
            return RakeInventoryType.RakeInventoryNotImplemented;
        }
    }
}