aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs
diff options
context:
space:
mode:
authorSean Dague2008-01-09 18:41:20 +0000
committerSean Dague2008-01-09 18:41:20 +0000
commit411d06c94e0b8e696eb6b19019f46079927151f2 (patch)
treee26dbe236ccc791e92b808e4b6ae63b0657b4f68 /OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs
parent* Added helpers to convert from LSL PrimitiveParams values to PrimitiveBaseSh... (diff)
downloadopensim-SC-411d06c94e0b8e696eb6b19019f46079927151f2.zip
opensim-SC-411d06c94e0b8e696eb6b19019f46079927151f2.tar.gz
opensim-SC-411d06c94e0b8e696eb6b19019f46079927151f2.tar.bz2
opensim-SC-411d06c94e0b8e696eb6b19019f46079927151f2.tar.xz
moving Region store for SQLite into the OpenSim.Framework.Data.SQLite
namespace to mirror what is done with MySQL. This will require a configuration change for people using SQLite for prims which is given as an example in OpenSim.ini.example
Diffstat (limited to 'OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs')
-rw-r--r--OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs1760
1 files changed, 1760 insertions, 0 deletions
diff --git a/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs b/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs
new file mode 100644
index 0000000..00b0acb
--- /dev/null
+++ b/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs
@@ -0,0 +1,1760 @@
1/*
2* Copyright (c) Contributors, http://opensimulator.org/
3* See CONTRIBUTORS.TXT for a full list of copyright holders.
4*
5* Redistribution and use in source and binary forms, with or without
6* modification, are permitted provided that the following conditions are met:
7* * Redistributions of source code must retain the above copyright
8* notice, this list of conditions and the following disclaimer.
9* * Redistributions in binary form must reproduce the above copyright
10* notice, this list of conditions and the following disclaimer in the
11* documentation and/or other materials provided with the distribution.
12* * Neither the name of the OpenSim Project nor the
13* names of its contributors may be used to endorse or promote products
14* derived from this software without specific prior written permission.
15*
16* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS AS IS AND ANY
17* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*
27*/
28
29using System;
30using System.Collections.Generic;
31using System.Data;
32using System.IO;
33using libsecondlife;
34using Mono.Data.SqliteClient;
35using OpenSim.Framework;
36using OpenSim.Framework.Console;
37using OpenSim.Region.Environment.Interfaces;
38using OpenSim.Region.Environment.LandManagement;
39using OpenSim.Region.Environment.Scenes;
40
41namespace OpenSim.Framework.Data.SQLite
42{
43 public class SQLiteRegionData : IRegionDataStore
44 {
45 private const string primSelect = "select * from prims";
46 private const string shapeSelect = "select * from primshapes";
47 private const string itemsSelect = "select * from primitems";
48 private const string terrainSelect = "select * from terrain limit 1";
49 private const string landSelect = "select * from land";
50 private const string landAccessListSelect = "select * from landaccesslist";
51
52 private DataSet ds;
53 private SqliteDataAdapter primDa;
54 private SqliteDataAdapter shapeDa;
55 private SqliteDataAdapter itemsDa;
56 private SqliteDataAdapter terrainDa;
57 private SqliteDataAdapter landDa;
58 private SqliteDataAdapter landAccessListDa;
59
60 private SqliteConnection m_conn;
61
62 private String m_connectionString;
63
64 private bool persistPrimInventories;
65
66 /***********************************************************************
67 *
68 * Public Interface Functions
69 *
70 **********************************************************************/
71
72 // see IRegionDataStore
73 public void Initialise(string connectionString, bool persistPrimInventories)
74 {
75 m_connectionString = connectionString;
76 this.persistPrimInventories = persistPrimInventories;
77
78 ds = new DataSet();
79
80 MainLog.Instance.Verbose("DATASTORE", "Sqlite - connecting: " + connectionString);
81 SqliteConnection conn = new SqliteConnection(m_connectionString);
82
83 // Arg! Hate databases..
84 m_conn = conn;
85
86 SqliteCommand primSelectCmd = new SqliteCommand(primSelect, conn);
87 primDa = new SqliteDataAdapter(primSelectCmd);
88 // SqliteCommandBuilder primCb = new SqliteCommandBuilder(primDa);
89
90 SqliteCommand shapeSelectCmd = new SqliteCommand(shapeSelect, conn);
91 shapeDa = new SqliteDataAdapter(shapeSelectCmd);
92 // SqliteCommandBuilder shapeCb = new SqliteCommandBuilder(shapeDa);
93
94 SqliteCommand itemsSelectCmd = new SqliteCommand(itemsSelect, conn);
95 itemsDa = new SqliteDataAdapter(itemsSelectCmd);
96
97 SqliteCommand terrainSelectCmd = new SqliteCommand(terrainSelect, conn);
98 terrainDa = new SqliteDataAdapter(terrainSelectCmd);
99
100 SqliteCommand landSelectCmd = new SqliteCommand(landSelect, conn);
101 landDa = new SqliteDataAdapter(landSelectCmd);
102
103 SqliteCommand landAccessListSelectCmd = new SqliteCommand(landAccessListSelect, conn);
104 landAccessListDa = new SqliteDataAdapter(landAccessListSelectCmd);
105
106 // We fill the data set, now we've got copies in memory for the information
107 // TODO: see if the linkage actually holds.
108 // primDa.FillSchema(ds, SchemaType.Source, "PrimSchema");
109 TestTables(conn);
110
111 lock (ds)
112 {
113 ds.Tables.Add(createPrimTable());
114 setupPrimCommands(primDa, conn);
115 primDa.Fill(ds.Tables["prims"]);
116
117 ds.Tables.Add(createShapeTable());
118 setupShapeCommands(shapeDa, conn);
119
120 if (persistPrimInventories)
121 {
122 ds.Tables.Add(createItemsTable());
123 setupItemsCommands(itemsDa, conn);
124 itemsDa.Fill(ds.Tables["primitems"]);
125 }
126
127 ds.Tables.Add(createTerrainTable());
128 setupTerrainCommands(terrainDa, conn);
129
130 ds.Tables.Add(createLandTable());
131 setupLandCommands(landDa, conn);
132
133 ds.Tables.Add(createLandAccessListTable());
134 setupLandAccessCommands(landAccessListDa, conn);
135
136 // WORKAROUND: This is a work around for sqlite on
137 // windows, which gets really unhappy with blob columns
138 // that have no sample data in them. At some point we
139 // need to actually find a proper way to handle this.
140 try
141 {
142 shapeDa.Fill(ds.Tables["primshapes"]);
143 }
144 catch (Exception)
145 {
146 MainLog.Instance.Verbose("DATASTORE", "Caught fill error on primshapes table");
147 }
148
149 try
150 {
151 terrainDa.Fill(ds.Tables["terrain"]);
152 }
153 catch (Exception)
154 {
155 MainLog.Instance.Verbose("DATASTORE", "Caught fill error on terrain table");
156 }
157
158 try
159 {
160 landDa.Fill(ds.Tables["land"]);
161 }
162 catch (Exception)
163 {
164 MainLog.Instance.Verbose("DATASTORE", "Caught fill error on land table");
165 }
166
167 try
168 {
169 landAccessListDa.Fill(ds.Tables["landaccesslist"]);
170 }
171 catch (Exception)
172 {
173 MainLog.Instance.Verbose("DATASTORE", "Caught fill error on landaccesslist table");
174 }
175 return;
176 }
177 }
178
179 public void StoreObject(SceneObjectGroup obj, LLUUID regionUUID)
180 {
181 lock (ds)
182 {
183 foreach (SceneObjectPart prim in obj.Children.Values)
184 {
185 if ((prim.ObjectFlags & (uint) LLObject.ObjectFlags.Physics) == 0)
186 {
187 MainLog.Instance.Verbose("DATASTORE", "Adding obj: " + obj.UUID + " to region: " + regionUUID);
188 addPrim(prim, Util.ToRawUuidString(obj.UUID), Util.ToRawUuidString(regionUUID));
189 }
190 else if (prim.Stopped)
191 {
192 MainLog.Instance.Verbose("DATASTORE",
193 "Adding stopped obj: " + obj.UUID + " to region: " + regionUUID);
194 addPrim(prim, Util.ToRawUuidString(obj.UUID), Util.ToRawUuidString(regionUUID));
195 }
196 else
197 {
198 // MainLog.Instance.Verbose("DATASTORE", "Ignoring Physical obj: " + obj.UUID + " in region: " + regionUUID);
199 }
200 }
201 }
202
203 Commit();
204 // MainLog.Instance.Verbose("Dump of prims:", ds.GetXml());
205 }
206
207 public void RemoveObject(LLUUID obj, LLUUID regionUUID)
208 {
209 DataTable prims = ds.Tables["prims"];
210 DataTable shapes = ds.Tables["primshapes"];
211
212 string selectExp = "SceneGroupID = '" + Util.ToRawUuidString(obj) + "'";
213 lock (ds)
214 {
215 DataRow[] primRows = prims.Select(selectExp);
216 foreach (DataRow row in primRows)
217 {
218 LLUUID uuid = new LLUUID((string) row["UUID"]);
219 DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(uuid));
220 if (shapeRow != null)
221 {
222 shapeRow.Delete();
223 }
224 row.Delete();
225 }
226 }
227
228 Commit();
229 }
230
231 /// <summary>
232 /// Load persisted objects from region storage.
233 /// </summary>
234 /// <param name="regionUUID"></param>
235 /// <returns>List of loaded groups</returns>
236 public List<SceneObjectGroup> LoadObjects(LLUUID regionUUID)
237 {
238 Dictionary<LLUUID, SceneObjectGroup> createdObjects = new Dictionary<LLUUID, SceneObjectGroup>();
239
240 List<SceneObjectGroup> retvals = new List<SceneObjectGroup>();
241
242 DataTable prims = ds.Tables["prims"];
243 DataTable shapes = ds.Tables["primshapes"];
244
245 string byRegion = "RegionUUID = '" + Util.ToRawUuidString(regionUUID) + "'";
246 string orderByParent = "ParentID ASC";
247
248 lock (ds)
249 {
250 DataRow[] primsForRegion = prims.Select(byRegion, orderByParent);
251 MainLog.Instance.Verbose("DATASTORE",
252 "Loaded " + primsForRegion.Length + " prims for region: " + regionUUID);
253
254 foreach (DataRow primRow in primsForRegion)
255 {
256 try
257 {
258 SceneObjectPart prim = null;
259
260 string uuid = (string) primRow["UUID"];
261 string objID = (string) primRow["SceneGroupID"];
262 if (uuid == objID) //is new SceneObjectGroup ?
263 {
264 SceneObjectGroup group = new SceneObjectGroup();
265 prim = buildPrim(primRow);
266 DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(prim.UUID));
267 if (shapeRow != null)
268 {
269 prim.Shape = buildShape(shapeRow);
270 }
271 else
272 {
273 MainLog.Instance.Notice(
274 "No shape found for prim in storage, so setting default box shape");
275 prim.Shape = PrimitiveBaseShape.Default;
276 }
277 group.AddPart(prim);
278 group.RootPart = prim;
279
280 createdObjects.Add(Util.ToRawUuidString(group.UUID), group);
281 retvals.Add(group);
282 }
283 else
284 {
285 prim = buildPrim(primRow);
286 DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(prim.UUID));
287 if (shapeRow != null)
288 {
289 prim.Shape = buildShape(shapeRow);
290 }
291 else
292 {
293 MainLog.Instance.Notice(
294 "No shape found for prim in storage, so setting default box shape");
295 prim.Shape = PrimitiveBaseShape.Default;
296 }
297 createdObjects[new LLUUID(objID)].AddPart(prim);
298 }
299
300 if (persistPrimInventories)
301 {
302 LoadItems(prim);
303 }
304 }
305 catch (Exception e)
306 {
307 MainLog.Instance.Error("DATASTORE", "Failed create prim object, exception and data follows");
308 MainLog.Instance.Verbose("DATASTORE", e.ToString());
309 foreach (DataColumn col in prims.Columns)
310 {
311 MainLog.Instance.Verbose("DATASTORE", "Col: " + col.ColumnName + " => " + primRow[col]);
312 }
313 }
314 }
315 }
316 return retvals;
317 }
318
319 /// <summary>
320 /// Load in a prim's persisted inventory.
321 /// </summary>
322 /// <param name="prim"></param>
323 private void LoadItems(SceneObjectPart prim)
324 {
325 MainLog.Instance.Verbose("DATASTORE", "Loading inventory for {0}, {1}", prim.Name, prim.UUID);
326
327 DataTable dbItems = ds.Tables["primitems"];
328
329 String sql = String.Format("primID = '{0}'", prim.UUID.ToString());
330 DataRow[] dbItemRows = dbItems.Select(sql);
331
332 IList<TaskInventoryItem> inventory = new List<TaskInventoryItem>();
333
334 foreach (DataRow row in dbItemRows)
335 {
336 TaskInventoryItem item = buildItem(row);
337 inventory.Add(item);
338
339 MainLog.Instance.Verbose("DATASTORE", "Restored item {0}, {1}", item.name, item.item_id);
340 }
341
342 prim.AddInventoryItems(inventory);
343
344 // XXX A nasty little hack to recover the folder id for the prim (which is currently stored in
345 // every item). This data should really be stored in the prim table itself.
346 if (dbItemRows.Length > 0)
347 {
348 prim.FolderID = inventory[0].parent_id;
349 }
350 }
351
352 public void StoreTerrain(double[,] ter, LLUUID regionID)
353 {
354 lock (ds)
355 {
356 int revision = Util.UnixTimeSinceEpoch();
357
358 // the following is an work around for .NET. The perf
359 // issues associated with it aren't as bad as you think.
360 SqliteConnection conn = new SqliteConnection(m_connectionString);
361 conn.Open();
362 MainLog.Instance.Verbose("DATASTORE", "Storing terrain revision r" + revision.ToString());
363 String sql = "insert into terrain(RegionUUID, Revision, Heightfield)" +
364 " values(:RegionUUID, :Revision, :Heightfield)";
365
366 using (SqliteCommand cmd = new SqliteCommand(sql, conn))
367 {
368 cmd.Parameters.Add(new SqliteParameter(":RegionUUID", Util.ToRawUuidString(regionID)));
369 cmd.Parameters.Add(new SqliteParameter(":Revision", revision));
370 cmd.Parameters.Add(new SqliteParameter(":Heightfield", serializeTerrain(ter)));
371 cmd.ExecuteNonQuery();
372 }
373
374 // This is added to get rid of the infinitely growing
375 // terrain databases which negatively impact on SQLite
376 // over time. Before reenabling this feature there
377 // needs to be a limitter put on the number of
378 // revisions in the database, as this old
379 // implementation is a DOS attack waiting to happen.
380
381 using (
382 SqliteCommand cmd =
383 new SqliteCommand("delete from terrain where RegionUUID=:RegionUUID and Revision < :Revision",
384 conn))
385 {
386 cmd.Parameters.Add(new SqliteParameter(":RegionUUID", Util.ToRawUuidString(regionID)));
387 cmd.Parameters.Add(new SqliteParameter(":Revision", revision));
388 cmd.ExecuteNonQuery();
389 }
390 conn.Close();
391 }
392 }
393
394 public double[,] LoadTerrain(LLUUID regionID)
395 {
396 lock (ds)
397 {
398 double[,] terret = new double[256,256];
399 terret.Initialize();
400 // the following is an work around for .NET. The perf
401 // issues associated with it aren't as bad as you think.
402 SqliteConnection conn = new SqliteConnection(m_connectionString);
403 conn.Open();
404 String sql = "select RegionUUID, Revision, Heightfield from terrain" +
405 " where RegionUUID=:RegionUUID order by Revision desc";
406
407
408 using (SqliteCommand cmd = new SqliteCommand(sql, conn))
409 {
410 cmd.Parameters.Add(new SqliteParameter(":RegionUUID", Util.ToRawUuidString(regionID)));
411
412 using (IDataReader row = cmd.ExecuteReader())
413 {
414 int rev = 0;
415 if (row.Read())
416 {
417 // TODO: put this into a function
418 byte[] heightmap = (byte[]) row["Heightfield"];
419 for (int x = 0; x < 256; x++)
420 {
421 for (int y = 0; y < 256; y++)
422 {
423 terret[x, y] = BitConverter.ToDouble(heightmap, ((x*256) + y)*8);
424 }
425 }
426 rev = (int) row["Revision"];
427 }
428 else
429 {
430 MainLog.Instance.Verbose("DATASTORE", "No terrain found for region");
431 conn.Close();
432 return null;
433 }
434
435 MainLog.Instance.Verbose("DATASTORE", "Loaded terrain revision r" + rev.ToString());
436 }
437 }
438 conn.Close();
439 return terret;
440 }
441 }
442
443 public void RemoveLandObject(LLUUID globalID)
444 {
445 lock (ds)
446 {
447 SqliteConnection conn = new SqliteConnection(m_connectionString);
448 conn.Open();
449
450 using (SqliteCommand cmd = new SqliteCommand("delete from land where UUID=:UUID", conn))
451 {
452 cmd.Parameters.Add(new SqliteParameter(":UUID", Util.ToRawUuidString(globalID)));
453 cmd.ExecuteNonQuery();
454 }
455
456 using (SqliteCommand cmd = new SqliteCommand("delete from landaccesslist where LandUUID=:UUID", conn))
457 {
458 cmd.Parameters.Add(new SqliteParameter(":UUID", Util.ToRawUuidString(globalID)));
459 cmd.ExecuteNonQuery();
460 }
461 conn.Close();
462 }
463 }
464
465 public void StoreLandObject(Land parcel, LLUUID regionUUID)
466 {
467 lock (ds)
468 {
469 SqliteConnection conn = new SqliteConnection(m_connectionString);
470 conn.Open();
471 DataTable land = ds.Tables["land"];
472 DataTable landaccesslist = ds.Tables["landaccesslist"];
473
474 DataRow landRow = land.Rows.Find(Util.ToRawUuidString(parcel.landData.globalID));
475 if (landRow == null)
476 {
477 landRow = land.NewRow();
478 fillLandRow(landRow, parcel.landData, regionUUID);
479 land.Rows.Add(landRow);
480 }
481 else
482 {
483 fillLandRow(landRow, parcel.landData, regionUUID);
484 }
485
486 using (
487 SqliteCommand cmd = new SqliteCommand("delete from landaccesslist where LandUUID=:LandUUID", conn))
488 {
489 cmd.Parameters.Add(new SqliteParameter(":LandUUID", Util.ToRawUuidString(parcel.landData.globalID)));
490 cmd.ExecuteNonQuery();
491 }
492
493 foreach (ParcelManager.ParcelAccessEntry entry in parcel.landData.parcelAccessList)
494 {
495 DataRow newAccessRow = landaccesslist.NewRow();
496 fillLandAccessRow(newAccessRow, entry, parcel.landData.globalID);
497 landaccesslist.Rows.Add(newAccessRow);
498 }
499 conn.Close();
500 }
501
502 Commit();
503 }
504
505 public List<LandData> LoadLandObjects(LLUUID regionUUID)
506 {
507 List<LandData> landDataForRegion = new List<LandData>();
508 lock (ds)
509 {
510 DataTable land = ds.Tables["land"];
511 DataTable landaccesslist = ds.Tables["landaccesslist"];
512 string searchExp = "RegionUUID = '" + Util.ToRawUuidString(regionUUID) + "'";
513 DataRow[] rawDataForRegion = land.Select(searchExp);
514 foreach (DataRow rawDataLand in rawDataForRegion)
515 {
516 LandData newLand = buildLandData(rawDataLand);
517 string accessListSearchExp = "LandUUID = '" + Util.ToRawUuidString(newLand.globalID) + "'";
518 DataRow[] rawDataForLandAccessList = landaccesslist.Select(accessListSearchExp);
519 foreach (DataRow rawDataLandAccess in rawDataForLandAccessList)
520 {
521 newLand.parcelAccessList.Add(buildLandAccessData(rawDataLandAccess));
522 }
523
524 landDataForRegion.Add(newLand);
525 }
526 }
527 return landDataForRegion;
528 }
529
530 public void Commit()
531 {
532 lock (ds)
533 {
534 primDa.Update(ds, "prims");
535 shapeDa.Update(ds, "primshapes");
536
537 if (persistPrimInventories)
538 {
539 itemsDa.Update(ds, "primitems");
540 }
541
542 terrainDa.Update(ds, "terrain");
543 landDa.Update(ds, "land");
544 landAccessListDa.Update(ds, "landaccesslist");
545 ds.AcceptChanges();
546 }
547 }
548
549 public void Shutdown()
550 {
551 Commit();
552 }
553
554 /***********************************************************************
555 *
556 * Database Definition Functions
557 *
558 * This should be db agnostic as we define them in ADO.NET terms
559 *
560 **********************************************************************/
561
562 private void createCol(DataTable dt, string name, Type type)
563 {
564 DataColumn col = new DataColumn(name, type);
565 dt.Columns.Add(col);
566 }
567
568 private DataTable createTerrainTable()
569 {
570 DataTable terrain = new DataTable("terrain");
571
572 createCol(terrain, "RegionUUID", typeof (String));
573 createCol(terrain, "Revision", typeof (Int32));
574 createCol(terrain, "Heightfield", typeof (Byte[]));
575
576 return terrain;
577 }
578
579 private DataTable createPrimTable()
580 {
581 DataTable prims = new DataTable("prims");
582
583 createCol(prims, "UUID", typeof (String));
584 createCol(prims, "RegionUUID", typeof (String));
585 createCol(prims, "ParentID", typeof (Int32));
586 createCol(prims, "CreationDate", typeof (Int32));
587 createCol(prims, "Name", typeof (String));
588 createCol(prims, "SceneGroupID", typeof (String));
589 // various text fields
590 createCol(prims, "Text", typeof (String));
591 createCol(prims, "Description", typeof (String));
592 createCol(prims, "SitName", typeof (String));
593 createCol(prims, "TouchName", typeof (String));
594 // permissions
595 createCol(prims, "ObjectFlags", typeof (Int32));
596 createCol(prims, "CreatorID", typeof (String));
597 createCol(prims, "OwnerID", typeof (String));
598 createCol(prims, "GroupID", typeof (String));
599 createCol(prims, "LastOwnerID", typeof (String));
600 createCol(prims, "OwnerMask", typeof (Int32));
601 createCol(prims, "NextOwnerMask", typeof (Int32));
602 createCol(prims, "GroupMask", typeof (Int32));
603 createCol(prims, "EveryoneMask", typeof (Int32));
604 createCol(prims, "BaseMask", typeof (Int32));
605 // vectors
606 createCol(prims, "PositionX", typeof (Double));
607 createCol(prims, "PositionY", typeof (Double));
608 createCol(prims, "PositionZ", typeof (Double));
609 createCol(prims, "GroupPositionX", typeof (Double));
610 createCol(prims, "GroupPositionY", typeof (Double));
611 createCol(prims, "GroupPositionZ", typeof (Double));
612 createCol(prims, "VelocityX", typeof (Double));
613 createCol(prims, "VelocityY", typeof (Double));
614 createCol(prims, "VelocityZ", typeof (Double));
615 createCol(prims, "AngularVelocityX", typeof (Double));
616 createCol(prims, "AngularVelocityY", typeof (Double));
617 createCol(prims, "AngularVelocityZ", typeof (Double));
618 createCol(prims, "AccelerationX", typeof (Double));
619 createCol(prims, "AccelerationY", typeof (Double));
620 createCol(prims, "AccelerationZ", typeof (Double));
621 // quaternions
622 createCol(prims, "RotationX", typeof (Double));
623 createCol(prims, "RotationY", typeof (Double));
624 createCol(prims, "RotationZ", typeof (Double));
625 createCol(prims, "RotationW", typeof (Double));
626
627 // sit target
628 createCol(prims, "SitTargetOffsetX", typeof (Double));
629 createCol(prims, "SitTargetOffsetY", typeof (Double));
630 createCol(prims, "SitTargetOffsetZ", typeof (Double));
631
632 createCol(prims, "SitTargetOrientW", typeof (Double));
633 createCol(prims, "SitTargetOrientX", typeof (Double));
634 createCol(prims, "SitTargetOrientY", typeof (Double));
635 createCol(prims, "SitTargetOrientZ", typeof (Double));
636
637 // Add in contraints
638 prims.PrimaryKey = new DataColumn[] {prims.Columns["UUID"]};
639
640 return prims;
641 }
642
643 private DataTable createShapeTable()
644 {
645 DataTable shapes = new DataTable("primshapes");
646 createCol(shapes, "UUID", typeof (String));
647 // shape is an enum
648 createCol(shapes, "Shape", typeof (Int32));
649 // vectors
650 createCol(shapes, "ScaleX", typeof (Double));
651 createCol(shapes, "ScaleY", typeof (Double));
652 createCol(shapes, "ScaleZ", typeof (Double));
653 // paths
654 createCol(shapes, "PCode", typeof (Int32));
655 createCol(shapes, "PathBegin", typeof (Int32));
656 createCol(shapes, "PathEnd", typeof (Int32));
657 createCol(shapes, "PathScaleX", typeof (Int32));
658 createCol(shapes, "PathScaleY", typeof (Int32));
659 createCol(shapes, "PathShearX", typeof (Int32));
660 createCol(shapes, "PathShearY", typeof (Int32));
661 createCol(shapes, "PathSkew", typeof (Int32));
662 createCol(shapes, "PathCurve", typeof (Int32));
663 createCol(shapes, "PathRadiusOffset", typeof (Int32));
664 createCol(shapes, "PathRevolutions", typeof (Int32));
665 createCol(shapes, "PathTaperX", typeof (Int32));
666 createCol(shapes, "PathTaperY", typeof (Int32));
667 createCol(shapes, "PathTwist", typeof (Int32));
668 createCol(shapes, "PathTwistBegin", typeof (Int32));
669 // profile
670 createCol(shapes, "ProfileBegin", typeof (Int32));
671 createCol(shapes, "ProfileEnd", typeof (Int32));
672 createCol(shapes, "ProfileCurve", typeof (Int32));
673 createCol(shapes, "ProfileHollow", typeof (Int32));
674 // text TODO: this isn't right, but I'm not sure the right
675 // way to specify this as a blob atm
676 createCol(shapes, "Texture", typeof (Byte[]));
677 createCol(shapes, "ExtraParams", typeof (Byte[]));
678
679 shapes.PrimaryKey = new DataColumn[] {shapes.Columns["UUID"]};
680
681 return shapes;
682 }
683
684 private DataTable createItemsTable()
685 {
686 DataTable items = new DataTable("primitems");
687
688 createCol(items, "itemID", typeof (String));
689 createCol(items, "primID", typeof (String));
690 createCol(items, "assetID", typeof (String));
691 createCol(items, "parentFolderID", typeof (String));
692
693 createCol(items, "invType", typeof (String));
694 createCol(items, "assetType", typeof (String));
695
696 createCol(items, "name", typeof (String));
697 createCol(items, "description", typeof (String));
698
699 createCol(items, "creationDate", typeof (Int64));
700 createCol(items, "creatorID", typeof (String));
701 createCol(items, "ownerID", typeof (String));
702 createCol(items, "lastOwnerID", typeof (String));
703 createCol(items, "groupID", typeof (String));
704
705 createCol(items, "nextPermissions", typeof (UInt32));
706 createCol(items, "currentPermissions", typeof (UInt32));
707 createCol(items, "basePermissions", typeof (UInt32));
708 createCol(items, "everyonePermissions", typeof (UInt32));
709 createCol(items, "groupPermissions", typeof (UInt32));
710
711 items.PrimaryKey = new DataColumn[] {items.Columns["itemID"]};
712
713 return items;
714 }
715
716 private DataTable createLandTable()
717 {
718 DataTable land = new DataTable("land");
719 createCol(land, "UUID", typeof (String));
720 createCol(land, "RegionUUID", typeof (String));
721 createCol(land, "LocalLandID", typeof (UInt32));
722
723 // Bitmap is a byte[512]
724 createCol(land, "Bitmap", typeof (Byte[]));
725
726 createCol(land, "Name", typeof (String));
727 createCol(land, "Desc", typeof (String));
728 createCol(land, "OwnerUUID", typeof (String));
729 createCol(land, "IsGroupOwned", typeof (Boolean));
730 createCol(land, "Area", typeof (Int32));
731 createCol(land, "AuctionID", typeof (Int32)); //Unemplemented
732 createCol(land, "Category", typeof (Int32)); //Enum libsecondlife.Parcel.ParcelCategory
733 createCol(land, "ClaimDate", typeof (Int32));
734 createCol(land, "ClaimPrice", typeof (Int32));
735 createCol(land, "GroupUUID", typeof (string));
736 createCol(land, "SalePrice", typeof (Int32));
737 createCol(land, "LandStatus", typeof (Int32)); //Enum. libsecondlife.Parcel.ParcelStatus
738 createCol(land, "LandFlags", typeof (UInt32));
739 createCol(land, "LandingType", typeof (Byte));
740 createCol(land, "MediaAutoScale", typeof (Byte));
741 createCol(land, "MediaTextureUUID", typeof (String));
742 createCol(land, "MediaURL", typeof (String));
743 createCol(land, "MusicURL", typeof (String));
744 createCol(land, "PassHours", typeof (Double));
745 createCol(land, "PassPrice", typeof (UInt32));
746 createCol(land, "SnapshotUUID", typeof (String));
747 createCol(land, "UserLocationX", typeof (Double));
748 createCol(land, "UserLocationY", typeof (Double));
749 createCol(land, "UserLocationZ", typeof (Double));
750 createCol(land, "UserLookAtX", typeof (Double));
751 createCol(land, "UserLookAtY", typeof (Double));
752 createCol(land, "UserLookAtZ", typeof (Double));
753
754 land.PrimaryKey = new DataColumn[] {land.Columns["UUID"]};
755
756 return land;
757 }
758
759 private DataTable createLandAccessListTable()
760 {
761 DataTable landaccess = new DataTable("landaccesslist");
762 createCol(landaccess, "LandUUID", typeof (String));
763 createCol(landaccess, "AccessUUID", typeof (String));
764 createCol(landaccess, "Flags", typeof (UInt32));
765
766 return landaccess;
767 }
768
769 /***********************************************************************
770 *
771 * Convert between ADO.NET <=> OpenSim Objects
772 *
773 * These should be database independant
774 *
775 **********************************************************************/
776
777 private SceneObjectPart buildPrim(DataRow row)
778 {
779 // TODO: this doesn't work yet because something more
780 // interesting has to be done to actually get these values
781 // back out. Not enough time to figure it out yet.
782 SceneObjectPart prim = new SceneObjectPart();
783 prim.UUID = new LLUUID((String) row["UUID"]);
784 // explicit conversion of integers is required, which sort
785 // of sucks. No idea if there is a shortcut here or not.
786 prim.ParentID = Convert.ToUInt32(row["ParentID"]);
787 prim.CreationDate = Convert.ToInt32(row["CreationDate"]);
788 prim.Name = (String) row["Name"];
789 // various text fields
790 prim.Text = (String) row["Text"];
791 prim.Description = (String) row["Description"];
792 prim.SitName = (String) row["SitName"];
793 prim.TouchName = (String) row["TouchName"];
794 // permissions
795 prim.ObjectFlags = Convert.ToUInt32(row["ObjectFlags"]);
796 prim.CreatorID = new LLUUID((String) row["CreatorID"]);
797 prim.OwnerID = new LLUUID((String) row["OwnerID"]);
798 prim.GroupID = new LLUUID((String) row["GroupID"]);
799 prim.LastOwnerID = new LLUUID((String) row["LastOwnerID"]);
800 prim.OwnerMask = Convert.ToUInt32(row["OwnerMask"]);
801 prim.NextOwnerMask = Convert.ToUInt32(row["NextOwnerMask"]);
802 prim.GroupMask = Convert.ToUInt32(row["GroupMask"]);
803 prim.EveryoneMask = Convert.ToUInt32(row["EveryoneMask"]);
804 prim.BaseMask = Convert.ToUInt32(row["BaseMask"]);
805 // vectors
806 prim.OffsetPosition = new LLVector3(
807 Convert.ToSingle(row["PositionX"]),
808 Convert.ToSingle(row["PositionY"]),
809 Convert.ToSingle(row["PositionZ"])
810 );
811 prim.GroupPosition = new LLVector3(
812 Convert.ToSingle(row["GroupPositionX"]),
813 Convert.ToSingle(row["GroupPositionY"]),
814 Convert.ToSingle(row["GroupPositionZ"])
815 );
816 prim.Velocity = new LLVector3(
817 Convert.ToSingle(row["VelocityX"]),
818 Convert.ToSingle(row["VelocityY"]),
819 Convert.ToSingle(row["VelocityZ"])
820 );
821 prim.AngularVelocity = new LLVector3(
822 Convert.ToSingle(row["AngularVelocityX"]),
823 Convert.ToSingle(row["AngularVelocityY"]),
824 Convert.ToSingle(row["AngularVelocityZ"])
825 );
826 prim.Acceleration = new LLVector3(
827 Convert.ToSingle(row["AccelerationX"]),
828 Convert.ToSingle(row["AccelerationY"]),
829 Convert.ToSingle(row["AccelerationZ"])
830 );
831 // quaternions
832 prim.RotationOffset = new LLQuaternion(
833 Convert.ToSingle(row["RotationX"]),
834 Convert.ToSingle(row["RotationY"]),
835 Convert.ToSingle(row["RotationZ"]),
836 Convert.ToSingle(row["RotationW"])
837 );
838
839 try
840 {
841 prim.SetSitTargetLL(new LLVector3(
842 Convert.ToSingle(row["SitTargetOffsetX"]),
843 Convert.ToSingle(row["SitTargetOffsetY"]),
844 Convert.ToSingle(row["SitTargetOffsetZ"])), new LLQuaternion(
845 Convert.ToSingle(
846 row["SitTargetOrientX"]),
847 Convert.ToSingle(
848 row["SitTargetOrientY"]),
849 Convert.ToSingle(
850 row["SitTargetOrientZ"]),
851 Convert.ToSingle(
852 row["SitTargetOrientW"])));
853 }
854 catch (InvalidCastException)
855 {
856 // Database table was created before we got here and now has null values :P
857 m_conn.Open();
858 SqliteCommand cmd =
859 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOffsetX float NOT NULL default 0;", m_conn);
860 cmd.ExecuteNonQuery();
861 cmd =
862 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOffsetY float NOT NULL default 0;", m_conn);
863 cmd.ExecuteNonQuery();
864 cmd =
865 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOffsetZ float NOT NULL default 0;", m_conn);
866 cmd.ExecuteNonQuery();
867 cmd =
868 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOrientW float NOT NULL default 0;", m_conn);
869 cmd.ExecuteNonQuery();
870 cmd =
871 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOrientX float NOT NULL default 0;", m_conn);
872 cmd.ExecuteNonQuery();
873 cmd =
874 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOrientY float NOT NULL default 0;", m_conn);
875 cmd.ExecuteNonQuery();
876 cmd =
877 new SqliteCommand("ALTER TABLE prims ADD COLUMN SitTargetOrientZ float NOT NULL default 0;", m_conn);
878 cmd.ExecuteNonQuery();
879 }
880
881 return prim;
882 }
883
884 /// <summary>
885 /// Build a prim inventory item from the persisted data.
886 /// </summary>
887 /// <param name="row"></param>
888 /// <returns></returns>
889 private TaskInventoryItem buildItem(DataRow row)
890 {
891 TaskInventoryItem taskItem = new TaskInventoryItem();
892
893 taskItem.item_id = new LLUUID((String)row["itemID"]);
894 taskItem.ParentPartID = new LLUUID((String)row["primID"]);
895 taskItem.asset_id = new LLUUID((String)row["assetID"]);
896 taskItem.parent_id = new LLUUID((String)row["parentFolderID"]);
897
898 taskItem.inv_type = (String)row["invType"];
899 taskItem.type = (String)row["assetType"];
900
901 taskItem.name = (String)row["name"];
902 taskItem.desc = (String)row["description"];
903 taskItem.creation_date = Convert.ToUInt32(row["creationDate"]);
904 taskItem.creator_id = new LLUUID((String)row["creatorID"]);
905 taskItem.owner_id = new LLUUID((String)row["ownerID"]);
906 taskItem.last_owner_id = new LLUUID((String)row["lastOwnerID"]);
907 taskItem.group_id = new LLUUID((String)row["groupID"]);
908
909 taskItem.next_owner_mask = Convert.ToUInt32(row["nextPermissions"]);
910 taskItem.owner_mask = Convert.ToUInt32(row["currentPermissions"]);
911 taskItem.base_mask = Convert.ToUInt32(row["basePermissions"]);
912 taskItem.everyone_mask = Convert.ToUInt32(row["everyonePermissions"]);
913 taskItem.group_mask = Convert.ToUInt32(row["groupPermissions"]);
914
915 return taskItem;
916 }
917
918 private LandData buildLandData(DataRow row)
919 {
920 LandData newData = new LandData();
921
922 newData.globalID = new LLUUID((String) row["UUID"]);
923 newData.localID = Convert.ToInt32(row["LocalLandID"]);
924
925 // Bitmap is a byte[512]
926 newData.landBitmapByteArray = (Byte[]) row["Bitmap"];
927
928 newData.landName = (String) row["Name"];
929 newData.landDesc = (String) row["Desc"];
930 newData.ownerID = (String) row["OwnerUUID"];
931 newData.isGroupOwned = (Boolean) row["IsGroupOwned"];
932 newData.area = Convert.ToInt32(row["Area"]);
933 newData.auctionID = Convert.ToUInt32(row["AuctionID"]); //Unemplemented
934 newData.category = (Parcel.ParcelCategory) Convert.ToInt32(row["Category"]);
935 //Enum libsecondlife.Parcel.ParcelCategory
936 newData.claimDate = Convert.ToInt32(row["ClaimDate"]);
937 newData.claimPrice = Convert.ToInt32(row["ClaimPrice"]);
938 newData.groupID = new LLUUID((String) row["GroupUUID"]);
939 newData.salePrice = Convert.ToInt32(row["SalePrice"]);
940 newData.landStatus = (Parcel.ParcelStatus) Convert.ToInt32(row["LandStatus"]);
941 //Enum. libsecondlife.Parcel.ParcelStatus
942 newData.landFlags = Convert.ToUInt32(row["LandFlags"]);
943 newData.landingType = (Byte) row["LandingType"];
944 newData.mediaAutoScale = (Byte) row["MediaAutoScale"];
945 newData.mediaID = new LLUUID((String) row["MediaTextureUUID"]);
946 newData.mediaURL = (String) row["MediaURL"];
947 newData.musicURL = (String) row["MusicURL"];
948 newData.passHours = Convert.ToSingle(row["PassHours"]);
949 newData.passPrice = Convert.ToInt32(row["PassPrice"]);
950 newData.snapshotID = (String) row["SnapshotUUID"];
951
952 newData.userLocation =
953 new LLVector3(Convert.ToSingle(row["UserLocationX"]), Convert.ToSingle(row["UserLocationY"]),
954 Convert.ToSingle(row["UserLocationZ"]));
955 newData.userLookAt =
956 new LLVector3(Convert.ToSingle(row["UserLookAtX"]), Convert.ToSingle(row["UserLookAtY"]),
957 Convert.ToSingle(row["UserLookAtZ"]));
958 newData.parcelAccessList = new List<ParcelManager.ParcelAccessEntry>();
959
960 return newData;
961 }
962
963 private ParcelManager.ParcelAccessEntry buildLandAccessData(DataRow row)
964 {
965 ParcelManager.ParcelAccessEntry entry = new ParcelManager.ParcelAccessEntry();
966 entry.AgentID = new LLUUID((string) row["AccessUUID"]);
967 entry.Flags = (ParcelManager.AccessList) row["Flags"];
968 entry.Time = new DateTime();
969 return entry;
970 }
971
972 private Array serializeTerrain(double[,] val)
973 {
974 MemoryStream str = new MemoryStream(65536*sizeof (double));
975 BinaryWriter bw = new BinaryWriter(str);
976
977 // TODO: COMPATIBILITY - Add byte-order conversions
978 for (int x = 0; x < 256; x++)
979 for (int y = 0; y < 256; y++)
980 bw.Write(val[x, y]);
981
982 return str.ToArray();
983 }
984
985// private void fillTerrainRow(DataRow row, LLUUID regionUUID, int rev, double[,] val)
986// {
987// row["RegionUUID"] = regionUUID;
988// row["Revision"] = rev;
989
990// MemoryStream str = new MemoryStream(65536*sizeof (double));
991// BinaryWriter bw = new BinaryWriter(str);
992
993// // TODO: COMPATIBILITY - Add byte-order conversions
994// for (int x = 0; x < 256; x++)
995// for (int y = 0; y < 256; y++)
996// bw.Write(val[x, y]);
997
998// row["Heightfield"] = str.ToArray();
999// }
1000
1001 private void fillPrimRow(DataRow row, SceneObjectPart prim, LLUUID sceneGroupID, LLUUID regionUUID)
1002 {
1003 row["UUID"] = Util.ToRawUuidString(prim.UUID);
1004 row["RegionUUID"] = Util.ToRawUuidString(regionUUID);
1005 row["ParentID"] = prim.ParentID;
1006 row["CreationDate"] = prim.CreationDate;
1007 row["Name"] = prim.Name;
1008 row["SceneGroupID"] = Util.ToRawUuidString(sceneGroupID);
1009 // the UUID of the root part for this SceneObjectGroup
1010 // various text fields
1011 row["Text"] = prim.Text;
1012 row["Description"] = prim.Description;
1013 row["SitName"] = prim.SitName;
1014 row["TouchName"] = prim.TouchName;
1015 // permissions
1016 row["ObjectFlags"] = prim.ObjectFlags;
1017 row["CreatorID"] = Util.ToRawUuidString(prim.CreatorID);
1018 row["OwnerID"] = Util.ToRawUuidString(prim.OwnerID);
1019 row["GroupID"] = Util.ToRawUuidString(prim.GroupID);
1020 row["LastOwnerID"] = Util.ToRawUuidString(prim.LastOwnerID);
1021 row["OwnerMask"] = prim.OwnerMask;
1022 row["NextOwnerMask"] = prim.NextOwnerMask;
1023 row["GroupMask"] = prim.GroupMask;
1024 row["EveryoneMask"] = prim.EveryoneMask;
1025 row["BaseMask"] = prim.BaseMask;
1026 // vectors
1027 row["PositionX"] = prim.OffsetPosition.X;
1028 row["PositionY"] = prim.OffsetPosition.Y;
1029 row["PositionZ"] = prim.OffsetPosition.Z;
1030 row["GroupPositionX"] = prim.GroupPosition.X;
1031 row["GroupPositionY"] = prim.GroupPosition.Y;
1032 row["GroupPositionZ"] = prim.GroupPosition.Z;
1033 row["VelocityX"] = prim.Velocity.X;
1034 row["VelocityY"] = prim.Velocity.Y;
1035 row["VelocityZ"] = prim.Velocity.Z;
1036 row["AngularVelocityX"] = prim.AngularVelocity.X;
1037 row["AngularVelocityY"] = prim.AngularVelocity.Y;
1038 row["AngularVelocityZ"] = prim.AngularVelocity.Z;
1039 row["AccelerationX"] = prim.Acceleration.X;
1040 row["AccelerationY"] = prim.Acceleration.Y;
1041 row["AccelerationZ"] = prim.Acceleration.Z;
1042 // quaternions
1043 row["RotationX"] = prim.RotationOffset.X;
1044 row["RotationY"] = prim.RotationOffset.Y;
1045 row["RotationZ"] = prim.RotationOffset.Z;
1046 row["RotationW"] = prim.RotationOffset.W;
1047
1048 // Sit target
1049 LLVector3 sitTargetPos = prim.GetSitTargetPositionLL();
1050 row["SitTargetOffsetX"] = sitTargetPos.X;
1051 row["SitTargetOffsetY"] = sitTargetPos.Y;
1052 row["SitTargetOffsetZ"] = sitTargetPos.Z;
1053
1054 LLQuaternion sitTargetOrient = prim.GetSitTargetOrientationLL();
1055 row["SitTargetOrientW"] = sitTargetOrient.W;
1056 row["SitTargetOrientX"] = sitTargetOrient.X;
1057 row["SitTargetOrientY"] = sitTargetOrient.Y;
1058 row["SitTargetOrientZ"] = sitTargetOrient.Z;
1059 }
1060
1061 private void fillItemRow(DataRow row, TaskInventoryItem taskItem)
1062 {
1063 row["itemID"] = taskItem.item_id;
1064 row["primID"] = taskItem.ParentPartID;
1065 row["assetID"] = taskItem.asset_id;
1066 row["parentFolderID"] = taskItem.parent_id;
1067
1068 row["invType"] = taskItem.inv_type;
1069 row["assetType"] = taskItem.type;
1070
1071 row["name"] = taskItem.name;
1072 row["description"] = taskItem.desc;
1073 row["creationDate"] = taskItem.creation_date;
1074 row["creatorID"] = taskItem.creator_id;
1075 row["ownerID"] = taskItem.owner_id;
1076 row["lastOwnerID"] = taskItem.last_owner_id;
1077 row["groupID"] = taskItem.group_id;
1078 row["nextPermissions"] = taskItem.next_owner_mask;
1079 row["currentPermissions"] = taskItem.owner_mask;
1080 row["basePermissions"] = taskItem.base_mask;
1081 row["everyonePermissions"] = taskItem.everyone_mask;
1082 row["groupPermissions"] = taskItem.group_mask;
1083 }
1084
1085 private void fillLandRow(DataRow row, LandData land, LLUUID regionUUID)
1086 {
1087 row["UUID"] = Util.ToRawUuidString(land.globalID);
1088 row["RegionUUID"] = Util.ToRawUuidString(regionUUID);
1089 row["LocalLandID"] = land.localID;
1090
1091 // Bitmap is a byte[512]
1092 row["Bitmap"] = land.landBitmapByteArray;
1093
1094 row["Name"] = land.landName;
1095 row["Desc"] = land.landDesc;
1096 row["OwnerUUID"] = Util.ToRawUuidString(land.ownerID);
1097 row["IsGroupOwned"] = land.isGroupOwned;
1098 row["Area"] = land.area;
1099 row["AuctionID"] = land.auctionID; //Unemplemented
1100 row["Category"] = land.category; //Enum libsecondlife.Parcel.ParcelCategory
1101 row["ClaimDate"] = land.claimDate;
1102 row["ClaimPrice"] = land.claimPrice;
1103 row["GroupUUID"] = Util.ToRawUuidString(land.groupID);
1104 row["SalePrice"] = land.salePrice;
1105 row["LandStatus"] = land.landStatus; //Enum. libsecondlife.Parcel.ParcelStatus
1106 row["LandFlags"] = land.landFlags;
1107 row["LandingType"] = land.landingType;
1108 row["MediaAutoScale"] = land.mediaAutoScale;
1109 row["MediaTextureUUID"] = Util.ToRawUuidString(land.mediaID);
1110 row["MediaURL"] = land.mediaURL;
1111 row["MusicURL"] = land.musicURL;
1112 row["PassHours"] = land.passHours;
1113 row["PassPrice"] = land.passPrice;
1114 row["SnapshotUUID"] = Util.ToRawUuidString(land.snapshotID);
1115 row["UserLocationX"] = land.userLocation.X;
1116 row["UserLocationY"] = land.userLocation.Y;
1117 row["UserLocationZ"] = land.userLocation.Z;
1118 row["UserLookAtX"] = land.userLookAt.X;
1119 row["UserLookAtY"] = land.userLookAt.Y;
1120 row["UserLookAtZ"] = land.userLookAt.Z;
1121 }
1122
1123 private void fillLandAccessRow(DataRow row, ParcelManager.ParcelAccessEntry entry, LLUUID parcelID)
1124 {
1125 row["LandUUID"] = Util.ToRawUuidString(parcelID);
1126 row["AccessUUID"] = Util.ToRawUuidString(entry.AgentID);
1127 row["Flags"] = entry.Flags;
1128 }
1129
1130 private PrimitiveBaseShape buildShape(DataRow row)
1131 {
1132 PrimitiveBaseShape s = new PrimitiveBaseShape();
1133 s.Scale = new LLVector3(
1134 Convert.ToSingle(row["ScaleX"]),
1135 Convert.ToSingle(row["ScaleY"]),
1136 Convert.ToSingle(row["ScaleZ"])
1137 );
1138 // paths
1139 s.PCode = Convert.ToByte(row["PCode"]);
1140 s.PathBegin = Convert.ToUInt16(row["PathBegin"]);
1141 s.PathEnd = Convert.ToUInt16(row["PathEnd"]);
1142 s.PathScaleX = Convert.ToByte(row["PathScaleX"]);
1143 s.PathScaleY = Convert.ToByte(row["PathScaleY"]);
1144 s.PathShearX = Convert.ToByte(row["PathShearX"]);
1145 s.PathShearY = Convert.ToByte(row["PathShearY"]);
1146 s.PathSkew = Convert.ToSByte(row["PathSkew"]);
1147 s.PathCurve = Convert.ToByte(row["PathCurve"]);
1148 s.PathRadiusOffset = Convert.ToSByte(row["PathRadiusOffset"]);
1149 s.PathRevolutions = Convert.ToByte(row["PathRevolutions"]);
1150 s.PathTaperX = Convert.ToSByte(row["PathTaperX"]);
1151 s.PathTaperY = Convert.ToSByte(row["PathTaperY"]);
1152 s.PathTwist = Convert.ToSByte(row["PathTwist"]);
1153 s.PathTwistBegin = Convert.ToSByte(row["PathTwistBegin"]);
1154 // profile
1155 s.ProfileBegin = Convert.ToUInt16(row["ProfileBegin"]);
1156 s.ProfileEnd = Convert.ToUInt16(row["ProfileEnd"]);
1157 s.ProfileCurve = Convert.ToByte(row["ProfileCurve"]);
1158 s.ProfileHollow = Convert.ToUInt16(row["ProfileHollow"]);
1159 // text TODO: this isn't right] = but I'm not sure the right
1160 // way to specify this as a blob atm
1161
1162 byte[] textureEntry = (byte[]) row["Texture"];
1163 s.TextureEntry = textureEntry;
1164
1165
1166 s.ExtraParams = (byte[]) row["ExtraParams"];
1167 // System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
1168 // string texture = encoding.GetString((Byte[])row["Texture"]);
1169 // if (!texture.StartsWith("<"))
1170 // {
1171 // //here so that we can still work with old format database files (ie from before I added xml serialization)
1172 // LLObject.TextureEntry textureEntry = null;
1173 // textureEntry = new LLObject.TextureEntry(new LLUUID(texture));
1174 // s.TextureEntry = textureEntry.ToBytes();
1175 // }
1176 // else
1177 // {
1178 // TextureBlock textureEntry = TextureBlock.FromXmlString(texture);
1179 // s.TextureEntry = textureEntry.TextureData;
1180 // s.ExtraParams = textureEntry.ExtraParams;
1181 // }
1182
1183 return s;
1184 }
1185
1186 private void fillShapeRow(DataRow row, SceneObjectPart prim)
1187 {
1188 PrimitiveBaseShape s = prim.Shape;
1189 row["UUID"] = Util.ToRawUuidString(prim.UUID);
1190 // shape is an enum
1191 row["Shape"] = 0;
1192 // vectors
1193 row["ScaleX"] = s.Scale.X;
1194 row["ScaleY"] = s.Scale.Y;
1195 row["ScaleZ"] = s.Scale.Z;
1196 // paths
1197 row["PCode"] = s.PCode;
1198 row["PathBegin"] = s.PathBegin;
1199 row["PathEnd"] = s.PathEnd;
1200 row["PathScaleX"] = s.PathScaleX;
1201 row["PathScaleY"] = s.PathScaleY;
1202 row["PathShearX"] = s.PathShearX;
1203 row["PathShearY"] = s.PathShearY;
1204 row["PathSkew"] = s.PathSkew;
1205 row["PathCurve"] = s.PathCurve;
1206 row["PathRadiusOffset"] = s.PathRadiusOffset;
1207 row["PathRevolutions"] = s.PathRevolutions;
1208 row["PathTaperX"] = s.PathTaperX;
1209 row["PathTaperY"] = s.PathTaperY;
1210 row["PathTwist"] = s.PathTwist;
1211 row["PathTwistBegin"] = s.PathTwistBegin;
1212 // profile
1213 row["ProfileBegin"] = s.ProfileBegin;
1214 row["ProfileEnd"] = s.ProfileEnd;
1215 row["ProfileCurve"] = s.ProfileCurve;
1216 row["ProfileHollow"] = s.ProfileHollow;
1217
1218 row["Texture"] = s.TextureEntry;
1219 row["ExtraParams"] = s.ExtraParams;
1220 }
1221
1222 private void addPrim(SceneObjectPart prim, LLUUID sceneGroupID, LLUUID regionUUID)
1223 {
1224 DataTable prims = ds.Tables["prims"];
1225 DataTable shapes = ds.Tables["primshapes"];
1226
1227 DataRow primRow = prims.Rows.Find(Util.ToRawUuidString(prim.UUID));
1228 if (primRow == null)
1229 {
1230 primRow = prims.NewRow();
1231 fillPrimRow(primRow, prim, sceneGroupID, regionUUID);
1232 prims.Rows.Add(primRow);
1233 }
1234 else
1235 {
1236 fillPrimRow(primRow, prim, sceneGroupID, regionUUID);
1237 }
1238
1239 DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(prim.UUID));
1240 if (shapeRow == null)
1241 {
1242 shapeRow = shapes.NewRow();
1243 fillShapeRow(shapeRow, prim);
1244 shapes.Rows.Add(shapeRow);
1245 }
1246 else
1247 {
1248 fillShapeRow(shapeRow, prim);
1249 }
1250
1251 if (persistPrimInventories)
1252 {
1253 addPrimInventory(prim.UUID, prim.TaskInventory);
1254 }
1255 }
1256
1257 /// <summary>
1258 /// Persist prim inventory. Deletes, updates and inserts rows.
1259 /// </summary>
1260 /// <param name="primID"></param>
1261 /// <param name="items"></param>
1262 /// <returns></returns>
1263 private void addPrimInventory(LLUUID primID, IDictionary<LLUUID, TaskInventoryItem> items)
1264 {
1265 MainLog.Instance.Verbose("DATASTORE", "Entered addPrimInventory with prim ID {0}", primID);
1266
1267 // Find all existing inventory rows for this prim
1268 DataTable dbItems = ds.Tables["primitems"];
1269
1270 String sql = String.Format("primID = '{0}'", primID);
1271 DataRow[] dbItemRows = dbItems.Select(sql);
1272
1273 // Build structures for manipulation purposes
1274 IDictionary<String, DataRow> dbItemsToRemove = new Dictionary<String, DataRow>();
1275 ICollection<TaskInventoryItem> itemsToAdd
1276 = new List<TaskInventoryItem>();
1277
1278 foreach (DataRow row in dbItemRows)
1279 {
1280 dbItemsToRemove.Add((String)row["itemID"], row);
1281 }
1282
1283 // Eliminate rows from the deletion set which already exist for this prim's inventory
1284 // TODO Very temporary, need to take account of simple metadata changes soon
1285 foreach (LLUUID itemId in items.Keys)
1286 {
1287 String rawItemId = itemId.ToString();
1288
1289 if (dbItemsToRemove.ContainsKey(rawItemId))
1290 {
1291 dbItemsToRemove.Remove(rawItemId);
1292 }
1293 else
1294 {
1295 itemsToAdd.Add(items[itemId]);
1296 }
1297 }
1298
1299 // Delete excess rows
1300 foreach (DataRow row in dbItemsToRemove.Values)
1301 {
1302 MainLog.Instance.Verbose(
1303 "DATASTORE",
1304 "Removing item {0}, {1} from prim ID {2}",
1305 row["name"], row["itemID"], row["primID"]);
1306
1307 row.Delete();
1308 }
1309
1310 // Insert items not already present
1311 foreach (TaskInventoryItem newItem in itemsToAdd)
1312 {
1313 MainLog.Instance.Verbose(
1314 "DATASTORE",
1315 "Adding item {0}, {1} to prim ID {1}",
1316 newItem.name, newItem.item_id, newItem.ParentPartID);
1317
1318 DataRow newItemRow = dbItems.NewRow();
1319 fillItemRow(newItemRow, newItem);
1320 dbItems.Rows.Add(newItemRow);
1321 }
1322 }
1323
1324 /***********************************************************************
1325 *
1326 * SQL Statement Creation Functions
1327 *
1328 * These functions create SQL statements for update, insert, and create.
1329 * They can probably be factored later to have a db independant
1330 * portion and a db specific portion
1331 *
1332 **********************************************************************/
1333
1334 private SqliteCommand createInsertCommand(string table, DataTable dt)
1335 {
1336 /**
1337 * This is subtle enough to deserve some commentary.
1338 * Instead of doing *lots* and *lots of hardcoded strings
1339 * for database definitions we'll use the fact that
1340 * realistically all insert statements look like "insert
1341 * into A(b, c) values(:b, :c) on the parameterized query
1342 * front. If we just have a list of b, c, etc... we can
1343 * generate these strings instead of typing them out.
1344 */
1345 string[] cols = new string[dt.Columns.Count];
1346 for (int i = 0; i < dt.Columns.Count; i++)
1347 {
1348 DataColumn col = dt.Columns[i];
1349 cols[i] = col.ColumnName;
1350 }
1351
1352 string sql = "insert into " + table + "(";
1353 sql += String.Join(", ", cols);
1354 // important, the first ':' needs to be here, the rest get added in the join
1355 sql += ") values (:";
1356 sql += String.Join(", :", cols);
1357 sql += ")";
1358 SqliteCommand cmd = new SqliteCommand(sql);
1359
1360 // this provides the binding for all our parameters, so
1361 // much less code than it used to be
1362 foreach (DataColumn col in dt.Columns)
1363 {
1364 cmd.Parameters.Add(createSqliteParameter(col.ColumnName, col.DataType));
1365 }
1366 return cmd;
1367 }
1368
1369 private SqliteCommand createUpdateCommand(string table, string pk, DataTable dt)
1370 {
1371 string sql = "update " + table + " set ";
1372 string subsql = "";
1373 foreach (DataColumn col in dt.Columns)
1374 {
1375 if (subsql.Length > 0)
1376 {
1377 // a map function would rock so much here
1378 subsql += ", ";
1379 }
1380 subsql += col.ColumnName + "= :" + col.ColumnName;
1381 }
1382 sql += subsql;
1383 sql += " where " + pk;
1384 SqliteCommand cmd = new SqliteCommand(sql);
1385
1386 // this provides the binding for all our parameters, so
1387 // much less code than it used to be
1388
1389 foreach (DataColumn col in dt.Columns)
1390 {
1391 cmd.Parameters.Add(createSqliteParameter(col.ColumnName, col.DataType));
1392 }
1393 return cmd;
1394 }
1395
1396
1397 private string defineTable(DataTable dt)
1398 {
1399 string sql = "create table " + dt.TableName + "(";
1400 string subsql = "";
1401 foreach (DataColumn col in dt.Columns)
1402 {
1403 if (subsql.Length > 0)
1404 {
1405 // a map function would rock so much here
1406 subsql += ",\n";
1407 }
1408 subsql += col.ColumnName + " " + sqliteType(col.DataType);
1409 if (dt.PrimaryKey.Length > 0 && col == dt.PrimaryKey[0])
1410 {
1411 subsql += " primary key";
1412 }
1413 }
1414 sql += subsql;
1415 sql += ")";
1416 return sql;
1417 }
1418
1419 /***********************************************************************
1420 *
1421 * Database Binding functions
1422 *
1423 * These will be db specific due to typing, and minor differences
1424 * in databases.
1425 *
1426 **********************************************************************/
1427
1428 ///<summary>
1429 /// This is a convenience function that collapses 5 repetitive
1430 /// lines for defining SqliteParameters to 2 parameters:
1431 /// column name and database type.
1432 ///
1433 /// It assumes certain conventions like :param as the param
1434 /// name to replace in parametrized queries, and that source
1435 /// version is always current version, both of which are fine
1436 /// for us.
1437 ///</summary>
1438 ///<returns>a built sqlite parameter</returns>
1439 private SqliteParameter createSqliteParameter(string name, Type type)
1440 {
1441 SqliteParameter param = new SqliteParameter();
1442 param.ParameterName = ":" + name;
1443 param.DbType = dbtypeFromType(type);
1444 param.SourceColumn = name;
1445 param.SourceVersion = DataRowVersion.Current;
1446 return param;
1447 }
1448
1449 private void setupItemsCommands(SqliteDataAdapter da, SqliteConnection conn)
1450 {
1451 da.InsertCommand = createInsertCommand("primitems", ds.Tables["primitems"]);
1452 da.InsertCommand.Connection = conn;
1453
1454 da.UpdateCommand = createUpdateCommand("primitems", "itemID = :itemID", ds.Tables["primitems"]);
1455 da.UpdateCommand.Connection = conn;
1456
1457 SqliteCommand delete = new SqliteCommand("delete from primitems where itemID = :itemID");
1458 delete.Parameters.Add(createSqliteParameter("itemID", typeof (String)));
1459 delete.Connection = conn;
1460 da.DeleteCommand = delete;
1461 }
1462
1463 private void setupPrimCommands(SqliteDataAdapter da, SqliteConnection conn)
1464 {
1465 da.InsertCommand = createInsertCommand("prims", ds.Tables["prims"]);
1466 da.InsertCommand.Connection = conn;
1467
1468 da.UpdateCommand = createUpdateCommand("prims", "UUID=:UUID", ds.Tables["prims"]);
1469 da.UpdateCommand.Connection = conn;
1470
1471 SqliteCommand delete = new SqliteCommand("delete from prims where UUID = :UUID");
1472 delete.Parameters.Add(createSqliteParameter("UUID", typeof (String)));
1473 delete.Connection = conn;
1474 da.DeleteCommand = delete;
1475 }
1476
1477 private void setupTerrainCommands(SqliteDataAdapter da, SqliteConnection conn)
1478 {
1479 da.InsertCommand = createInsertCommand("terrain", ds.Tables["terrain"]);
1480 da.InsertCommand.Connection = conn;
1481 }
1482
1483 private void setupLandCommands(SqliteDataAdapter da, SqliteConnection conn)
1484 {
1485 da.InsertCommand = createInsertCommand("land", ds.Tables["land"]);
1486 da.InsertCommand.Connection = conn;
1487
1488 da.UpdateCommand = createUpdateCommand("land", "UUID=:UUID", ds.Tables["land"]);
1489 da.UpdateCommand.Connection = conn;
1490 }
1491
1492 private void setupLandAccessCommands(SqliteDataAdapter da, SqliteConnection conn)
1493 {
1494 da.InsertCommand = createInsertCommand("landaccesslist", ds.Tables["landaccesslist"]);
1495 da.InsertCommand.Connection = conn;
1496 }
1497
1498 private void setupShapeCommands(SqliteDataAdapter da, SqliteConnection conn)
1499 {
1500 da.InsertCommand = createInsertCommand("primshapes", ds.Tables["primshapes"]);
1501 da.InsertCommand.Connection = conn;
1502
1503 da.UpdateCommand = createUpdateCommand("primshapes", "UUID=:UUID", ds.Tables["primshapes"]);
1504 da.UpdateCommand.Connection = conn;
1505
1506 SqliteCommand delete = new SqliteCommand("delete from primshapes where UUID = :UUID");
1507 delete.Parameters.Add(createSqliteParameter("UUID", typeof (String)));
1508 delete.Connection = conn;
1509 da.DeleteCommand = delete;
1510 }
1511
1512 /// <summary>
1513 /// Create the necessary database tables.
1514 /// </summary>
1515 /// <param name="conn"></param>
1516 private void InitDB(SqliteConnection conn)
1517 {
1518 string createPrims = defineTable(createPrimTable());
1519 string createShapes = defineTable(createShapeTable());
1520 string createItems = defineTable(createItemsTable());
1521 string createTerrain = defineTable(createTerrainTable());
1522 string createLand = defineTable(createLandTable());
1523 string createLandAccessList = defineTable(createLandAccessListTable());
1524
1525 SqliteCommand pcmd = new SqliteCommand(createPrims, conn);
1526 SqliteCommand scmd = new SqliteCommand(createShapes, conn);
1527 SqliteCommand icmd = new SqliteCommand(createItems, conn);
1528 SqliteCommand tcmd = new SqliteCommand(createTerrain, conn);
1529 SqliteCommand lcmd = new SqliteCommand(createLand, conn);
1530 SqliteCommand lalcmd = new SqliteCommand(createLandAccessList, conn);
1531
1532 conn.Open();
1533
1534 try
1535 {
1536 pcmd.ExecuteNonQuery();
1537 }
1538 catch (SqliteSyntaxException)
1539 {
1540 MainLog.Instance.Warn("SQLITE", "Primitives Table Already Exists");
1541 }
1542
1543 try
1544 {
1545 scmd.ExecuteNonQuery();
1546 }
1547 catch (SqliteSyntaxException)
1548 {
1549 MainLog.Instance.Warn("SQLITE", "Shapes Table Already Exists");
1550 }
1551
1552 if (persistPrimInventories)
1553 {
1554 try
1555 {
1556 icmd.ExecuteNonQuery();
1557 }
1558 catch (SqliteSyntaxException)
1559 {
1560 MainLog.Instance.Warn("SQLITE", "Primitives Inventory Table Already Exists");
1561 }
1562 }
1563
1564 try
1565 {
1566 tcmd.ExecuteNonQuery();
1567 }
1568 catch (SqliteSyntaxException)
1569 {
1570 MainLog.Instance.Warn("SQLITE", "Terrain Table Already Exists");
1571 }
1572
1573 try
1574 {
1575 lcmd.ExecuteNonQuery();
1576 }
1577 catch (SqliteSyntaxException)
1578 {
1579 MainLog.Instance.Warn("SQLITE", "Land Table Already Exists");
1580 }
1581
1582 try
1583 {
1584 lalcmd.ExecuteNonQuery();
1585 }
1586 catch (SqliteSyntaxException)
1587 {
1588 MainLog.Instance.Warn("SQLITE", "LandAccessList Table Already Exists");
1589 }
1590 conn.Close();
1591 }
1592
1593 private bool TestTables(SqliteConnection conn)
1594 {
1595 SqliteCommand primSelectCmd = new SqliteCommand(primSelect, conn);
1596 SqliteDataAdapter pDa = new SqliteDataAdapter(primSelectCmd);
1597
1598 SqliteCommand shapeSelectCmd = new SqliteCommand(shapeSelect, conn);
1599 SqliteDataAdapter sDa = new SqliteDataAdapter(shapeSelectCmd);
1600
1601 SqliteCommand itemsSelectCmd = new SqliteCommand(itemsSelect, conn);
1602 SqliteDataAdapter iDa = new SqliteDataAdapter(itemsSelectCmd);
1603
1604 SqliteCommand terrainSelectCmd = new SqliteCommand(terrainSelect, conn);
1605 SqliteDataAdapter tDa = new SqliteDataAdapter(terrainSelectCmd);
1606
1607 SqliteCommand landSelectCmd = new SqliteCommand(landSelect, conn);
1608 SqliteDataAdapter lDa = new SqliteDataAdapter(landSelectCmd);
1609
1610 SqliteCommand landAccessListSelectCmd = new SqliteCommand(landAccessListSelect, conn);
1611 SqliteDataAdapter lalDa = new SqliteDataAdapter(landAccessListSelectCmd);
1612
1613 DataSet tmpDS = new DataSet();
1614 try
1615 {
1616 pDa.Fill(tmpDS, "prims");
1617 sDa.Fill(tmpDS, "primshapes");
1618
1619 if (persistPrimInventories)
1620 iDa.Fill(tmpDS, "primitems");
1621
1622 tDa.Fill(tmpDS, "terrain");
1623 lDa.Fill(tmpDS, "land");
1624 lalDa.Fill(tmpDS, "landaccesslist");
1625 }
1626 catch (SqliteSyntaxException)
1627 {
1628 MainLog.Instance.Verbose("DATASTORE", "SQLite Database doesn't exist... creating");
1629 InitDB(conn);
1630 }
1631
1632 pDa.Fill(tmpDS, "prims");
1633 sDa.Fill(tmpDS, "primshapes");
1634
1635 if (persistPrimInventories)
1636 iDa.Fill(tmpDS, "primitems");
1637
1638 tDa.Fill(tmpDS, "terrain");
1639 lDa.Fill(tmpDS, "land");
1640 lalDa.Fill(tmpDS, "landaccesslist");
1641
1642 foreach (DataColumn col in createPrimTable().Columns)
1643 {
1644 if (!tmpDS.Tables["prims"].Columns.Contains(col.ColumnName))
1645 {
1646 MainLog.Instance.Verbose("DATASTORE", "Missing required column:" + col.ColumnName);
1647 return false;
1648 }
1649 }
1650
1651 foreach (DataColumn col in createShapeTable().Columns)
1652 {
1653 if (!tmpDS.Tables["primshapes"].Columns.Contains(col.ColumnName))
1654 {
1655 MainLog.Instance.Verbose("DATASTORE", "Missing required column:" + col.ColumnName);
1656 return false;
1657 }
1658 }
1659
1660 // TODO Not restoring prim inventories quite yet
1661
1662 foreach (DataColumn col in createTerrainTable().Columns)
1663 {
1664 if (!tmpDS.Tables["terrain"].Columns.Contains(col.ColumnName))
1665 {
1666 MainLog.Instance.Verbose("DATASTORE", "Missing require column:" + col.ColumnName);
1667 return false;
1668 }
1669 }
1670
1671 foreach (DataColumn col in createLandTable().Columns)
1672 {
1673 if (!tmpDS.Tables["land"].Columns.Contains(col.ColumnName))
1674 {
1675 MainLog.Instance.Verbose("DATASTORE", "Missing require column:" + col.ColumnName);
1676 return false;
1677 }
1678 }
1679
1680 foreach (DataColumn col in createLandAccessListTable().Columns)
1681 {
1682 if (!tmpDS.Tables["landaccesslist"].Columns.Contains(col.ColumnName))
1683 {
1684 MainLog.Instance.Verbose("DATASTORE", "Missing require column:" + col.ColumnName);
1685 return false;
1686 }
1687 }
1688
1689 return true;
1690 }
1691
1692 /***********************************************************************
1693 *
1694 * Type conversion functions
1695 *
1696 **********************************************************************/
1697
1698 private DbType dbtypeFromType(Type type)
1699 {
1700 if (type == typeof (String))
1701 {
1702 return DbType.String;
1703 }
1704 else if (type == typeof (Int32))
1705 {
1706 return DbType.Int32;
1707 }
1708 else if (type == typeof (Double))
1709 {
1710 return DbType.Double;
1711 }
1712 else if (type == typeof (Byte))
1713 {
1714 return DbType.Byte;
1715 }
1716 else if (type == typeof (Double))
1717 {
1718 return DbType.Double;
1719 }
1720 else if (type == typeof (Byte[]))
1721 {
1722 return DbType.Binary;
1723 }
1724 else
1725 {
1726 return DbType.String;
1727 }
1728 }
1729
1730 // this is something we'll need to implement for each db
1731 // slightly differently.
1732 private string sqliteType(Type type)
1733 {
1734 if (type == typeof (String))
1735 {
1736 return "varchar(255)";
1737 }
1738 else if (type == typeof (Int32))
1739 {
1740 return "integer";
1741 }
1742 else if (type == typeof (Int64))
1743 {
1744 return "integer";
1745 }
1746 else if (type == typeof (Double))
1747 {
1748 return "float";
1749 }
1750 else if (type == typeof (Byte[]))
1751 {
1752 return "blob";
1753 }
1754 else
1755 {
1756 return "string";
1757 }
1758 }
1759 }
1760}