Ejemplo n.º 1
0
        public static void TestOperationGETDoesNotFail()
        {
            _provider.PutAsset(_asset);

            Assert.DoesNotThrow(() => {
                _provider.GetAsset(_asset.Uuid);
            }, "Failed to retreive the asset just stored.");
        }
Ejemplo n.º 2
0
        public ThreadTest(RemoteServer server, bool alsoPurgeLocal)
        {
            _server = server;

            //setup the test by adding 100 shared assets
            Console.WriteLine("Putting 100 random assets to server");
            Console.WriteLine(DateTime.Now);
            SHA1 sha = new SHA1CryptoServiceProvider();


            for (int i = 0; i < 100; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes();
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server.PutAsset(asset);
            }

            if (alsoPurgeLocal)
            {
                _server.MaintPurgeLocals();
            }

            Console.WriteLine("Done: " + DateTime.Now);
        }
Ejemplo n.º 3
0
        public void SingleWrite()
        {
            string uuidstr = OpenMetaverse.UUID.Random().ToString();

            byte[] randomBytes = TestUtil.RandomBytes();

            Asset asset = new Asset(uuidstr, 1,
                                    false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);

            _server.PutAsset(asset);
        }
        public CrossServerBiasTest3(RemoteServer server1, RemoteServer[] server2, RemoteServer server3)
        {
            _server1 = server1;
            _server2 = server2;
            _server3 = server3;

            //setup the test by adding 200 shared assets
            Console.WriteLine("Putting 200 random assets to server1");
            Console.WriteLine(DateTime.Now);
            SHA1 sha = new SHA1CryptoServiceProvider();


            for (int i = 0; i < 200; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes();
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server1.PutAsset(asset);
            }

            Console.WriteLine("Done: " + DateTime.Now);

            Console.WriteLine("Putting 10 random assets to server2");
            Console.WriteLine(DateTime.Now);


            for (int i = 0; i < 10; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes(1000, 50000);
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server2[0].PutAsset(asset);
                _server3.PutAsset(asset);
            }

            Console.WriteLine("Done: " + DateTime.Now);
        }
Ejemplo n.º 5
0
        static void CopyAsset(string uuid, RemoteServer src, RemoteServer dst)
        {
            lock (Conn)
            {
                using (SQLiteCommand countCmd = new SQLiteCommand("SELECT COUNT(*) FROM copied_assets WHERE uuid = '" + uuid + "'", Conn))
                {
                    int count = Convert.ToInt32(countCmd.ExecuteScalar());

                    if (count != 0)
                    {
                        Console.WriteLine("Skipping {0}", uuid);
                        return;
                    }
                }
            }

            Console.WriteLine("Copying {0} ({1})", uuid, NumCopied);

            try
            {
                Asset asset = src.GetAsset(uuid);
                dst.PutAsset(asset);

                lock (Conn)
                {
                    using (SQLiteCommand cmd = new SQLiteCommand("INSERT INTO copied_assets(uuid) VALUES('" + uuid + "');", Conn))
                    {
                        cmd.ExecuteNonQuery();

                        NumCopied++;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to copy asset: " + e.Message);
                using (StreamWriter writer = new StreamWriter("error.txt"))
                {
                    writer.WriteLine("Unable to copy asset: " + e.Message);
                    writer.Close();
                }

                Console.WriteLine("Error detected, any key continues");
                Console.ReadLine();
            }
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Handles a request to store an asset to the remote server.
        /// </summary>
        /// <param name="asset">Asset.</param>
        /// <exception cref="T:Chattel.AssetWriteException">Thrown if there was an error storing the asset.</exception>
        public void StoreAssetSync(StratusAsset asset)
        {
            asset = asset ?? throw new ArgumentNullException(nameof(asset));
            if (asset.Id == Guid.Empty)
            {
                throw new ArgumentException("Assets must not have a zero ID");
            }

            try {
                _provider.PutAsset(StratusAsset.ToWHIPAsset(asset));
            }
            catch (AssetServerError e) {
                LOG.Log(Logging.LogLevel.Error, () => $"[{_serverHandle}] Error sending asset to server.", e);
                throw new AssetWriteException(asset.Id, e);
            }
            catch (AuthException e) {
                LOG.Log(Logging.LogLevel.Error, () => $"[{_serverHandle}] Authentication error sending asset to server.", e);
                throw new AssetWriteException(asset.Id, e);
            }
        }
        public void Run()
        {
            int i     = 0;
            int count = 0;

            MySqlCommand countCmd =
                new MySqlCommand(
                    "SELECT COUNT(*) as CNT " +
                    "FROM assets " +
                    "WHERE create_time >= ?createTime ",
                    _conn);

            countCmd.Parameters.AddWithValue("?createTime", _startAt);

            countCmd.CommandType = CommandType.Text;
            count = Convert.ToInt32(countCmd.ExecuteScalar());
            countCmd.Dispose();

            MySqlCommand cmd =
                new MySqlCommand(
                    "SELECT id, name, description, assetType, local, temporary, data, create_time " +
                    "FROM assets " +
                    "WHERE create_time >= ?createTime " +
                    "ORDER BY create_time",
                    _conn);

            cmd.Parameters.AddWithValue("?createTime", _startAt);



            try
            {
                using (MySqlDataReader dbReader = cmd.ExecuteReader())
                {
                    while (dbReader.Read() && !_stop)
                    {
                        bool local;
                        bool temp;
                        try
                        {
                            local = (bool)dbReader["local"];
                        }
                        catch (InvalidCastException)
                        {
                            local = false;
                        }

                        try
                        {
                            temp = (bool)dbReader["temporary"];
                        }
                        catch (InvalidCastException)
                        {
                            temp = false;
                        }

                        string uuid       = (string)dbReader["id"];
                        sbyte  assetType  = (sbyte)dbReader["assetType"];
                        int    createTime = (int)dbReader["create_time"];
                        string name       = (string)dbReader["name"];
                        string desc       = (string)dbReader["description"];
                        byte[] data       = (byte[])dbReader["data"];

                        Asset whipAsset =
                            new Asset(
                                uuid,
                                (byte)assetType,
                                local,
                                temp,
                                createTime,
                                name,
                                desc,
                                data);

                        try
                        {
                            _server.PutAsset(whipAsset);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("Error putting asset: " + e.Message);
                        }

                        Console.WriteLine("Writing " + (string)dbReader["id"]);
                        Console.WriteLine(++i + " of " + count);

                        _startAt = (int)dbReader["create_time"];
                    }
                    dbReader.Close();
                    cmd.Dispose();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine("Last time written: " + _startAt);
            }

            Console.WriteLine("Last time written: " + _startAt);
        }
        public CrossServerTestMulticonn(RemoteServer server1, RemoteServer server2, RemoteServer server3,
                                        RemoteServer[] allConnections, bool putDupesToAll)
        {
            _server1        = server1;
            _server2        = server2;
            _server3        = server3;
            _allConnections = allConnections;

            Console.WriteLine("Putting 100 random assets to server1");
            Console.WriteLine(DateTime.Now);
            SHA1 sha = new SHA1CryptoServiceProvider();


            for (int i = 0; i < 100; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes(500, 800000);
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server1.PutAsset(asset);
            }

            Console.WriteLine("Done: " + DateTime.Now);

            Console.WriteLine("Putting 100 random assets to server2");
            Console.WriteLine(DateTime.Now);


            for (int i = 0; i < 100; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes(500, 800000);
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server2.PutAsset(asset);
            }

            Console.WriteLine("Done: " + DateTime.Now);

            Console.WriteLine("Putting 100 random assets to server3");
            Console.WriteLine(DateTime.Now);


            for (int i = 0; i < 100; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes(500, 800000);
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server3.PutAsset(asset);
            }

            Console.WriteLine("Putting  duplicate assets to all servers");

            for (int i = 0; i < 20; i++)
            {
                string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                byte[] randomBytes   = TestUtil.RandomBytes(500, 800000);
                byte[] challengeHash = sha.ComputeHash(randomBytes);
                _assetUuids.Add(uuidstr);
                _existingAssets.Add(uuidstr, challengeHash);

                Asset asset = new Asset(uuidstr, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                _server1.PutAsset(asset);

                if (putDupesToAll)
                {
                    _server2.PutAsset(asset);
                    _server3.PutAsset(asset);
                }

                _assetsThatExistOnAll.Add(uuidstr);
            }

            Console.WriteLine("Done: " + DateTime.Now);
        }
        static void ExecCmd(RemoteServer server, string cmd)
        {
            if (cmd == "iwim_heartbeat")
            {
                Socket sendSocket = new Socket(AddressFamily.InterNetwork,
                                               SocketType.Dgram, ProtocolType.Udp);

                sendSocket.Bind(new IPEndPoint(IPAddress.Any, 32702));

                Console.WriteLine("Sending heartbeat to server");
                IPAddress  sendTo       = Dns.GetHostAddresses(server.HostName)[0];
                IPEndPoint sendEndPoint = new IPEndPoint(sendTo, 32701);


                byte[] buffer = new byte[5];
                buffer[0] = 2;
                buffer[1] = 0xFF;
                buffer[2] = 0xFF;
                buffer[3] = 0xFF;
                buffer[4] = 0xFF;

                sendSocket.SendTo(buffer, 5, SocketFlags.None, sendEndPoint);
                sendSocket.Close();
            }

            if (cmd == "iwim_query_random" || cmd == "iwim_query_random_loop")
            {
                Socket sendSocket = new Socket(AddressFamily.InterNetwork,
                                               SocketType.Dgram, ProtocolType.Udp);

                int loops;

                if (cmd == "iwim_query_random")
                {
                    loops = 1;
                }
                else
                {
                    loops = 1000;
                }

                Console.WriteLine("Searching for {0} random assets starting {1}", loops, DateTime.Now);
                IPAddress  sendTo       = Dns.GetHostAddresses(server.HostName)[0];
                IPEndPoint sendEndPoint = new IPEndPoint(sendTo, 32701);
                byte[]     buffer       = new byte[33];
                buffer[0] = 0;

                for (int i = 0; i < loops; i++)
                {
                    Guid   guid  = Guid.NewGuid();
                    string fguid = guid.ToString().Replace("-", "");
                    Array.Copy(Util.UuidToAscii(fguid), 0, buffer, 1, 32);

                    sendSocket.SendTo(buffer, 33, SocketFlags.None, sendEndPoint);

                    EndPoint recvEndpoint = new IPEndPoint(IPAddress.Any, sendEndPoint.Port);
                    byte[]   response     = new byte[34];
                    sendSocket.ReceiveFrom(response, ref recvEndpoint);
                }

                Console.WriteLine("Done {0}", DateTime.Now);
            }

            if (cmd.Length > 10 && cmd.Substring(0, 10) == "iwim_query")
            {
                string uuid = cmd.Substring(11);

                Console.WriteLine("Asking for named asset " + uuid);
                server.GetAsset(uuid);
            }

            if (cmd == "getrandom")
            {
                Guid guid = Guid.NewGuid();
                Console.WriteLine("Asking for random asset " + guid.ToString());
                server.GetAsset(guid.ToString());
            }

            if (cmd.Length > 11 && cmd.Substring(0, 11) == "randomforce")
            {
                string begUUID = cmd.Substring(12);
                Guid   guid    = Guid.NewGuid();
                string newGuid = guid.ToString();
                newGuid = begUUID + newGuid.Substring(3);

                Console.WriteLine("Asking for semirandom asset " + newGuid);
                server.GetAsset(newGuid);
            }

            if (cmd.Length > 10 && cmd.Substring(0, 10) == "srandomput")
            {
                string begUUID = cmd.Substring(11);
                Guid   guid    = Guid.NewGuid();
                string newGuid = guid.ToString();
                newGuid = begUUID + newGuid.Substring(3);

                Console.WriteLine("Putting semirandom asset " + newGuid);

                Asset asset = new Asset(newGuid, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", TestUtil.RandomBytes());
                server.PutAsset(asset);
            }

            if (cmd.Length > 6 && cmd.Substring(0, 6) == "getone")
            {
                string uuid = cmd.Substring(7);

                Console.WriteLine("Asking for named asset " + uuid);
                Asset asset = server.GetAsset(uuid);

                using (System.IO.FileStream outstream = System.IO.File.OpenWrite("asset.txt"))
                {
                    outstream.Write(asset.Data, 0, asset.Data.Length);
                    outstream.Close();
                }
            }

            if (cmd == "put")
            {
                Console.WriteLine("Putting 1000 random assets to server");
                Console.WriteLine(DateTime.Now);
                for (int i = 0; i < 1000; i++)
                {
                    Asset asset = new Asset(OpenMetaverse.UUID.Random().ToString(), 1,
                                            false, false, 0, "Random Asset", "Radom Asset Desc", TestUtil.RandomBytes());
                    server.PutAsset(asset);
                }
                Console.WriteLine("Done: " + DateTime.Now);
            }

            if (cmd == "putone")
            {
                string uuid = OpenMetaverse.UUID.Random().ToString();
                _lastRandom = uuid;

                Console.WriteLine("Putting 1 random asset to server");
                Console.WriteLine(uuid);
                Console.WriteLine(DateTime.Now);

                Asset asset = new Asset(uuid, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", TestUtil.RandomBytes());
                server.PutAsset(asset);

                Console.WriteLine("Done: " + DateTime.Now);
            }

            if (cmd == "repeatput")
            {
                Console.WriteLine("Reputting random asset to server");
                Console.WriteLine(_lastRandom);
                Console.WriteLine(DateTime.Now);

                Asset asset = new Asset(_lastRandom, 1,
                                        false, false, 0, "Random Asset", "Radom Asset Desc", TestUtil.RandomBytes());
                server.PutAsset(asset);

                Console.WriteLine("Done: " + DateTime.Now);
            }

            if (cmd == "verify")
            {
                Dictionary <string, byte[]> uuids = new Dictionary <string, byte[]>();

                Console.WriteLine("Putting 1000 random 100K assets to server");
                Console.WriteLine(DateTime.Now);

                SHA1 sha = new SHA1CryptoServiceProvider();


                for (int i = 0; i < 1000; i++)
                {
                    string uuidstr       = OpenMetaverse.UUID.Random().ToString();
                    byte[] randomBytes   = TestUtil.RandomBytes();
                    byte[] challengeHash = sha.ComputeHash(randomBytes);
                    uuids.Add(uuidstr, challengeHash);

                    Asset asset = new Asset(uuidstr, 1,
                                            false, false, 0, "Random Asset", "Radom Asset Desc", randomBytes);
                    server.PutAsset(asset);
                }
                Console.WriteLine("Done: " + DateTime.Now);
                Console.WriteLine("Rereading written assets");
                Console.WriteLine(DateTime.Now);

                foreach (KeyValuePair <string, byte[]> kvp in uuids)
                {
                    Asset  a    = server.GetAsset(kvp.Key);
                    byte[] hash = sha.ComputeHash(a.Data);
                    if (!TestUtil.Test.test(hash, kvp.Value))
                    {
                        Console.WriteLine("Mismatched hash on " + kvp.Key);
                        Console.WriteLine("Got " + Util.HashToHex(hash) + " expected " + Util.HashToHex(kvp.Value));

                        ASCIIEncoding encoding = new ASCIIEncoding();

                        Console.WriteLine("Data " + encoding.GetString(a.Data));
                    }
                }

                Console.WriteLine("finished verifing assets");
                Console.WriteLine(DateTime.Now);
            }

            if (cmd == "purgelocals")
            {
                server.MaintPurgeLocals();
            }

            if (cmd == "thread")
            {
                Console.WriteLine("Starting heavy thread test");
                ThreadTest test = new ThreadTest(server, false);
                test.Start();
            }

            if (cmd == "thread_purgelocal")
            {
                Console.WriteLine("Starting heavy thread test");
                ThreadTest test = new ThreadTest(server, true);
                test.Start();
            }

            if (cmd == "threadmesh")
            {
                RemoteServer server2 = ConnectServerByConsole();

                Console.WriteLine("Starting heavy thread mesh test");
                CrossServerThreadTest test = new CrossServerThreadTest(server, server2);
                test.Start();
            }

            if (cmd == "threadmesh3")
            {
                RemoteServer server2 = ConnectServerByConsole();
                RemoteServer server3 = ConnectServerByConsole();

                Console.WriteLine("Starting heavy thread mesh test");
                CrossServerThreadTest3 test = new CrossServerThreadTest3(server, server2, server3);
                test.Start();
            }

            if (cmd == "testmulticonn")
            {
                RemoteServer[] server1 = ConnectServerByConsoleX(10);
                RemoteServer[] server2 = ConnectServerByConsoleX(10);
                RemoteServer[] server3 = ConnectServerByConsoleX(10);

                List <RemoteServer> allServers = new List <RemoteServer>();
                allServers.AddRange(server1);
                allServers.AddRange(server2);
                allServers.AddRange(server3);

                CrossServerTestMulticonn test = new CrossServerTestMulticonn(server1[0], server2[0], server3[0], allServers.ToArray(), true);
                test.Start();
            }

            if (cmd == "testmulticonnsingle")
            {
                RemoteServer[] server1 = ConnectServerByConsoleX(10);

                CrossServerTestMulticonn test = new CrossServerTestMulticonn(server1[0], server1[0], server1[0], server1, false);
                test.Start();
            }

            if (cmd == "meshbias3")
            {
                RemoteServer[] servers2 = ConnectServerByConsoleX(10);
                RemoteServer   server3  = ConnectServerByConsole();

                Console.WriteLine("Starting heavy thread biased mesh test");
                CrossServerBiasTest3 test = new CrossServerBiasTest3(server, servers2, server3);
                test.Start();
            }

            if (cmd == "connect")
            {
                //fast connect and disconnect to try and kill the whip service
                for (int i = 0; i < 1000; i++)
                {
                    server.Stop();
                    server.Start();
                }
            }

            if (cmd == "status")
            {
                Console.WriteLine(server.GetServerStatus());
            }

            if (cmd == "prefix")
            {
                Console.WriteLine(server.GetAssetIds("00000000000000000000000000000000"));
            }

            if (cmd == "compare")
            {
                RemoteServer server1 = ConnectServerByConsole();
                RemoteServer server2 = ConnectServerByConsole();
                RunCompare(server1, server2);
            }

            if (cmd == "import")
            {
                Console.Write("Connection String: ");
                string connString = Console.ReadLine();
                Console.WriteLine();
                Console.Write("Start at: ");
                int startAt = Convert.ToInt32(Console.ReadLine());
                Console.WriteLine();

                _import = new AssetImport(server, connString, startAt);
                _import.Start();
            }

            if (cmd == "msimport")
            {
                Console.Write("Connection String: ");
                string connString = Console.ReadLine();
                Console.WriteLine();
                Console.Write("Start at: ");
                Guid startAt = new Guid(Console.ReadLine());
                Console.WriteLine();

                _msimport = new MsSqlAssetImport(server, connString, startAt);
                _msimport.Start();
            }

            if (cmd == "stop import")
            {
                if (_import != null)
                {
                    _import.Stop();
                }
                _import = null;
                if (_msimport != null)
                {
                    _msimport.Stop();
                }
                _msimport = null;
            }
        }
Ejemplo n.º 10
0
        public void Run()
        {
            int i = 0;

            /*int count = 0;
             *
             * SqlCommand countCmd =
             *      new SqlCommand(
             *          "SELECT COUNT(*) as CNT " +
             *          "FROM assets " +
             *          "WHERE id >= @id ",
             *          _conn);
             *
             * countCmd.Parameters.AddWithValue("@id", _startAt);
             *
             * countCmd.CommandType = CommandType.Text;
             * count = Convert.ToInt32(countCmd.ExecuteScalar());
             * countCmd.Dispose();
             */

            while (true)
            {
                SqlCommand cmd =
                    new SqlCommand(
                        "SELECT TOP 100 id, name, description, assetType, local, temporary, data, create_time " +
                        "FROM assets " +
                        "WHERE id > @id " +
                        "ORDER BY id",
                        _conn);

                cmd.Parameters.AddWithValue("@id", _startAt);

                try
                {
                    using (SqlDataReader dbReader = cmd.ExecuteReader())
                    {
                        while (dbReader.Read() && !_stop)
                        {
                            bool local;
                            bool temp;
                            try
                            {
                                local = (bool)dbReader["local"];
                            }
                            catch (InvalidCastException)
                            {
                                local = false;
                            }

                            try
                            {
                                temp = (bool)dbReader["temporary"];
                            }
                            catch (InvalidCastException)
                            {
                                temp = false;
                            }

                            string uuid       = ((Guid)dbReader["id"]).ToString();
                            sbyte  assetType  = (sbyte)(byte)dbReader["assetType"];
                            int    createTime = (int)dbReader["create_time"];
                            string name       = (string)dbReader["name"];
                            string desc       = (string)dbReader["description"];
                            byte[] data       = (byte[])dbReader["data"];

                            Asset whipAsset =
                                new Asset(
                                    uuid,
                                    (byte)assetType,
                                    local,
                                    temp,
                                    createTime,
                                    name,
                                    desc,
                                    data);

                            try
                            {
                                _server.PutAsset(whipAsset);
                            }
                            catch (Exception e)
                            {
                                Log("Error putting asset: " + e);
                                Console.Read();
                            }

                            Log("Writing " + ((Guid)dbReader["id"]).ToString());
                            Log(++i + " this run");

                            _startAt = (Guid)dbReader["id"];
                        }
                        dbReader.Close();
                        cmd.Dispose();
                    }
                }
                catch (Exception e)
                {
                    Log(e.ToString());
                    Log("Last asset written: " + _startAt);
                }

                Log("Last asset written: " + _startAt);
            }
        }