MongoDB geospatial index in C# - c#

I have been trying to get started but run into the same rock time after time trying to create and query MongoDB with C# official driver. The problem is how to create data with geo information. I am just not finding the answer.
Code:
MongoUrl url = new MongoUrl("mongodb://xxx.xx.x.xx/mydb");
MongoServer server = MongoServer.Create(url);
MongoDatabase database = server.GetDatabase("mydb");
<-- this works fine
BsonDocument[] batch = {
new BsonDocument {
{ "name", "Bran" },
{ "loc", "10, 10" }
},
new BsonDocument {
{ "name", "Ayla" },
{ "loc", "0, 0" }
}
};
places.InsertBatch(batch);
<-- that part is wrong somehow
places.EnsureIndex(IndexKeys.GeoSpatial("loca"));
var queryplaces = Query.WithinCircle("loca", 0, 0, 11);
var cursor = places.Find(queryplaces);
foreach (var hit in cursor)
{
foreach (var VARIABLE in hit)
{
Console.WriteLine(VARIABLE.Value);
}
}
<-- I think that part should show both documents, now showing none. A simple find shows them both.
Would be happy for some help.

example below is in C# (it's important to note order in the array which is longitude, latitude- follows the more logical order of x,y as opposed to the more commonly used form where latitude precedes longitude) :
1.) first your class needs to have this:
public double[] Location { get; set; }
public double Latitude
{
get { return _latitude; }
set
{
Location[1] = value;
_latitude = value;
}
}
public double Longitude
{
get { return _longitude; }
set
{
Location[0] = value;
_longitude = value;
}
}
public MyClass()
{
Location = new double[2];
}
2.) then here is some code to get you started with the official C# driver and doing an insert w/ use of geo indexing:
/// <summary>
/// Inserts object and creates GeoIndex on collection (assumes TDocument is a class
/// containing an array double[] Location where [0] is the x value (as longitude)
/// and [1] is the y value (as latitude) - this order is important for spherical queries.
///
/// Collection name is assigned as typeof(TDocument).ToString()
/// </summary>
/// <param name="dbName">Your target database</param>
/// <param name="data">The object you're storing</param>
/// <param name="geoIndexName">The name of the location based array on which to create the geoIndex</param>
/// <param name="indexNames">optional: a dictionary containing any additional fields on which you would like to create an index
/// where the key is the name of the field on which you would like to create your index and the value should be either SortDirection.Ascending
/// or SortDirection.Descending. NOTE: this should not include geo indexes! </param>
/// <returns>void</returns>
public static void MongoGeoInsert<TDocument>(string dbName, TDocument data, string geoIndexName, Dictionary<string, SortDirection> indexNames = null)
{
Connection connection = new Connection(dbName);
MongoCollection collection = connection.GetMongoCollection<TDocument>(typeof(TDocument).Name, connection.Db);
collection.Insert<TDocument>(data);
/* NOTE: Latitude and Longitude MUST be wrapped in separate class or array */
IndexKeysBuilder keys = IndexKeys.GeoSpatial(geoIndexName);
IndexOptionsBuilder options = new IndexOptionsBuilder();
options.SetName("idx_" + typeof(TDocument).Name);
// since the default GeoSpatial range is -180 to 180, we don't need to set anything here, but if
// we wanted to use something other than latitude/longitude, we could do so like this:
// options.SetGeoSpatialRange(-180.0, 180.0);
if (indexNames != null)
{
foreach (var indexName in indexNames)
{
if (indexName.Value == SortDirection.Decending)
{
keys = keys.Descending(indexName.Key);
}
else if (indexName.Value == SortDirection.Ascending)
{
keys = keys.Ascending(indexName.Key);
}
}
}
collection.EnsureIndex(keys, options);
connection.Db.Server.Disconnect();
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;
namespace MyMongo.Helpers
{
public class Connection
{
private const string DbName = "";
private const string Prefix = "mongodb://";
//private const string Server = "(...):27017/";
private const string Server = "localhost:27017/";
private const string PassWord = "";
private const string UserName = "";
private const string Delimeter = "";
//if using MongoHQ
//private const string Delimeter = ":";
//private const string Prefix = "mongodb://";
//private const string DbName = "(...)";
//private const string UserName = "(...)";
//private const string Server = "#flame.mongohq.com:(<port #>)/";
//private const string PassWord = "(...)";
private readonly string _connectionString = string.Empty;
public MongoDatabase Db { get; private set; }
public MongoCollection Collection { get; private set; }
public Connection()
{
_connectionString = Prefix + UserName + Delimeter + PassWord + Server + DbName;
}
public Connection(string dbName)
{
_connectionString = Prefix + UserName + Delimeter + PassWord + Server + DbName;
Db = GetDatabase(dbName);
}
//mongodb://[username:password#]hostname[:port][/[database][?options]]
public MongoDatabase GetDatabase(string dbName)
{
MongoServer server = MongoServer.Create(_connectionString);
MongoDatabase database = server.GetDatabase(dbName);
return database;
}
public MongoCollection<TDocument> GetMongoCollection<TDocument>(string collectionName, MongoDatabase db, SafeMode safeMode = null)
{
if (safeMode == null) { safeMode = new SafeMode(true); }
MongoCollection<TDocument> result = db.GetCollection<TDocument>(collectionName, safeMode);
return result;
}
}
}

After looking and searching I found the answer here: https://github.com/karlseguin/pots-importer/blob/master/PotsImporter/NodeImporter.cs
This to be read with my first piece of code as this fixes it.
MongoCollection<BsonDocument> places =
database.GetCollection<BsonDocument>("places");
BsonDocument[] batch = {
new BsonDocument { { "name", "Bran" }, { "loc", new BsonArray(new[] { 10, 10 }) } },
new BsonDocument { { "name", "Ayla" }, { "loc", new BsonArray(new[] { 0, 0 }) } }
};
places.InsertBatch(batch);
places.EnsureIndex(IndexKeys.GeoSpatial("loc"));
var queryplaces = Query.WithinCircle("loc", 5, 5, 10);
var cursor = places.Find(queryplaces);
foreach (var hit in cursor)
{
Console.WriteLine("in circle");
foreach (var VARIABLE in hit)
{
Console.WriteLine(VARIABLE.Value);
}
}
As a point of clarification: The problem with the code in the question is that location information should not be stored as a string, but rather as an array of 2 elements (x, y).

Related

Bind Multi level json with configuration in c# web api?

I have multi-level json coming from aws secret and I want to bind this json or secret with the configuration of c# so that I can use it in the whole project.
public class AmazonSecretsManagerConfigurationProvider : ConfigurationProvider
{
private readonly string _region;
private readonly string _secretName;
/// <summary>
/// Initializes a new instance of the <see cref="AmazonSecretsManagerConfigurationProvider"/> class.
/// </summary>
/// <param name="region"></param>
/// <param name="secretName"></param>
public AmazonSecretsManagerConfigurationProvider(string region, string secretName)
{
_region = region;
_secretName = secretName;
}
public override void Load()
{
var secret = GetSecret();
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(secret);
Dictionary<string, string> Data = data.ToDictionary(k => k.Key, k => k.Value.ToString());
}
private string GetSecret()
{
var request = new GetSecretValueRequest
{
SecretId = "awsseceret",
VersionStage = "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified.
};
string accesskey = "################";
string secretkey = "#######################";
using (var client =
new AmazonSecretsManagerClient(accesskey, secretkey, RegionEndpoint.GetBySystemName(this._region)))
{
var response = client.GetSecretValueAsync(request).Result;
string secretString;
if (response.SecretString != null)
{
secretString = response.SecretString;
}
else
{
var memoryStream = response.SecretBinary;
var reader = new StreamReader(memoryStream);
secretString =
System.Text.Encoding.UTF8
.GetString(Convert.FromBase64String(reader.ReadToEnd()));
}
return secretString;
}
}
}
}
I get aws secretString in the secret variable but how can I bind this to the configuration?

sorting list of strings based on date 20210419

I have. list of files, names of the files are log20211904.json what would be the best way to sort this name based on the date that is included in the name of the file? What I get now from my method substring is 20210419 which is not valid date time. I wonder how to do it effectively and get valid DateTimeOffset from 20210419
I was thinking
var orderedFIle = new List<LogFile>();
var fileDetail = new LogFile()
{
FileName = "log20210419",
};
var fileDetailtest = new LogFile()
{
FileName = "log20210420",
};
var test = fileDetail.FileName.Substring(3, 8);
Console.WriteLine(test);
orderedFIle.Add(fileDetail);
orderedFIle.Add(fileDetailtest);
var list = orderedFIle.OrderByDescending(f => DateTime.Parse(f.FileName.Substring( 3,8)));
foreach (var VARIABLE in list)
{
Console.WriteLine(VARIABLE.FileName);
}
But the date is actually without separators. What would be the best approach
Use IComparable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace HotelManagement
{
public class Program
{
public static void Main(string[] args)
{
string[] filenames = {
"log20211904.json",
"log20211804.json",
"log20211904.json",
"log20210903.json",
"log20211902.json",
"log20211910.json",
"log20211909.json"
};
string[] results = filenames.OrderBy(x => new SortFileName(x)).ToArray();
}
}
public class SortFileName : IComparable<SortFileName>
{
private DateTime date { get; set; }
public SortFileName(string filename)
{
date = DateTime.ParseExact(filename.Substring(3,8), "yyyyddMM", System.Globalization.CultureInfo.InvariantCulture);
}
public int CompareTo(SortFileName other)
{
return this.date.CompareTo(other.date);
}
}
}
Define a class as follows.
/// <summary>
/// Use a regular expression to validate the file name and extract the date parts.
/// Implement IComparable to define a default sort order.
/// </summary>
public class JsonLogFileName : IComparable<JsonLogFileName>
{
// Private data members.
private string fileName;
private DateTime fileDate;
// Public accessor methods.
public String FileName => this.fileName;
public DateTime FileDate => this.fileDate;
/// <summary>
/// Class constructor.
/// </summary>
/// <param name="fileName"></param>
public JsonLogFileName(string fileName) {
// Use a regular expression to validate the file name
// and extract the date parts.
Match m = Regex.Match(fileName, #"^log(\d{4})(\d{2})(\d{2}).json$");
if (!m.Success) {
throw new ArgumentException();
}
this.fileName = fileName;
int year = int.Parse(m.Groups[1].Value);
int month = int.Parse(m.Groups[2].Value);
int day = int.Parse(m.Groups[3].Value);
this.fileDate = new DateTime(year, month, day);
}
// Default sort order
public int CompareTo(JsonLogFileName other) {
return DateTime.Compare(this.FileDate, other.FileDate);
}
}
You can use it like this.
public static void Main(String[] args) {
List<JsonLogFileName> jsonLogFileNameList = new List<JsonLogFileName> {
new JsonLogFileName("log20210419.json"),
new JsonLogFileName("log20210418.json"),
new JsonLogFileName("log20210417.json"),
new JsonLogFileName("log20210416.json"),
new JsonLogFileName("log20210415.json"),
};
jsonLogFileNameList.Sort();
foreach (JsonLogFileName jsonFileName in jsonLogFileNameList) {
Console.WriteLine($"{jsonFileName.FileName}: {jsonFileName.FileDate}");
}
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
Since the date in the string is already in a sortable format you can just sort by the full log name.
For example:
var l = new List<LogFile>();
l.Add(new LogFile(){FileName = "log20210419"});
l.Add(new LogFile(){FileName = "log20210420"});
l.Add(new LogFile(){FileName = "log20210101"});
foreach (var f in l.OrderByDescending(f => f.FileName))
{
Console.WriteLine(f.FileName);
}
prints
log20210420
log20210419
log20210101
If you really want to parse then you can use ParseExact:
DateTime.ParseExact(f.FileName, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)

c# - Code that HAS NOT RUN YET is causing an exception? How is this even possible?

So I am completely stumped on this one. I am getting an error Object reference not set to an instance of an object. and I am not sure why.
I have a class FILE
public class FILE
{
private string _fileName;
public string fileName
{
get
{
if (!Settings.Values.CaseSensitive)
return this._fileName.ToUpper();
else
return this._fileName;
}
set
{
if (!Settings.Values.CaseSensitive)
this._fileName = value.ToUpper();
else
this._fileName = value;
}
}
public string folderName { get; set; }
public byte[] fileHashDigest { get; set; }
}
I am creating an instance like:
FILE test1233;
test1233 = new FILE(); // <---- Ex thrown here!? Why???
test1233.fileName = "";
folderName = "";
fileHashDigest = new byte[1];
As soon as the variable is placed on the stack, it throws an exception. BUT... if I remove all refrences to this variable on code further down (WHICH HAS NOT YET BEEN EXECUTED IN DEBUGMODE!!!) then no exception gets thrown. What on earth is going on here?
For refrence, here is the method in it's entirety:
private bool IsFolderOverride(FileCollection zipFILEList, DataTable exceptionTableFileList, DataRow currentRow, ref DataTable detectedFolderRenames)
{
bool foundInExceptionTable = false;
foreach (DataRow exRow in exceptionTableFileList.Rows)
{
if (exRow["FILE_NAME"].ToString().ToUpper() == currentRow["FILE_NAME"].ToString().ToUpper() &&
(decimal)exRow["WINDOW_GROUP_ID"] == (decimal)currentRow["WINDOW_GROUP_ID"])
{
string name = exRow["FILE_NAME"].ToString().ToUpper();
string folder = exRow["FOLDER_NAME"].ToString().ToUpper();
byte[] digest = (byte[])exRow["FILE_HASH_DIGEST"];
CopyCat exCopyCat = new CopyCat();
exCopyCat.fileName = name;
exCopyCat.folderName = folder;
exCopyCat.fileHashDigest = digest;
//HAS AN EXCEPTION!
FILE test1233 = new FILE();
test1233.fileName = "";
test1233.folderName = "";
test1233.fileHashDigest = new byte[1];
//NO EXCEPTION THROWN
FILE test = new FILE();
bool test9 = zipFileList.Contains(test1233);
test.fileName = name;
test.folderName = folder;
test.fileHashDigest = digest;
FILE test123 = new FILE();
if (zipFileList.Contains(test1233)) // Exact match found in zip in old folder from exception table.
{
FILE exists = zipFileList.Where(f => f.fileName == test1233.fileName &&
f.fileHashDigest.SequenceEqual(test1233.fileHashDigest)).First();
object[] items = exRow.ItemArray;
Array.Resize(ref items, items.Length + 4);
items[items.Length - 1] = "Y";
items[items.Length - 2] = exists.folderName;
items[items.Length - 3] = test1233.folderName;
items[items.Length - 4] = "Folder Override";
if (detectedFolderRenames.Rows.Count == 0 || !detectedFolderRenames.Rows.Contains(items[0]))
detectedFolderRenames.Rows.Add(items);
foundInExceptionTable = true;
break;
}
else if (zipFileList.ContainsPartially(test1233)) // Match in zip with Different Hash found from ex table.
{
FILE exists = zipFileList.Where(f => f.fileName == test1233.fileName).First();
object[] items = exRow.ItemArray;
Array.Resize(ref items, items.Length + 4);
items[items.Length - 1] = "N";
items[items.Length - 2] = exists.folderName;
items[items.Length - 3] = test1233.folderName;
items[items.Length - 4] = "Folder Override";
if (detectedFolderRenames.Rows.Count == 0 || !detectedFolderRenames.Rows.Contains(items[0]))
detectedFolderRenames.Rows.Add(items);
foundInExceptionTable = true;
break;
}
}
else
continue;
}
return foundInExceptionTable;
}
UPDATE: I am still working on an example for you, but in the mean time here is potentially helpful information:
test1233' threw an exception of type 'System.NullReferenceException'
Data: {System.Collections.ListDictionaryInternal}
HResult: -2147467261
HelpLink: null
InnerException: null
Message: "Object reference not set to an instance of an object."
Source: null
StackTrace: null
TargetSite: null
The Data: {System.Collections.ListDictionaryInternal} part is a little interesting to me, my class does not use any dictionary lists.
UPDATE #2: Ok, I have produced a reproducible sequence of steps for others to try. On your machines, it may be just fine, like Jon Skeet said, it might be my debug environment settings but please try and let me know. Here are the steps to reproduce.
Open console app project and copy paste code below.
Set a break point here:
First run code past break point, it works! :D
Then run code again but this time STOP at the break point and DRAG the executing statement cursor INTO the if statement from here:
to here:
There it is! So the error was caused from my method of testing, but does this make any sense or is this just me on my machine?
CODE:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
FILECollection randomCollection = new FILECollection();
// Fill with junk test data:
for(int i = 0; i<10; i++)
{
FILE junkfile = new FILE() { fileName = i.ToString(), folderName = i.ToString(), fileHashDigest = new byte[1] };
randomCollection.Add(junkfile);
}
if (true)
{
Console.WriteLine("testing this weird exception issue...");
FILE test;
test = new FILE();
test.fileName = "3";
test.folderName = "3";
test.fileHashDigest = new byte[1];
FILE exists = randomCollection.Where(f => f.fileName == test.fileName &&
f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();
}
}
}
public class FILE
{
public FILE() { _fileName = "";}
private string _fileName;
public string fileName
{
get
{
if (false)
return this._fileName.ToUpper();
else
return this._fileName;
}
set
{
if (false)
this._fileName = value.ToUpper();
else
this._fileName = value;
}
}
public string folderName { get; set; }
public byte[] fileHashDigest { get; set; }
}
public class FILECollection : IEnumerable<FILE>, ICollection<FILE>
{
private HashSet<FILE> svgHash;
private static List<FILE> PreallocationList;
public string FileName = "N/A";
/// <summary>
/// Default Constructor, will not
/// preallocate memory.
/// </summary>
/// <param name="PreallocationSize"></param>
public FILECollection()
{
this.svgHash = new HashSet<FILE>();
this.svgHash.Clear();
}
/// <summary>
/// Overload Constructor Preallocates
/// memory to be used for the new
/// FILE Collection.
/// </summary>
public FILECollection(int PreallocationSize, string fileName = "N/A", int fileHashDigestSize = 32)
{
FileName = fileName;
PreallocationList = new List<FILE>(PreallocationSize);
for (int i = 0; i <= PreallocationSize; i++)
{
byte[] buffer = new byte[fileHashDigestSize];
FILE preallocationSVG = new FILE()
{
fileName = "",
folderName = "",
fileHashDigest = buffer
};
PreallocationList.Add(preallocationSVG);
}
this.svgHash = new HashSet<FILE>(PreallocationList);
this.svgHash.Clear(); // Capacity remains unchanged until a call to TrimExcess is made.
}
/// <summary>
/// Add an FILE file to
/// the FILE Collection.
/// </summary>
/// <param name="svg"></param>
public void Add(FILE svg)
{
this.svgHash.Add(svg);
}
/// <summary>
/// Removes all elements
/// from the FILE Collection
/// </summary>
public void Clear()
{
svgHash.Clear();
}
/// <summary>
/// Determine if the FILE collection
/// contains the EXACT FILE file, folder,
/// and byte[] sequence. This guarantees
/// that the collection contains the EXACT
/// file you are looking for.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(FILE item)
{
return svgHash.Any(f => f.fileHashDigest.SequenceEqual(item.fileHashDigest) &&
f.fileName == item.fileName &&
f.folderName == item.folderName);
}
/// <summary>
/// Determine if the FILE collection
/// contains the same file and folder name,
/// byte[] sequence is not compared. The file and folder
/// name may be the same but this does not guarantee the
/// file contents are exactly the same. Use Contains() instead.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool ContainsPartially(FILE item)
{
return svgHash.Any(f => f.fileName == item.fileName &&
f.folderName == item.folderName);
}
/// <summary>
/// Returns the total number
/// of FILE files in the Collection.
/// </summary>
public int Count
{ get { return svgHash.Count(); } }
public bool IsReadOnly
{ get { return true; } }
public void CopyTo(FILE[] array, int arrayIndex)
{
svgHash.CopyTo(array, arrayIndex);
}
public bool Remove(FILE item)
{
return svgHash.Remove(item);
}
public IEnumerator<FILE> GetEnumerator()
{
return svgHash.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return svgHash.GetEnumerator();
}
}
}
I think either I am debugging in a terribly wrong way, or Microsoft should take a look at this. It's like future code is breaking current code...which is impossible!
I tried to execute the code in a console app and is working, can you give more details? Answers about the Settings initialization doesn't make sense here at this point because you should be able to create the instance of FILE. Once you try to assign(set) or request(get) then you are dealing with fileName property. I am not seeing why you get the exception when you create the instance.
static void Main(string[] args)
{
FILE test1233;
test1233 = new FILE(); // <---- Ex is not thrown here!? test1233.fileName = "";
test1233.folderName = "";
test1233.fileHashDigest = new byte[1];
}
public class FILE
{
private string _fileName;
public string fileName
{
get
{
if (YOUR SETTING CONDITION HERE)
return this._fileName.ToUpper();
else
return this._fileName;
}
set
{
if (YOUR SETTING CONDITION HERE)
this._fileName = value.ToUpper();
else
this._fileName = value;
}
}
public string folderName { get; set; }
public byte[] fileHashDigest { get; set; }
}
see the cursor on the print screen
Here is my debug configuration

Set default value for string prompt

The editor class has a method called GetString which prompts the user for a string value via AutoCAD's command prompt. I call it in this wrapper method:
public static string PromptUserForString(string message = "Enter a string: ", string defaultAnswer = "")
{
return _editor.GetString("\n" + message).StringResult;
}
The argument message becomes the message the user sees when prompted for a string. How do I set it up so that the value of default answer is automatically set to be the answer so that if the user hits enter right away that becomes the value like in the screen shot below
So 1 is automatically typed as an answer meaning the user can either hit enter for the value of 1 or change 1 to whatever non-default answer they want
I paste you some code as example for the different prompts :
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.ApplicationServices;
namespace EditorUtilities
{
/// <summary>
/// Prompts with the active document ( MdiActiveDocument )
/// </summary>
public class EditorHelper : IEditorHelper
{
private readonly Editor _editor;
public EditorHelper(Document document)
{
_editor = document.Editor;
}
public PromptEntityResult PromptForObject(string promptMessage, Type allowedType, bool exactMatchOfAllowedType)
{
var polyOptions = new PromptEntityOptions(promptMessage);
polyOptions.SetRejectMessage("Entity is not of type " + allowedType);
polyOptions.AddAllowedClass(allowedType, exactMatchOfAllowedType);
var polyResult = _editor.GetEntity(polyOptions);
return polyResult;
}
public PromptPointResult PromptForPoint(string promptMessage, bool useDashedLine = false, bool useBasePoint = false, Point3d basePoint = new Point3d(),bool allowNone = true)
{
var pointOptions = new PromptPointOptions(promptMessage);
if (useBasePoint)
{
pointOptions.UseBasePoint = true;
pointOptions.BasePoint = basePoint;
pointOptions.AllowNone = allowNone;
}
if (useDashedLine)
{
pointOptions.UseDashedLine = true;
}
var pointResult = _editor.GetPoint(pointOptions);
return pointResult;
}
public PromptPointResult PromptForPoint(PromptPointOptions promptPointOptions)
{
return _editor.GetPoint(promptPointOptions);
}
public PromptDoubleResult PromptForDouble(string promptMessage, double defaultValue = 0.0)
{
var doubleOptions = new PromptDoubleOptions(promptMessage);
if (Math.Abs(defaultValue - 0.0) > Double.Epsilon)
{
doubleOptions.UseDefaultValue = true;
doubleOptions.DefaultValue = defaultValue;
}
var promptDoubleResult = _editor.GetDouble(doubleOptions);
return promptDoubleResult;
}
public PromptIntegerResult PromptForInteger(string promptMessage)
{
var promptIntResult = _editor.GetInteger(promptMessage);
return promptIntResult;
}
public PromptResult PromptForKeywordSelection(
string promptMessage, IEnumerable<string> keywords, bool allowNone, string defaultKeyword = "")
{
var promptKeywordOptions = new PromptKeywordOptions(promptMessage) { AllowNone = allowNone };
foreach (var keyword in keywords)
{
promptKeywordOptions.Keywords.Add(keyword);
}
if (defaultKeyword != "")
{
promptKeywordOptions.Keywords.Default = defaultKeyword;
}
var keywordResult = _editor.GetKeywords(promptKeywordOptions);
return keywordResult;
}
public Point3dCollection PromptForRectangle(out PromptStatus status, string promptMessage)
{
var resultRectanglePointCollection = new Point3dCollection();
var viewCornerPointResult = PromptForPoint(promptMessage);
var pointPromptStatus = viewCornerPointResult.Status;
if (viewCornerPointResult.Status == PromptStatus.OK)
{
var rectangleJig = new RectangleJig(viewCornerPointResult.Value);
var jigResult = _editor.Drag(rectangleJig);
if (jigResult.Status == PromptStatus.OK)
{
// remove duplicate point at the end of the rectangle
var polyline = rectangleJig.Polyline;
var viewPolylinePoints = GeometryUtility.GetPointsFromPolyline(polyline);
if (viewPolylinePoints.Count == 5)
{
viewPolylinePoints.RemoveAt(4); // dont know why but true, probably mirror point with the last point
}
}
pointPromptStatus = jigResult.Status;
}
status = pointPromptStatus;
return resultRectanglePointCollection;
}
public PromptSelectionResult PromptForSelection(string promptMessage = null, SelectionFilter filter = null)
{
var selectionOptions = new PromptSelectionOptions { MessageForAdding = promptMessage };
var selectionResult = String.IsNullOrEmpty(promptMessage) ? _editor.SelectAll(filter) : _editor.GetSelection(selectionOptions, filter);
return selectionResult;
}
public PromptSelectionResult PromptForSelection(PromptSelectionOptions promptSelectionOptions,SelectionFilter filter = null)
{
return _editor.GetSelection(promptSelectionOptions, filter);
}
public void WriteMessage(string message)
{
_editor.WriteMessage(message);
}
public void DrawVector(Point3d from, Point3d to, int color, bool drawHighlighted)
{
_editor.DrawVector(from, to, color, drawHighlighted);
}
}
}

C# (Craft.Net.Client) ServerList Exception

I'm using Craft.Net.Client library but I've got an error when trying to use the ServerList.SaveTo(string file) method : Unable to read beyond the end of the stream (EndOfStreamException)
ServerList.cs :
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using fNbt;
namespace Craft.Net.Client
{
/// <summary>
/// Provides functionality for interacting with
/// the saved vanilla server list.
/// </summary>
public class ServerList
{
public static string ServersDat
{
get
{
return Path.Combine(DotMinecraft.GetDotMinecraftPath(), "servers.dat");
}
}
public ServerList()
{
Servers = new List<Server>();
}
public List<Server> Servers { get; set; }
public void Save()
{
SaveTo(ServersDat);
}
public void SaveTo(string file)
{
var nbt = new NbtFile(file); // ERROR : Unable to read beyond the end of the stream (EndOfStreamException)
nbt.RootTag = new NbtCompound("");
var list = new NbtList("servers", NbtTagType.Compound);
foreach (var server in Servers)
{
var compound = new NbtCompound();
compound.Add(new NbtString("name", server.Name));
compound.Add(new NbtString("ip", server.Ip));
compound.Add(new NbtByte("hideAddress", (byte)(server.HideAddress ? 1 : 0)));
compound.Add(new NbtByte("acceptTextures", (byte)(server.AcceptTextures ? 1 : 0)));
list.Add(compound);
}
nbt.RootTag.Add(list);
nbt.SaveToFile(file, NbtCompression.None);
}
public static ServerList Load()
{
return LoadFrom(ServersDat);
}
public static ServerList LoadFrom(string file)
{
var list = new ServerList();
var nbt = new NbtFile(file);
foreach (NbtCompound server in nbt.RootTag["servers"] as NbtList)
{
var entry = new Server();
if (server.Contains("name"))
entry.Name = server["name"].StringValue;
if (server.Contains("ip"))
entry.Ip = server["ip"].StringValue;
if (server.Contains("hideAddress"))
entry.HideAddress = server["hideAddress"].ByteValue == 1;
if (server.Contains("acceptTextures"))
entry.AcceptTextures = server["acceptTextures"].ByteValue == 1;
list.Servers.Add(entry);
}
return list;
}
public class Server
{
public string Name { get; set; }
public string Ip { get; set; }
public bool HideAddress { get; set; }
public bool AcceptTextures { get; set; }
public override string ToString()
{
return Name;
}
}
}
}
And where I call the method :
ServerList.Server server = new ServerList.Server();
server.Name = "x";
server.Ip = "x";
server.HideAddress = true;
server.AcceptTextures = true;
ServerList list = new ServerList();
list.Servers.Add(server);
if (!File.Exists(RuntimeInfo.getMinecraftDir() + #"\servers.dat"))
{
File.Create(RuntimeInfo.getMinecraftDir() + #"\servers.dat");
}
list.SaveTo(RuntimeInfo.getMinecraftDir() + #"\servers.dat");
One thing I've noticed is that I only got the error when servers.dat is empty but not if it has already servers saved.
Can anyone help me?
Thanks in advance,
EDIT : Thanks to steveg89, the solution below solves the problem (updating SaveTo method) :
public void SaveTo(string file)
{
NbtFile nbt;
if (File.Exists(RuntimeInfo.getMinecraftDir() + #"\servers.dat"))
{
nbt = new NbtFile();
nbt.SaveToFile(RuntimeInfo.getMinecraftDir() + #"\servers.dat", NbtCompression.None);
}
else
nbt = new NbtFile();
nbt.RootTag = new NbtCompound("");
var list = new NbtList("servers", NbtTagType.Compound);
foreach (var server in Servers)
{
var compound = new NbtCompound();
compound.Add(new NbtString("name", server.Name));
compound.Add(new NbtString("ip", server.Ip));
compound.Add(new NbtByte("hideAddress", (byte)(server.HideAddress ? 1 : 0)));
compound.Add(new NbtByte("acceptTextures", (byte)(server.AcceptTextures ? 1 : 0)));
list.Add(compound);
}
nbt.RootTag.Add(list);
nbt.SaveToFile(file, NbtCompression.None);
}
I notice their constructor for an NbtFile tries to read from the file. It's assuming the file is in the correct format already. That means you'd have to create it and save it. Their API SHOULD handle this for you but doesn't, so try this
if (!File.Exists(RuntimeInfo.getMinecraftDir() + #"\servers.dat"))
{
NbtFile nbt = new NbtFile();
nbt.SaveToFile(RuntimeInfo.getMinecraftDir() + #"\servers.dat", Nbt.Compression.None);
}
I think a smarter way to do it would be to correct the SaveTo method of the ServerList. This method would mean you wouldn't have to check it in your code, but it does mean you'd be using your own flavor of Craft.Net. I'd do it like so:
public void SaveTo(string file)
{
NbtFile nbt;
if( File.Exists( file ) )
{
nbt = new NbtFile(file);
}
else
{
nbt = new NbtFile();
}
nbt.RootTag = new NbtCompound("");
var list = new NbtList("servers", NbtTagType.Compound);
foreach (var server in Servers)
{
var compound = new NbtCompound();
compound.Add(new NbtString("name", server.Name));
compound.Add(new NbtString("ip", server.Ip));
compound.Add(new NbtByte("hideAddress", (byte)(server.HideAddress ? 1 : 0)));
compound.Add(new NbtByte("acceptTextures", (byte)(server.AcceptTextures ? 1 : 0)));
list.Add(compound);
}
nbt.RootTag.Add(list);
nbt.SaveToFile(file, NbtCompression.None);
}

Categories