[smuxi/experiments/sqlite: 1/27] [Engine] New partial message buffer implementation based on JSON and Git
- From: Mirco M. M. Bauer <mmmbauer src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [smuxi/experiments/sqlite: 1/27] [Engine] New partial message buffer implementation based on JSON and Git
- Date: Sun, 23 Feb 2014 17:06:28 +0000 (UTC)
commit 6894d2816dc8c30fb838cc86b5cd78100613eac5
Author: Mirco Bauer <meebey meebey net>
Date: Sun Feb 19 15:50:31 2012 +0100
[Engine] New partial message buffer implementation based on JSON and Git
- Include BSD 3-clause licensed ServiceStack.Text library for
JSON serialization / deserialization
- Include GPLv2-only + linking exception licensed libgit2sharp library for
storing messages in a Git repository
- Added DataContract attributes to MessageModel and related to hint
the JSON serializer
- Initial partial GitMessageBuffer implementation based on Index
- Implemented Add() and get_Count()
- Stage all new messages to Index
- Commit non-empty Index every 60 seconds
- Commit on Dispose
.gitmodules | 3 +
lib/libgit2sharp | 1 +
src/Engine-Tests/Db4oMessageBufferTests.cs | 7 +-
src/Engine-Tests/GitMessageBufferTests.cs | 53 +++++
src/Engine-Tests/ListMessageBufferTests.cs | 5 +
src/Engine-Tests/MessageBufferTestsBase.cs | 73 +++++++
src/Engine-Tests/MessageModelTests.cs | 193 +++++++++++++++++++
src/Engine/MessageBuffers/Db4oMessageBuffer.cs | 47 +----
src/Engine/MessageBuffers/GitMessageBuffer.cs | 245 ++++++++++++++++++++++++
src/Engine/MessageBuffers/MessageBufferBase.cs | 41 ++++
src/Engine/Messages/ImageMessagePartModel.cs | 12 +-
src/Engine/Messages/MessageModel.cs | 8 +-
src/Engine/Messages/MessagePartModel.cs | 9 +-
src/Engine/Messages/MessageType.cs | 2 +
src/Engine/Messages/TextMessagePartModel.cs | 17 ++-
src/Engine/Messages/UrlMessagePartModel.cs | 12 +-
src/Engine/TextColor.cs | 8 +-
17 files changed, 688 insertions(+), 48 deletions(-)
---
diff --git a/.gitmodules b/.gitmodules
index 8790c08..9dbc624 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,3 +31,6 @@
[submodule "lib/StarkSoftProxy"]
path = lib/StarkSoftProxy
url = https://github.com/meebey/starksoftproxy.git
+[submodule "lib/libgit2sharp"]
+ path = lib/libgit2sharp
+ url = git://git.qnetp.net/libgit2sharp.git
diff --git a/lib/libgit2sharp b/lib/libgit2sharp
new file mode 160000
index 0000000..1cb40da
--- /dev/null
+++ b/lib/libgit2sharp
@@ -0,0 +1 @@
+Subproject commit 1cb40daeab62551b44a964e2dce60bad098a0477
diff --git a/src/Engine-Tests/Db4oMessageBufferTests.cs b/src/Engine-Tests/Db4oMessageBufferTests.cs
index 59b3133..4ee199a 100644
--- a/src/Engine-Tests/Db4oMessageBufferTests.cs
+++ b/src/Engine-Tests/Db4oMessageBufferTests.cs
@@ -39,6 +39,11 @@ namespace Smuxi.Engine
File.Delete(dbFile);
}
+ return OpenBuffer();
+ }
+
+ protected override IMessageBuffer OpenBuffer()
+ {
return new Db4oMessageBuffer("testuser", "testprot", "testnet", "testchat");
}
@@ -46,7 +51,7 @@ namespace Smuxi.Engine
public void Reopen()
{
Buffer.Dispose();
- Buffer = new Db4oMessageBuffer("testuser", "testprot", "testnet", "testchat");
+ OpenBuffer();
Enumerator();
}
diff --git a/src/Engine-Tests/GitMessageBufferTests.cs b/src/Engine-Tests/GitMessageBufferTests.cs
new file mode 100644
index 0000000..0e2a2e4
--- /dev/null
+++ b/src/Engine-Tests/GitMessageBufferTests.cs
@@ -0,0 +1,53 @@
+// Smuxi - Smart MUltipleXed Irc
+//
+// Copyright (c) 2012 Mirco Bauer <meebey meebey net>
+//
+// Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+using System;
+using System.IO;
+using NUnit.Framework;
+using Smuxi.Common;
+
+namespace Smuxi.Engine
+{
+ [TestFixture]
+ public class GitMessageBufferTests : MessageBufferTestsBase
+ {
+ static GitMessageBufferTests() {
+ log4net.Config.BasicConfigurator.Configure();
+ }
+
+ protected override IMessageBuffer CreateBuffer()
+ {
+ var repoPath = Path.Combine(Platform.GetBuffersPath("testuser"),
+ "testprot");
+ repoPath = Path.Combine(repoPath, "testnet");
+ repoPath = Path.Combine(repoPath, "testchat.git");
+ if (Directory.Exists(repoPath)) {
+ Directory.Delete(repoPath, true);
+ }
+
+ return OpenBuffer();
+ }
+
+ protected override IMessageBuffer OpenBuffer()
+ {
+ return new GitMessageBuffer("testuser", "testprot", "testnet", "testchat");
+ }
+
+ }
+}
diff --git a/src/Engine-Tests/ListMessageBufferTests.cs b/src/Engine-Tests/ListMessageBufferTests.cs
index 1605701..70b1c96 100644
--- a/src/Engine-Tests/ListMessageBufferTests.cs
+++ b/src/Engine-Tests/ListMessageBufferTests.cs
@@ -32,5 +32,10 @@ namespace Smuxi.Engine
{
return new ListMessageBuffer();
}
+
+ protected override IMessageBuffer OpenBuffer()
+ {
+ return CreateBuffer();
+ }
}
}
diff --git a/src/Engine-Tests/MessageBufferTestsBase.cs b/src/Engine-Tests/MessageBufferTestsBase.cs
index 9537573..408c30c 100644
--- a/src/Engine-Tests/MessageBufferTestsBase.cs
+++ b/src/Engine-Tests/MessageBufferTestsBase.cs
@@ -30,6 +30,7 @@ namespace Smuxi.Engine
protected List<MessageModel> TestMessages { get; set; }
protected abstract IMessageBuffer CreateBuffer();
+ protected abstract IMessageBuffer OpenBuffer();
[SetUp]
public void SetUp()
@@ -119,6 +120,49 @@ namespace Smuxi.Engine
}
[Test]
+ public void GetRangeBenchmarkCold()
+ {
+ var builder = new MessageBuilder();
+ builder.AppendIdendityName(
+ new ContactModel("meeebey", "meebey", "netid", "netprot")
+ );
+ builder.AppendText("solange eine message aber keine url hat ist der vorteil nur gering (wenn
ueberhaupt)");
+ var msg = builder.ToMessage();
+
+ var itemCount = 50000;
+ // generate items
+ for (int i = 0; i < itemCount; i++) {
+ Buffer.Add(new MessageModel(msg));
+ }
+ // close buffer
+ Buffer.Dispose();
+
+ int runs = 10;
+ var messageCount = 0;
+ DateTime start, stop;
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ Buffer = OpenBuffer();
+ // retrieve the newest 200 messages
+ messageCount += Buffer.GetRange(itemCount - 200, 200).Count;
+ Buffer.Dispose();
+ }
+ stop = DateTime.UtcNow;
+ Assert.AreEqual(runs * 200, messageCount);
+
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Buffer.GetRange({0}, 200): avg: {1:0.00} ms ({2:0.00} ms per item) items: {3} runs: {4}
took: {5:0.00} ms",
+ itemCount - 200,
+ total / runs,
+ total / runs / messageCount,
+ itemCount,
+ runs,
+ total
+ );
+ }
+
+ [Test]
public void Add()
{
MessageBuilder msg = new MessageBuilder();
@@ -130,6 +174,35 @@ namespace Smuxi.Engine
}
[Test]
+ public void AddBenchmark()
+ {
+ var itemCount = 50000;
+ //var itemCount = 15000;
+ DateTime start, stop;
+ start = DateTime.UtcNow;
+ for (int i = 0; i < itemCount; i++) {
+ var builder = new MessageBuilder();
+ builder.AppendIdendityName(
+ new ContactModel("meeebey", "meebey", "netid", "netprot")
+ );
+ builder.AppendText("solange eine message aber keine url hat ist der vorteil nur gering (wenn
ueberhaupt)");
+ var msg = builder.ToMessage();
+ Buffer.Add(msg);
+ }
+ // force flush / close
+ Buffer.Dispose();
+ stop = DateTime.UtcNow;
+
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Buffer.Add(msg): avg: {0:0.00} ms/msg items: {1} took: {2:0.00} ms",
+ total / itemCount,
+ itemCount,
+ total
+ );
+ }
+
+ [Test]
public void Clear()
{
Buffer.Clear();
diff --git a/src/Engine-Tests/MessageModelTests.cs b/src/Engine-Tests/MessageModelTests.cs
index 031879b..a26d536 100644
--- a/src/Engine-Tests/MessageModelTests.cs
+++ b/src/Engine-Tests/MessageModelTests.cs
@@ -28,6 +28,27 @@ namespace Smuxi.Engine
[TestFixture]
public class MessageModelTests
{
+ MessageModel SimpleMessage { get; set; }
+ MessageModel ComplexMessage { get; set; }
+
+ [TestFixtureSetUp]
+ public void SetUp()
+ {
+ var builder = new MessageBuilder();
+ builder.AppendIdendityName(
+ new ContactModel("meeebey", "meebey", "netid", "netprot")
+ );
+ builder.AppendSpace();
+ builder.AppendText("solange eine message aber keine url hat ist der vorteil nur gering (wenn
ueberhaupt)");
+ SimpleMessage = builder.ToMessage();
+
+ var topic = "Smuxi the IRC client for sophisticated users: http://smuxi.org/ | Smuxi 0.7.2.2
'Lovegood' released (2010-07-27) http://bit.ly/9nvsZF | FAQ: http://smuxi.org/faq/ | Deutsch? -> #smuxi.de |
Español? -> #smuxi.es | Smuxi @ FOSDEM 2010 talk: http://bit.ly/anHJfm";
+ var msg = new MessageModel(topic);
+ MessageParser.ParseUrls(msg);
+ msg.Compact();
+ ComplexMessage = msg;
+ }
+
[Test]
public void Equals()
{
@@ -55,6 +76,37 @@ namespace Smuxi.Engine
}
[Test]
+ public void LameCopyConstructor()
+ {
+ var copiedMsg = new MessageModel(SimpleMessage);
+
+ Assert.AreNotSame(SimpleMessage, copiedMsg);
+ Assert.IsNotNull(copiedMsg.MessageParts);
+ Assert.AreNotSame(SimpleMessage.MessageParts, copiedMsg.MessageParts);
+ Assert.AreEqual(SimpleMessage, copiedMsg);
+ }
+
+ [Test]
+ public void LameCopyConstructorBenchmark()
+ {
+ int runs = 50000;
+ DateTime start, stop;
+
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var copiedMsg = new MessageModel(SimpleMessage);
+ }
+ stop = DateTime.UtcNow;
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Ctor(): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+ }
+
+ [Test]
public void Compact()
{
var msg = new MessageModel("foo bar");
@@ -210,5 +262,146 @@ namespace Smuxi.Engine
Console.WriteLine("Compacted Parts: " + msg.MessageParts.Count);
Console.WriteLine("Compacted Size: " + stream.Length);
}
+
+ [Test]
+ public void BinarySerializeDeserializeBenchmark()
+ {
+ }
+
+ [Test]
+ public void ServiceStackJsonSerialize()
+ {
+ //ServiceStack.Text.JsConfig<TextColor>.SerializeFn = color => color.ToString();
+ ServiceStack.Text.JsConfig<MessagePartModel>.ExcludeTypeInfo = true;
+
+ ComplexMessage.TimeStamp = DateTime.Parse("2012-01-01T00:00:00Z").ToUniversalTime();
+ var json = ServiceStack.Text.JsonSerializer.SerializeToString(ComplexMessage);
+ //var json = ServiceStack.Text.TypeSerializer.SerializeAndFormat(TestMessage);
+ //Console.WriteLine(json);
+ Console.WriteLine(ServiceStack.Text.JsvFormatter.Format(json));
+ Assert.IsNotNull(json);
+ Assert.IsNotEmpty(json);
+
Assert.AreEqual(@"{""TimeStamp"":""\/Date(1325376000000)\/"",""MessageParts"":[{""Type"":""Text"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""Text"":""Smuxi
the IRC client for sophisticated users:
"",""IsHighlight"":false},{""Type"":""URL"",""Url"":""http://smuxi.org/"",""Protocol"":""Http"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""IsHighlight"":false},{""Type"":""Text"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""Text"":""
| Smuxi 0.7.2.2 'Lovegood' released (2010-07-27)
"",""IsHighlight"":false},{""Type"":""URL"",""Url"":""http://bit.ly/9nvsZF"",""Protocol"":""Http"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""IsHighlight"":false},{""Type"":""T
ext"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""Text"":""
| FAQ:
"",""IsHighlight"":false},{""Type"":""URL"",""Url"":""http://smuxi.org/faq/"",""Protocol"":""Http"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""IsHighlight"":false},{""Type"":""Text"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""Text"":""
| Deutsch? -> #smuxi.de | Español? -> #smuxi.es | Smuxi @ FOSDEM 2010 talk:
"",""IsHighlight"":false},{""Type"":""URL"",""Url"":""http://bit.ly/anHJfm"",""Protocol"":""Http"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false,""Italic"":false,""IsHighlight"":false},{""Type"":""Text"",""ForegroundColor"":{""Value"":-1},""BackgroundColor"":{""Value"":-1},""Underline"":false,""Bold"":false
,""Italic"":false,""Text"":"" "",""IsHighlight"":false}],""MessageType"":""Normal""}",
+ json);
+ }
+
+ [Test]
+ public void ServiceStackJsonSerializeBenchmark()
+ {
+ ServiceStack.Text.JsConfig<MessagePartModel>.ExcludeTypeInfo = true;
+
+ int runs = 50000;
+ DateTime start, stop;
+
+ MessageModel msg = null;
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var json = ServiceStack.Text.JsonSerializer.SerializeToString(SimpleMessage);
+ }
+ stop = DateTime.UtcNow;
+ //Assert.AreEqual(ComplexMessage, msg);
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Serialize(Simple): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var json = ServiceStack.Text.JsonSerializer.SerializeToString(ComplexMessage);
+ }
+ stop = DateTime.UtcNow;
+ //Assert.AreEqual(ComplexMessage, msg);
+ total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Serialize(Complex): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+ }
+
+ [Test]
+ public void ServiceStackJsonSerializeDeserializeBenchmark()
+ {
+ ServiceStack.Text.JsConfig<MessagePartModel>.ExcludeTypeInfo = true;
+
+ int runs = 50000;
+ DateTime start, stop;
+
+ MessageModel msg = null;
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var json = ServiceStack.Text.JsonSerializer.SerializeToString(ComplexMessage);
+ //msg = ServiceStack.Text.JsonSerializer.DeserializeFromString<MessageModel>(json);
+ }
+ stop = DateTime.UtcNow;
+ //Assert.AreEqual(ComplexMessage, msg);
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Serialize(): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+ }
+
+ [Test]
+ public void NewtonsoftJsonSerialize()
+ {
+ var serializer = new Newtonsoft.Json.JsonSerializer() {
+ DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore,
+ NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
+ };
+ var writer = new StringWriter();
+ serializer.Serialize(writer, ComplexMessage);
+ Console.WriteLine(writer.ToString());
+ }
+
+ [Test]
+ public void NewtonsoftJsonSerializeBenchmark()
+ {
+ var serializer = new Newtonsoft.Json.JsonSerializer() {
+ DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore,
+ NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
+ };
+
+ int runs = 50000;
+ DateTime start, stop;
+
+ MessageModel msg = null;
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var writer = new StringWriter();
+ serializer.Serialize(writer, SimpleMessage);
+ var json = writer.ToString();
+ }
+ stop = DateTime.UtcNow;
+ //Assert.AreEqual(ComplexMessage, msg);
+ var total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Serialize(SimpleMessage): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+
+ start = DateTime.UtcNow;
+ for (int i = 0; i < runs; i++) {
+ var writer = new StringWriter();
+ serializer.Serialize(writer, ComplexMessage);
+ }
+ stop = DateTime.UtcNow;
+ //Assert.AreEqual(ComplexMessage, msg);
+ total = (stop - start).TotalMilliseconds;
+ Console.WriteLine(
+ "Serialize(ComplexMessage): avg: {0:0.00} ms runs: {1} took: {2:0.00} ms",
+ total / runs,
+ runs,
+ total
+ );
+ }
}
}
diff --git a/src/Engine/MessageBuffers/Db4oMessageBuffer.cs b/src/Engine/MessageBuffers/Db4oMessageBuffer.cs
index 138621c..5ad3cef 100644
--- a/src/Engine/MessageBuffers/Db4oMessageBuffer.cs
+++ b/src/Engine/MessageBuffers/Db4oMessageBuffer.cs
@@ -42,7 +42,6 @@ namespace Smuxi.Engine
IObjectContainer Database { get; set; }
string DatabaseFile { get; set; }
bool IsEmptyDatabase { get; set; }
- string SessionUsername { get; set; }
bool AggressiveGC { get; set; }
#if DB4O_8_0
IEmbeddedConfiguration DatabaseConfiguration { get; set; }
@@ -73,43 +72,23 @@ namespace Smuxi.Engine
}
}
- private Db4oMessageBuffer()
- {
- FlushInterval = DefaultFlushInterval;
- }
-
public Db4oMessageBuffer(string sessionUsername, string protocol,
- string networkId, string chatId) : this()
+ string networkId, string chatId) :
+ base(sessionUsername, protocol, networkId, chatId)
{
- if (sessionUsername == null) {
- throw new ArgumentNullException("sessionUsername");
- }
- if (protocol == null) {
- throw new ArgumentNullException("protocol");
- }
- if (networkId == null) {
- throw new ArgumentNullException("networkId");
- }
- if (chatId == null) {
- throw new ArgumentNullException("chatId");
- }
-
- SessionUsername = sessionUsername;
- Protocol = protocol;
- NetworkID = networkId;
- ChatID = chatId;
-
+ FlushInterval = DefaultFlushInterval;
AggressiveGC = true;
DatabaseFile = GetDatabaseFile();
InitDatabase();
}
- public Db4oMessageBuffer(string dbPath) : this()
+ public Db4oMessageBuffer(string dbPath)
{
if (dbPath == null) {
throw new ArgumentNullException("dbPath");
}
+ FlushInterval = DefaultFlushInterval;
DatabaseFile = dbPath;
InitDatabase();
}
@@ -375,21 +354,7 @@ namespace Smuxi.Engine
string GetDatabaseFile()
{
- var dbPath = Platform.GetBuffersPath(SessionUsername);
- var protocol = Protocol.ToLower();
- var network = NetworkID.ToLower();
- dbPath = Path.Combine(dbPath, protocol);
- if (network != protocol) {
- dbPath = Path.Combine(dbPath, network);
- }
- dbPath = IOSecurity.GetFilteredPath(dbPath);
- if (!Directory.Exists(dbPath)) {
- Directory.CreateDirectory(dbPath);
- }
-
- var chatId = IOSecurity.GetFilteredFileName(ChatID.ToLower());
- dbPath = Path.Combine(dbPath, String.Format("{0}.db4o", chatId));
- return dbPath;
+ return String.Format("{0}.db4o", GetBufferPath());
}
void OpenDatabase()
diff --git a/src/Engine/MessageBuffers/GitMessageBuffer.cs b/src/Engine/MessageBuffers/GitMessageBuffer.cs
new file mode 100644
index 0000000..7debc76
--- /dev/null
+++ b/src/Engine/MessageBuffers/GitMessageBuffer.cs
@@ -0,0 +1,245 @@
+// Smuxi - Smart MUltipleXed Irc
+//
+// Copyright (c) 2012 Mirco Bauer <meebey meebey net>
+//
+// Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using ServiceStack.Text;
+using LibGit2Sharp;
+using Smuxi.Common;
+
+namespace Smuxi.Engine
+{
+ public class GitMessageBuffer : MessageBufferBase
+ {
+#if LOG4NET
+ static readonly log4net.ILog f_Logger =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+#endif
+ Int64 f_MessageNumber = -1;
+ Repository Repository { get; set; }
+ string RepositoryPath { get; set; }
+ TimeSpan CommitInterval { get; set; }
+ Timer CommitTimer { get; set; }
+ StringBuilder CommitMessage { get; set; }
+
+ Int64 MessageNumber {
+ get {
+ if (f_MessageNumber == -1) {
+ var msgNumber = 0L;
+ var reference = Repository.Refs["refs/heads/master"] as DirectReference;
+ if (reference != null) {
+ var commit = reference.Target as Commit;
+ foreach (var treeEntry in commit.Tree) {
+ var filename = treeEntry.Name;
+ var strNumber = filename.Substring(0, filename.IndexOf("."));
+ var intNumber = 0L;
+ Int64.TryParse(strNumber, out intNumber);
+ if (intNumber > msgNumber) {
+ msgNumber = intNumber;
+ }
+ }
+ }
+ f_MessageNumber = msgNumber;
+ }
+ return f_MessageNumber;
+ }
+ set {
+ f_MessageNumber = value;
+ }
+ }
+
+ static GitMessageBuffer() {
+ JsConfig<MessagePartModel>.ExcludeTypeInfo = true;
+ }
+
+ public GitMessageBuffer(string sessionUsername, string protocol,
+ string networkId, string chatId) :
+ base(sessionUsername, protocol, networkId, chatId)
+ {
+ var bufferPath = GetBufferPath();
+ RepositoryPath = bufferPath + ".git";
+ if (!Directory.Exists(RepositoryPath)) {
+ Repository.Init(RepositoryPath, false);
+ }
+ Repository = new Repository(RepositoryPath);
+
+ CommitMessage = new StringBuilder(1024);
+ CommitInterval = TimeSpan.FromMinutes(1);
+ CommitTimer = new Timer(delegate { Flush(); }, null,
+ CommitInterval, CommitInterval);
+ }
+
+ public override void Add(MessageModel msg)
+ {
+ if (msg == null) {
+ throw new ArgumentNullException("msg");
+ }
+
+ /*
+ if (MaxCapacity > 0 && Index.Count >= MaxCapacity) {
+ RemoveAt(0);
+ }
+ */
+
+ var msgFileName = String.Format("{0}.v1.json", ++MessageNumber);
+ var msgFilePath = Path.Combine(RepositoryPath, msgFileName);
+ using (var writer = File.OpenWrite(msgFilePath))
+ using (var textWriter = new StreamWriter(writer, Encoding.UTF8)) {
+ //JsonSerializer.SerializeToStream(msg, writer);
+ JsonSerializer.SerializeToWriter(msg, textWriter);
+ }
+
+ DateTime start, stop;
+ lock (Repository) {
+ start = DateTime.UtcNow;
+ var index = Repository.Index;
+ //index.Stage(msgFilePath);
+ index.AddToIndex(msgFileName);
+ stop = DateTime.UtcNow;
+ }
+ f_Logger.DebugFormat("Add(): Index.AddToIndex() took: {0:0.00} ms",
+ (stop - start).TotalMilliseconds);
+
+ File.Delete(msgFilePath);
+ CommitMessage.Append(
+ String.Format("{0}: {1}\n", msgFileName, msg.ToString())
+ );
+
+ // TODO: create tree, commit tree, repack repo reguraly (see rugged docs)
+ }
+
+ #region implemented abstract members of Smuxi.Engine.MessageBufferBase
+ public override int Count {
+ get {
+ var repo = Repository;
+ var count = repo.Index.Count;
+ var reference = repo.Refs["refs/heads/master"] as DirectReference;
+ if (reference == null) {
+ return count;
+ }
+ var commit = reference.Target as Commit;
+ if (commit == null) {
+ return count;
+ }
+ count += commit.Tree.Count;
+ return count;
+ }
+ }
+
+ public override MessageModel this[int index] {
+ get {
+ throw new NotImplementedException ();
+ }
+ set {
+ throw new NotImplementedException ();
+ }
+ }
+
+ public override void Clear ()
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool Contains (MessageModel item)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void CopyTo (MessageModel[] array, int arrayIndex)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool Remove (MessageModel item)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override System.Collections.Generic.IEnumerator<MessageModel> GetEnumerator ()
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override int IndexOf (MessageModel item)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void Insert (int index, MessageModel item)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void RemoveAt (int index)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override System.Collections.Generic.IList<MessageModel> GetRange (int offset, int limit)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void Dispose()
+ {
+ Flush();
+
+ var repo = Repository;
+ if (repo != null) {
+ Repository = null;
+ repo.Dispose();
+ }
+ }
+ #endregion
+
+ void Flush()
+ {
+ Trace.Call();
+
+ var repo = Repository;
+ if (repo == null) {
+ return;
+ }
+ lock (repo) {
+ if (repo.Index.Count == 0 || CommitMessage.Length == 0) {
+ // nothing to commit
+ return;
+ }
+
+ DateTime start, stop;
+
+ start = DateTime.UtcNow;
+ repo.Index.UpdatePhysicalIndex();
+ stop = DateTime.UtcNow;
+ f_Logger.DebugFormat("Commit(): Repository.Index.UpdatePhysicalIndex() took: {0:0.00} ms",
+ (stop - start).TotalMilliseconds);
+
+ start = DateTime.UtcNow;
+ // FIXME: CommitMessage is not thread-safe!
+ repo.Commit(CommitMessage.ToString(), false);
+ stop = DateTime.UtcNow;
+ f_Logger.DebugFormat("Commit(): Repository.Commit() took: {0:0.00} ms",
+ (stop - start).TotalMilliseconds);
+
+ CommitMessage.Clear();
+ }
+ }
+ }
+}
diff --git a/src/Engine/MessageBuffers/MessageBufferBase.cs b/src/Engine/MessageBuffers/MessageBufferBase.cs
index 4ca9793..144effb 100644
--- a/src/Engine/MessageBuffers/MessageBufferBase.cs
+++ b/src/Engine/MessageBuffers/MessageBufferBase.cs
@@ -19,6 +19,7 @@
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
+using System.IO;
using System.Collections;
using System.Collections.Generic;
using Smuxi.Common;
@@ -30,6 +31,7 @@ namespace Smuxi.Engine
protected string Protocol { get; set; }
protected string NetworkID { get; set; }
protected string ChatID { get; set; }
+ protected string SessionUsername { get; set; }
public int MaxCapacity { get; set; }
public bool IsReadOnly {
@@ -42,11 +44,50 @@ namespace Smuxi.Engine
{
}
+ protected MessageBufferBase(string sessionUsername, string protocol,
+ string networkId, string chatId)
+ {
+ if (sessionUsername == null) {
+ throw new ArgumentNullException("sessionUsername");
+ }
+ if (protocol == null) {
+ throw new ArgumentNullException("protocol");
+ }
+ if (networkId == null) {
+ throw new ArgumentNullException("networkId");
+ }
+ if (chatId == null) {
+ throw new ArgumentNullException("chatId");
+ }
+
+ SessionUsername = sessionUsername;
+ Protocol = protocol;
+ NetworkID = networkId;
+ ChatID = chatId;
+ }
+
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
+ protected string GetBufferPath()
+ {
+ var path = Platform.GetBuffersPath(SessionUsername);
+ var protocol = Protocol.ToLower();
+ var network = NetworkID.ToLower();
+ path = Path.Combine(path, protocol);
+ if (network != protocol) {
+ path = Path.Combine(path, network);
+ }
+ path = IOSecurity.GetFilteredPath(path);
+ if (!Directory.Exists(path)) {
+ Directory.CreateDirectory(path);
+ }
+ var chatId = IOSecurity.GetFilteredFileName(ChatID.ToLower());
+ return Path.Combine(path, chatId);
+ }
+
public abstract int Count { get; }
public abstract MessageModel this[int index] { get; set; }
diff --git a/src/Engine/Messages/ImageMessagePartModel.cs b/src/Engine/Messages/ImageMessagePartModel.cs
index 3e9df19..d76796a 100644
--- a/src/Engine/Messages/ImageMessagePartModel.cs
+++ b/src/Engine/Messages/ImageMessagePartModel.cs
@@ -34,6 +34,7 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
[Serializable]
+ [DataContract]
public class ImageMessagePartModel : MessagePartModel
{
#if LOG4NET
@@ -41,7 +42,15 @@ namespace Smuxi.Engine
#endif
private string f_ImageFileName;
private string f_AlternativeText;
-
+
+ [DataMember]
+ public override string Type {
+ get {
+ return "Image";
+ }
+ }
+
+ [DataMember]
public string ImageFileName {
get {
return f_ImageFileName;
@@ -51,6 +60,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public string AlternativeText {
get {
return f_AlternativeText;
diff --git a/src/Engine/Messages/MessageModel.cs b/src/Engine/Messages/MessageModel.cs
index 4fb360a..679624c 100644
--- a/src/Engine/Messages/MessageModel.cs
+++ b/src/Engine/Messages/MessageModel.cs
@@ -31,6 +31,7 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
[Serializable]
+ [DataContract]
public class MessageModel : ISerializable
{
static readonly Regex NickRegex = new Regex("^<([^ ]+)> ");
@@ -40,6 +41,7 @@ namespace Smuxi.Engine
[NonSerialized]
private bool f_IsCompactable;
+ [DataMember]
public DateTime TimeStamp {
get {
return f_TimeStamp;
@@ -48,19 +50,22 @@ namespace Smuxi.Engine
f_TimeStamp = value;
}
}
-
+
+ [DataMember]
public IList<MessagePartModel> MessageParts {
get {
return f_MessageParts;
}
}
+ [IgnoreDataMember]
public bool IsEmpty {
get {
return f_MessageParts.Count == 0;
}
}
+ [DataMember]
public MessageType MessageType {
get {
return f_MessageType;
@@ -70,6 +75,7 @@ namespace Smuxi.Engine
}
}
+ [IgnoreDataMember]
public bool IsCompactable {
get {
return f_IsCompactable;
diff --git a/src/Engine/Messages/MessagePartModel.cs b/src/Engine/Messages/MessagePartModel.cs
index 85861e5..7f91f8d 100644
--- a/src/Engine/Messages/MessagePartModel.cs
+++ b/src/Engine/Messages/MessagePartModel.cs
@@ -33,10 +33,17 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
[Serializable]
+ [DataContract]
public abstract class MessagePartModel : ISerializable
{
private bool f_IsHighlight;
+ [DataMember]
+ public abstract string Type {
+ get;
+ }
+
+ [DataMember]
public bool IsHighlight {
get {
return f_IsHighlight;
@@ -45,7 +52,7 @@ namespace Smuxi.Engine
f_IsHighlight = value;
}
}
-
+
protected MessagePartModel()
{
}
diff --git a/src/Engine/Messages/MessageType.cs b/src/Engine/Messages/MessageType.cs
index 41ad96e..14e09dc 100644
--- a/src/Engine/Messages/MessageType.cs
+++ b/src/Engine/Messages/MessageType.cs
@@ -27,9 +27,11 @@
*/
using System;
+using System.Runtime.Serialization;
namespace Smuxi.Engine
{
+ [DataContract]
public enum MessageType
{
Normal,
diff --git a/src/Engine/Messages/TextMessagePartModel.cs b/src/Engine/Messages/TextMessagePartModel.cs
index 85c9329..131b50f 100644
--- a/src/Engine/Messages/TextMessagePartModel.cs
+++ b/src/Engine/Messages/TextMessagePartModel.cs
@@ -33,6 +33,7 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
[Serializable]
+ [DataContract]
public class TextMessagePartModel : MessagePartModel
{
private TextColor f_ForegroundColor;
@@ -41,7 +42,15 @@ namespace Smuxi.Engine
private bool f_Bold;
private bool f_Italic;
private string f_Text;
-
+
+ [DataMember]
+ public override string Type {
+ get {
+ return "Text";
+ }
+ }
+
+ [DataMember]
public TextColor ForegroundColor {
get {
return f_ForegroundColor;
@@ -54,6 +63,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public TextColor BackgroundColor {
get {
return f_BackgroundColor;
@@ -66,6 +76,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public bool Underline {
get {
return f_Underline;
@@ -75,6 +86,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public bool Bold {
get {
return f_Bold;
@@ -84,6 +96,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public bool Italic {
get {
return f_Italic;
@@ -93,6 +106,7 @@ namespace Smuxi.Engine
}
}
+ [DataMember]
public string Text {
get {
return f_Text;
@@ -102,6 +116,7 @@ namespace Smuxi.Engine
}
}
+ [IgnoreDataMember]
public int Length {
get {
if (f_Text == null) {
diff --git a/src/Engine/Messages/UrlMessagePartModel.cs b/src/Engine/Messages/UrlMessagePartModel.cs
index e738e2d..0a787ee 100644
--- a/src/Engine/Messages/UrlMessagePartModel.cs
+++ b/src/Engine/Messages/UrlMessagePartModel.cs
@@ -33,6 +33,7 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
+ [DataContract]
public enum UrlProtocol {
None,
Unknown,
@@ -46,13 +47,22 @@ namespace Smuxi.Engine
}
[Serializable]
+ [DataContract]
public class UrlMessagePartModel : TextMessagePartModel
{
#if LOG4NET
private static readonly log4net.ILog _Logger =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endif
private string _Url;
-
+
+ [DataMember]
+ public override string Type {
+ get {
+ return "URL";
+ }
+ }
+
+ [DataMember]
public string Url {
get {
if (_Url == null) {
diff --git a/src/Engine/TextColor.cs b/src/Engine/TextColor.cs
index 2f89a27..600791e 100644
--- a/src/Engine/TextColor.cs
+++ b/src/Engine/TextColor.cs
@@ -34,6 +34,7 @@ using Smuxi.Common;
namespace Smuxi.Engine
{
[Serializable]
+ [DataContract]
public class TextColor : ISerializable
{
public static readonly TextColor None = new TextColor();
@@ -42,7 +43,8 @@ namespace Smuxi.Engine
public static readonly TextColor Grey = new TextColor(128, 128, 128);
private int f_Value;
-
+
+ [DataMember]
public int Value {
get {
return f_Value;
@@ -52,24 +54,28 @@ namespace Smuxi.Engine
}
}
+ [IgnoreDataMember]
public string HexCode {
get {
return f_Value.ToString("X6");
}
}
+ [IgnoreDataMember]
public byte Red {
get {
return (byte) ((f_Value & 0xFF0000) >> 16);
}
}
+ [IgnoreDataMember]
public byte Green {
get {
return (byte) ((f_Value & 0xFF00) >> 8);
}
}
+ [IgnoreDataMember]
public byte Blue {
get {
return (byte) (f_Value & 0xFF);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]