I have been working with HIDAPI with C and trying to get Mono to communicate with the HIDAPI using interop. I have done a lot of searching and have not been able to find anyone who has gotten HIDAPI to work with Mono on OS X.
Does anyone know if I can redirect the output from a HID device using HIDAPI to a local virtual serial port and then have Mono just read from the serial port?
Another option, would anyone know if I could use something like Arduino leonardo or Circuits#Home USB Host Shield?
At least until I can sort out PInvoke on Mono.
Thanks
I found and adapted this code from elsewhere, though I can't for the life of me find the source. If someone knows, please let me know so I can properly attribute it and link to it. It's been working well for me on both Windows and OS X. You do have to build hidapi for each platform, obviously.
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace HidApiCommunicationLayer
{
internal class HidApiInteropCommLayer
{
#region Interop
#if WINDOWS
private const string HIDAPI_DLL = "hidapi.dll";
#else
private const string HIDAPI_DLL = "hidapi.dylib";
#endif
protected IntPtr _device;
private Object _locker = new object();
public bool IsOpen()
{
return _device != IntPtr.Zero;
}
public void Open(ushort vid, ushort hid, string serial)
{
if (_device != IntPtr.Zero) throw new Exception("a device is already opened; close it first.");
IntPtr ret = hid_open(vid, hid, serial);
_device = ret;
//if (_device != IntPtr.Zero)
// hid_set_nonblocking(_device, true);
}
public int Read(byte[] buffer, int length)
{
lock (_locker)
{
AssertValidDev();
int ret = hid_read_timeout(_device, buffer, (uint)length, 1);
if (ret < 0)
throw new Exception("Failed to Read.");
return ret;
}
}
public void Close()
{
AssertValidDev();
hid_close(_device);
_device = IntPtr.Zero;
}
public int ExitHidAPI()
{
return hid_exit();
}
public String GetProductString()
{
AssertValidDev();
byte[] buf = new byte[1000];
int ret = HidApiInteropCommLayer.hid_get_product_string(_device, buf, (uint)(buf.Length / 4) - 1);
if (ret < 0)
throw new Exception("failed to receive product string");
return EncodeBuffer(buf);
}
public String GetManufacturerString()
{
AssertValidDev();
byte[] buf = new byte[1000];
int ret = HidApiInteropCommLayer.hid_get_manufacturer_string(_device, buf, (uint)(buf.Length / 4) - 1);
if (ret < 0)
throw new Exception("failed to receive manufacturer string");
return EncodeBuffer(buf);
}
public int GetFeatureReport(byte[] buffer, int length)
{
AssertValidDev();
int ret = hid_get_feature_report(_device, buffer, (uint)length);
if (ret < 0)
throw new Exception("failed to get feature report");
return ret;
}
public int SendFeatureReport(byte[] buffer)
{
int ret = hid_send_feature_report(_device, buffer, (uint)buffer.Length);
//if (ret < 0)
// throw new Exception ("failed to send feature report");
return ret;
}
public int Write(byte[] buffer)
{
lock (_locker)
{
AssertValidDev();
int ret = hid_write(_device, buffer, HID_MAX_PACKET_SIZE + 1);
//if (ret < 0)
// Custom logging
return ret;
}
}
public String Error()
{
AssertValidDev();
IntPtr ret = hid_error(_device);
return Marshal.PtrToStringAuto(ret);
}
public string GetIndexedString(int index)
{
AssertValidDev();
byte[] buf = new byte[1000];
int ret = HidApiInteropCommLayer.hid_get_indexed_string(_device, index, buf, (uint)(buf.Length / 4) - 1);
if (ret < 0)
throw new Exception("failed to receive indexed string");
return EncodeBuffer(buf);
}
public string GetSerialNumberString()
{
AssertValidDev();
byte[] buf = new byte[1000];
int ret = HidApiInteropCommLayer.hid_get_serial_number_string(_device, buf, (uint)(buf.Length / 4) - 1);
if (ret < 0)
throw new Exception("failed to receive serial number string");
return EncodeBuffer(buf);
}
private string EncodeBuffer(byte[] buffer)
{
return Encoding.Unicode.GetString(buffer).Trim('\0');
}
private void AssertValidDev()
{
if (_device == IntPtr.Zero) throw new Exception("No device opened");
}
#region DllImports
[DllImport(HIDAPI_DLL)]
private static extern int hid_read(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);
[DllImport(HIDAPI_DLL)]
private static extern int hid_read_timeout(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length, int timeout);
[DllImport(HIDAPI_DLL)]
private static extern IntPtr hid_open(ushort vid, ushort pid, [MarshalAs(UnmanagedType.LPWStr)] string serial);
[DllImport(HIDAPI_DLL)]
private static extern void hid_close(IntPtr device);
[DllImport(HIDAPI_DLL)]
private static extern int hid_init();
[DllImport(HIDAPI_DLL)]
private static extern int hid_exit();
[DllImport(HIDAPI_DLL)]
private static extern int hid_get_product_string(IntPtr device, [Out] byte[] _string, uint length);
[DllImport(HIDAPI_DLL)]
private static extern int hid_get_manufacturer_string(IntPtr device, [Out] byte[] _string, uint length);
[DllImport(HIDAPI_DLL)]
private static extern int hid_get_feature_report(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);
[DllImport(HIDAPI_DLL)]
private static extern int hid_get_serial_number_string(IntPtr device, [Out] byte[] serial, uint maxlen);
[DllImport(HIDAPI_DLL)]
private static extern int hid_get_indexed_string(IntPtr device, int string_index, [Out] byte[] _string, uint maxlen);
[DllImport(HIDAPI_DLL)]
private static extern IntPtr hid_error(IntPtr device);
[DllImport(HIDAPI_DLL)]
private static extern int hid_send_feature_report(IntPtr device, [In, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);
[DllImport(HIDAPI_DLL)]
private static extern int hid_set_nonblocking(IntPtr device, [In, MarshalAs(UnmanagedType.SysInt)] bool nonblock);
[DllImport(HIDAPI_DLL)]
private static extern int hid_write(IntPtr device, [In, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);
[DllImport(HIDAPI_DLL)]
private static extern IntPtr hid_open_path([In, MarshalAs(UnmanagedType.LPStr)] string path);
#endregion DllImports
#endregion Interop
#region Constructors
public static HidApiInteropCommLayer GetDevice(ushort vid, ushort pid)
{
try
{
HidApiInteropCommLayer layer = new HidApiInteropCommLayer();
layer.Open(vid, pid, null);
return layer._device == IntPtr.Zero ? null : layer;
}
catch (System.BadImageFormatException fx)
{
//Custom logging
return null;
}
catch (Exception ex)
{
//Custom logging
return null;
}
}
#endregion Constructors
private const int HID_MAX_PACKET_SIZE = 1024;
#region ICommunicationLayer
public void Init()
{
try
{
if (IsOpen())
{
ContinueReadProcessing = true;
ReadThread = new Thread(new ThreadStart(ReadLoop));
ReadThread.Name = "HidApiReadThread";
ReadThread.Start();
}
else
{
Disconnect();
}
}
catch (Exception ex)
{
//Custom logging
throw;
}
}
public bool SendData(byte[] data)
{
try
{
MemoryStream stream = new MemoryStream(HID_MAX_PACKET_SIZE + 1);
stream.WriteByte(0);
stream.Write(data, 0, HID_MAX_PACKET_SIZE);
int ret = Write(stream.ToArray());
if (ret >= 0)
return true;
else
{
return false;
}
}
catch (Exception ex)
{
//Custom logging
return false;
}
}
public event EventHandler<DataEventArgs> DataReceived;
public event EventHandler Disconnected;
public void Start()
{
ContinueReadProcessing = true;
}
public void Stop()
{
Disconnect();
}
#endregion ICommunicationLayer
private Thread ReadThread = null;
protected volatile bool ContinueReadProcessing = true;
private void ReadLoop()
{
var culture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
while (ContinueReadProcessing)
{
try
{
byte[] report = new byte[HID_MAX_PACKET_SIZE];
var result = Read(report, HID_MAX_PACKET_SIZE);
if (result > 0)
{
DataReceived(this, new DataEventArgs(report));
}
else if (result < 0)
{
Disconnect();
}
}
catch (Exception ex)
{
Disconnect();
}
Thread.Sleep(1);
}
}
private void Disconnect()
{
ContinueReadProcessing = false;
Disconnected(this, EventArgs.Empty);
}
#region IDisposable Members
public void Dispose()
{
ContinueReadProcessing = false;
ReadThread.Join(500);
if (ReadThread.IsAlive)
{
ReadThread.Abort();
}
if (IsOpen())
Close();
int res = ExitHidAPI();
}
#endregion IDisposable Members
}
internal class Utf32Marshaler : ICustomMarshaler
{
private static Utf32Marshaler instance = new Utf32Marshaler();
public static ICustomMarshaler GetInstance(string s)
{
return instance;
}
public void CleanUpManagedData(object o)
{
}
public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
//UnixMarshal.FreeHeap(pNativeData);
}
public int GetNativeDataSize()
{
return IntPtr.Size;
}
public IntPtr MarshalManagedToNative(object obj)
{
string s = obj as string;
if (s == null)
return IntPtr.Zero;
return Marshal.StringToHGlobalAuto(s);
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
return Marshal.PtrToStringAuto(pNativeData);
}
}
public class DataEventArgs : EventArgs
{
public DataEventArgs(byte[] data)
{
Data = data;
}
public byte[] Data { get; private set; }
}
}
Arduino Leonardo can't act as a USB Host.
In principle though, yes, you could make a device act as a CDC Serial Port on the device side, and have it act as Host USB to a HID device.
Alternatively, you could skip the HIDAPI step entirely and use HidSharp. :) It'll P/Invoke directly into the MacOS X native APIs.
Hope this helps
James
Related
I have the following piece of code from a regular mvc app which uploads a file by impersonating a user
public class PublicController : Controller
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsImpersonationContext impersonationContext;
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
public SomeActionMethod(model containing file)
{
if (ImpersonateValidUser(userName: "someuserwithpowertoupload", domain: "", password: "somepassword"))
{
path = "Somepath";
file.SaveAs(path);
}
}
private bool ImpersonateValidUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, impersonationLevel: 2, hNewToken: ref tokenDuplicate) != 0)
{
using (tempWindowsIdentity = new WindowsIdentity(tokenDuplicate))
{
this.impersonationContext = tempWindowsIdentity.Impersonate();
if (this.impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
return false;
}
Problem here is that WindowsImpersonationContext doesnt exist in .net core. Can anyone provide a code snippet which impersonates a user? Microsoft docs here https://learn.microsoft.com/en-us/dotnet/standard/security/impersonating-and-reverting isnt very helpful.
Thank you.
From the docs:
ASP.NET Core doesn't implement impersonation. Apps run with the app's identity for all requests, using app pool or process identity. If the app should perform an action on behalf of a user, use WindowsIdentity.RunImpersonated or RunImpersonatedAsync in a terminal inline middleware in Startup.Configure.
app.Run(async (context) =>
{
try
{
var user = (WindowsIdentity)context.User.Identity;
await context.Response
.WriteAsync($"User: {user.Name}\tState: {user.ImpersonationLevel}\n");
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
var impersonatedUser = WindowsIdentity.GetCurrent();
var message =
$"User: {impersonatedUser.Name}\t" +
$"State: {impersonatedUser.ImpersonationLevel}";
var bytes = Encoding.UTF8.GetBytes(message);
context.Response.Body.Write(bytes, 0, bytes.Length);
});
}
catch (Exception e)
{
await context.Response.WriteAsync(e.ToString());
}
});
I want to collect coin on a touching the coin object.So i did like this.
if (Gdx.input.justTouched()) {
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
game.camera.unproject(touchPos);
for (int i = 0; i < coins.length; i++) {
Rectangle textureBounds = new Rectangle(coins[i].getX(), coins[i].getY(), coins[i].getWidth(),coins[i].getHeight());
if (textureBounds.contains(touchPos.x, touchPos.y) && !coins[i].isBreakBool() && coins[i].isCoinVisible()) {
//after touch something happens
}
}
}
}
I increments the score and everything is proper except the touch.I want to make the coin invisible/moving immediately after touching down.This is not happening with justTouched().
So I want to use input processor touch down event for this.
I have my input processor class with touch down event like this.
public class MyInputProcessor implements InputProcessor,GestureListener {
public static boolean isTouchDown=false;
public static boolean isTouchUp=false;
public static boolean isTap=false;
public static boolean isLongPress=false;
public static boolean isFling=false;
public static boolean isSwipeDown=false;
public static boolean isSwipeUp=false;
public static boolean isSwipeLeft=false;
public static boolean isSwipeRight=false;
public static boolean isZoomed=false;
public static float zoomInitDist=0;
public static float zoomDist=0;
public MyInputProcessor() {
// TODO Auto-generated constructor stub
System.out.println("My Input Processor Created..");
}
public InputMultiplexer returnInput() {
// TODO Auto-generated method stub
InputMultiplexer im = new InputMultiplexer();
GestureDetector gd = new GestureDetector(this);
im.addProcessor(gd);
im.addProcessor(this);
return im;
}
#Override
public boolean touchDown(float x, float y, int pointer, int button) {
// TODO Auto-generated method stub
this.isTouchDown=true;
return true;
}
#Override
public boolean tap(float x, float y, int count, int button) {
// TODO Auto-generated method stub
this.isTap=true;
return true;
}
#Override
public boolean longPress(float x, float y) {
// TODO Auto-generated method stub
this.isLongPress=true;
return true;
}
#Override
public boolean fling(float velocityX, float velocityY, int button) {
// TODO Auto-generated method stub
if(Math.abs(velocityX)>Math.abs(velocityY)){
if(velocityX>0){
this.isSwipeRight=true;
}else{
this.isSwipeLeft=true;
}
}else{
if(velocityY>0){
this.isSwipeDown=true;
}else{
this.isSwipeUp=true;
}
}
return true;
}
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean panStop(float x, float y, int pointer, int button) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean zoom(float initialDistance, float distance) {
// TODO Auto-generated method stub
this.isZoomed=true;
this.zoomInitDist=initialDistance;
this.zoomDist=distance;
return true;
}
#Override
public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) {
// TODO Auto-generated method stub
return true;
}
#Override
public void pinchStop() {
// TODO Auto-generated method stub
}
#Override
public boolean keyDown(int keycode) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean keyUp(int keycode) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean keyTyped(char character) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean mouseMoved(int screenX, int screenY) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean scrolled(int amount) {
// TODO Auto-generated method stub
return true;
}
}
Inside gameScreen class,
MyInputProcessor myInputProcessor = new MyInputProcessor();
InputMultiplexer im = myInputProcessor.returnInput(stage);
Gdx.input.setInputProcessor(im);
But I am confused of how to get this touch position on touchdown event of input processor.
Your MyInputProcessor should be like this.
public class MyInputProcessor implements InputProcessor {
Vector3 touchPos;
GameScreen gameScreen;
public static InputMultiplexer getMux(GameScreen gameScreen){
InputMultiplexer im = new InputMultiplexer();
im.addProcessor(gameScreen.stage);
im.addProcessor(new MyInputProcessor(gameScreen));
return im;
}
public MyInputProcessor(GameScreen gameScreen){
touchPos=new Vector3();
this.gameScreen=gameScreen; // keep reference to access data member of GameScreen
}
#Override
public boolean keyDown(int keycode) {
return false;
}
#Override
public boolean keyUp(int keycode) {
return false;
}
#Override
public boolean keyTyped(char character) {
return false;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
touchPos.set(screenX,screenY,0);
gameScreen.game.camera.unproject(touchPos);
Coin coins[]=gamescreen.coins;
for (int i = 0; i < coins.length; i++) {
Rectangle textureBounds = new Rectangle(coins[i].getX(), coins[i].getY(), coins[i].getWidth(),coins[i].getHeight());
if (textureBounds.contains(touchPos.x, touchPos.y) && !coins[i].isBreakBool() && coins[i].isCoinVisible()) {
//after touch something happens
}
}
return false;
}
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
return false;
}
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
return false;
}
#Override
public boolean mouseMoved(int screenX, int screenY) {
return false;
}
#Override
public boolean scrolled(int amount) {
return false;
}
}
Set it as InputProcessor inside your GameScreen class.
Gdx.input.setInputProcessor(MyInputProcessor.getMux(this));
I'm trying to deserialize JSON from Reddit that you can obtain by appending .json to the url. An example would be:
http://www.reddit.com/r/pics/comments/1wvx52/.json?sort=top
However, I am getting the error message:
Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 9765
At line 1 column 9765 in the json there is the following code: "replies": "", whereas normally this would contain an object like this: replies: {
kind: "Listing",
data: {}
},
Does this mean that the json is a String when there is no data, but an object otherwise? How can I deserialize with gson properly if this is the case? I've included my classes below. I still need to figure out how to handle the json starting off with an array of basically two different objects (the first listing in the json is describing the link, while the second listing is describing the comments), but I'll cross that bridge when I get there. Thanks in advance if anyone can shed some light on this issue.
Main Class
public static void main(String[] args)
{
ArrayList<CommentsResults> commentsResults = new ArrayList<CommentsResults>();
String commentsURL = "http://www.reddit.com/r/pics/comments/1wvx52/.json?sort=top";
URL url = null;
try
{
url = new URL(commentsURL);
} catch (MalformedURLException ex)
{
System.out.println(ex.getMessage());
}
try
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openStream()));
String jsonText = readAll(bufferedReader);
Gson gson = new GsonBuilder().create();
commentsResults = gson.fromJson(jsonText, new TypeToken<ArrayList<CommentsResults>>(){}.getType());
} catch (IOException ex)
{
System.out.println(ex.getMessage());
}
}
private static String readAll(Reader reader) throws IOException
{
StringBuilder stringBuilder = new StringBuilder();
int cp;
while ((cp = reader.read()) != -1)
{
stringBuilder.append((char) cp);
}
return stringBuilder.toString();
}
CommentsResults Class
public class CommentsResults {
private String kind;
private CommentsData data;
public CommentsResults()
{
}
public CommentsResults(String kind, CommentsData data)
{
this.kind = kind;
this.data = data;
}
public String getKind()
{
return kind;
}
public CommentsData getData()
{
return data;
}
public void setKind(String kind)
{
this.kind = kind;
}
public void setData(CommentsData data)
{
this.data = data;
}
}
CommentsData Class
private String modhash;
private List <CommentsChild> children;
public CommentsData()
{
}
public CommentsData(String modhash, List<CommentsChild> children)
{
this.modhash = modhash;
this.children = children;
}
public String getModhash()
{
return modhash;
}
public List<CommentsChild> getChildren()
{
return children;
}
public void setModhash(String modhash)
{
this.modhash = modhash;
}
public void setChildren(List<CommentsChild> children)
{
this.children = children;
}
CommentsChild Class
private String kind;
private Comment data;
public CommentsChild()
{
}
public CommentsChild(String kind, Comment comment)
{
this.kind = kind;
this.data = comment;
}
public String getKind()
{
return kind;
}
public Comment getComment()
{
return data;
}
public void setKind(String kind)
{
this.kind = kind;
}
public void setComment(Comment comment)
{
this.data = comment;
}
Comment Class
public class Comment {
private CommentsResults replies;
private String id;
private int gilded;
private String author;
private String parent_id;
private String body;
private int downs;
private String link_id;
private boolean score_hidden;
private int created_utc;
private String distinguished;
public Comment()
{
}
public Comment(CommentsResults replies, String id, int gilded, String author, String parent_id, String body, int downs, String link_id, boolean score_hidden, int created_utc, String distinguished)
{
this.replies = replies;
this.id = id;
this.gilded = gilded;
this.author = author;
this.parent_id = parent_id;
this.body = body;
this.downs = downs;
this.link_id = link_id;
this.score_hidden = score_hidden;
this.created_utc = created_utc;
this.distinguished = distinguished;
}
public CommentsResults getReplies()
{
return replies;
}
public String getId()
{
return id;
}
public int getGilded()
{
return gilded;
}
public String getAuthor()
{
return author;
}
public String getParent_id()
{
return parent_id;
}
public String getBody()
{
return body;
}
public int getDowns()
{
return downs;
}
public String getLink_id()
{
return link_id;
}
public boolean isScore_hidden()
{
return score_hidden;
}
public int getCreated_utc()
{
return created_utc;
}
public String getDistinguished()
{
return distinguished;
}
public void setReplies(CommentsResults replies)
{
this.replies = replies;
}
public void setId(String id)
{
this.id = id;
}
public void setGilded(int gilded)
{
this.gilded = gilded;
}
public void setAuthor(String author)
{
this.author = author;
}
public void setParent_id(String parent_id)
{
this.parent_id = parent_id;
}
public void setBody(String body)
{
this.body = body;
}
public void setDowns(int downs)
{
this.downs = downs;
}
public void setLink_id(String link_id)
{
this.link_id = link_id;
}
public void setScore_hidden(boolean score_hidden)
{
this.score_hidden = score_hidden;
}
public void setCreated_utc(int created_utc)
{
this.created_utc = created_utc;
}
public void setDistinguished(String distinguished)
{
this.distinguished = distinguished;
}
}
So in the off chance this helps somebody (which seems dubious at this point) I decided to parse the Json manually using recursion. Here's how I did it:
public static void getCommentsOnLink()
{
String commentsURL= "http://www.reddit.com/r/pics/comments/1wvx52/.json?sort=top";
URL url = null;
try
{
url = new URL(commentsURL);
} catch (MalformedURLException ex)
{
System.out.println(ex.getMessage());
}
String JsonText = readCommentJsonFromURL(url);
RedditCommentResults redditCommentResults = getCommentResults(JsonText);
}
private static String readCommentJsonFromURL(URL url)
{
String JSONText = null;
try
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openStream()));
JSONText = readAll(bufferedReader);
} catch (IOException ex)
{
System.out.println(ex.getMessage());
}
return JSONText;
}
private static String readAll(Reader reader) throws IOException
{
StringBuilder stringBuilder = new StringBuilder();
int cp;
while ((cp = reader.read()) != -1)
{
stringBuilder.append((char) cp);
}
return stringBuilder.toString();
}
private static RedditCommentResults getCommentResults(String JsonText)
{
JsonParser parser = new JsonParser();
JsonArray completeJson = (JsonArray) parser.parse(JsonText);
//get link and comment object from the array containing an object for each
JsonObject linkParentJson = (JsonObject) completeJson.get(0);
JsonObject commentParentJson = (JsonObject) completeJson.get(1);
//use automatic deserializer for redditLink
JsonObject linkListingDataJson = linkParentJson.getAsJsonObject("data");
JsonObject linkChildrenJson = linkListingDataJson.getAsJsonArray("children").get(0).getAsJsonObject();
JsonObject linkDataJson = linkChildrenJson.getAsJsonObject("data");
Link commentLink = gson.fromJson(linkDataJson, Link.class);
RedditLink redditCommentLink = new RedditLink(commentLink);
//parse comments manually
JsonObject commentDataJson = commentParentJson.getAsJsonObject("data");
JsonArray commentChildrenJson = commentDataJson.getAsJsonArray("children");
//get all of the comments from the JsonArray
ArrayList<RedditComment> redditComments = getNestedComments(commentChildrenJson);
RedditCommentResults redditCommentResults = new RedditCommentResults(redditComments, redditCommentLink);
return redditCommentResults;
}
private static ArrayList<RedditComment> getNestedComments(JsonArray commentWrapperJsonArray)
{
ArrayList<RedditComment> redditComments = new ArrayList();
for (JsonElement commentWrapperJson : commentWrapperJsonArray)
{
//cast Element to Object so we can search for the primitive "kind". Finally we get it as a String
String kind = commentWrapperJson.getAsJsonObject().getAsJsonPrimitive("kind").getAsString();
//if the comment is of type t1 meaning it is a comment and not a "more" (a "more" is a comment which
//hasn't been loaded yet because it does not have a great deal of upvotes relative to other comments)
if (kind.equals("t1"))
{
JsonObject commentJson = commentWrapperJson.getAsJsonObject().getAsJsonObject("data");
Comment comment = gson.fromJson(commentJson, Comment.class);
RedditComment redditComment = new RedditComment(comment);
JsonElement repliesJson = commentJson.get("replies");
//if the reply is not equal to an empty String (i.e. if there is at least one reply)
if (!repliesJson.isJsonPrimitive())
{
JsonObject dataJson = repliesJson.getAsJsonObject().getAsJsonObject("data");
JsonArray childrenJson = dataJson.getAsJsonArray("children");
ArrayList<RedditComment> nestedComments = getNestedComments(childrenJson);
redditComment.setReplies(nestedComments);
}
redditComments.add(redditComment);
}
}
return redditComments;
}
You have to remove the private CommentsResults replies; from Comment Class and compose replies in CommentsChild class. According to Json fomate you model is
CommentResult -
CommentsData --
List <CommentsChild> children---
CommentsResults replies
recursion/ repeation of comment result
public class CommentsChild {
private String kind;
private Comment data;
//
private CommentsResults replies;
}
With the help of this question and this post I've managed to get WebHttpBinding to work with compression (by means of copy-pasting the code). When pointing the browser to my service method, it downloads a file which I can rename to .zip and decompress, so the compression part works. But I'm not able to use Json instead of XML. When I add the webget attribute to the method I just get "[Fiddler] ReadResponse() failed: The server did not return a response for this request."
Also the GZipMessageEncoder.WriteMessage isn't even called. What do I need to change in order to get this working with Json ?
Thanks.
From the post on MSDN, you'd also need to override the MessageEncoder.IsContentTypeSupported to make sure that the gzip encoder also accepts JSON.
The code below has the modified version of that code. I also added a message inspector to add a Content-Encoding header, which will allow the browsers to understand the data as-is.
public class StackOverflow_14602036
{
[DataContract]
public class MyDC
{
[DataMember]
public string str;
[DataMember]
public int[] intArray;
public static MyDC CreateLargeInstance(int size)
{
Random rndGen = new Random(1);
StringBuilder sb = new StringBuilder();
MyDC result = new MyDC();
for (int i = 0; i < size; i++)
{
sb.Append((char)rndGen.Next('a', 'z'));
}
result.str = sb.ToString();
result.intArray = new int[size];
for (int i = 0; i < size; i++)
{
result.intArray[i] = rndGen.Next();
}
return result;
}
}
[ServiceContract]
public interface ITest
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
MyDC GetLargeData(int size);
}
public class Service : ITest
{
public MyDC GetLargeData(int size)
{
return MyDC.CreateLargeInstance(size);
}
}
#region Gzip Encoder Sample
//This class is used to create the custom encoder (GZipMessageEncoder)
internal class GZipMessageEncoderFactory : MessageEncoderFactory
{
MessageEncoder encoder;
//The GZip encoder wraps an inner encoder
//We require a factory to be passed in that will create this inner encoder
public GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory)
{
if (messageEncoderFactory == null)
throw new ArgumentNullException("messageEncoderFactory", "A valid message encoder factory must be passed to the GZipEncoder");
encoder = new GZipMessageEncoder(messageEncoderFactory.Encoder);
}
//The service framework uses this property to obtain an encoder from this encoder factory
public override MessageEncoder Encoder
{
get { return encoder; }
}
public override MessageVersion MessageVersion
{
get { return encoder.MessageVersion; }
}
//This is the actual GZip encoder
class GZipMessageEncoder : MessageEncoder
{
static string GZipContentType = "application/x-gzip";
//This implementation wraps an inner encoder that actually converts a WCF Message
//into textual XML, binary XML or some other format. This implementation then compresses the results.
//The opposite happens when reading messages.
//This member stores this inner encoder.
MessageEncoder innerEncoder;
//We require an inner encoder to be supplied (see comment above)
internal GZipMessageEncoder(MessageEncoder messageEncoder)
: base()
{
if (messageEncoder == null)
throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder");
innerEncoder = messageEncoder;
}
//public override string CharSet
//{
// get { return ""; }
//}
public override string ContentType
{
get { return GZipContentType; }
}
public override string MediaType
{
get { return GZipContentType; }
}
//SOAP version to use - we delegate to the inner encoder for this
public override MessageVersion MessageVersion
{
get { return innerEncoder.MessageVersion; }
}
public override bool IsContentTypeSupported(string contentType)
{
return this.innerEncoder.IsContentTypeSupported(contentType);
}
//Helper method to compress an array of bytes
static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
{
MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(buffer.Array, 0, messageOffset);
using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
{
gzStream.Write(buffer.Array, messageOffset, buffer.Count);
}
byte[] compressedBytes = memoryStream.ToArray();
byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);
Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, compressedBytes.Length - messageOffset);
return byteArray;
}
//Helper method to decompress an array of bytes
static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
{
MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count - buffer.Offset);
MemoryStream decompressedStream = new MemoryStream();
int totalRead = 0;
int blockSize = 1024;
byte[] tempBuffer = bufferManager.TakeBuffer(blockSize);
using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
while (true)
{
int bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
if (bytesRead == 0)
break;
decompressedStream.Write(tempBuffer, 0, bytesRead);
totalRead += bytesRead;
}
}
bufferManager.ReturnBuffer(tempBuffer);
byte[] decompressedBytes = decompressedStream.ToArray();
byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset);
Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
return byteArray;
}
//One of the two main entry points into the encoder. Called by WCF to decode a buffered byte array into a Message.
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
//Decompress the buffer
ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager);
//Use the inner encoder to decode the decompressed buffer
Message returnMessage = innerEncoder.ReadMessage(decompressedBuffer, bufferManager);
returnMessage.Properties.Encoder = this;
return returnMessage;
}
//One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array.
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
//Use the inner encoder to encode a Message into a buffered byte array
ArraySegment<byte> buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
//Compress the resulting byte array
return CompressBuffer(buffer, bufferManager, messageOffset);
}
public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType)
{
GZipStream gzStream = new GZipStream(stream, CompressionMode.Decompress, true);
return innerEncoder.ReadMessage(gzStream, maxSizeOfHeaders);
}
public override void WriteMessage(Message message, System.IO.Stream stream)
{
using (GZipStream gzStream = new GZipStream(stream, CompressionMode.Compress, true))
{
innerEncoder.WriteMessage(message, gzStream);
}
// innerEncoder.WriteMessage(message, gzStream) depends on that it can flush data by flushing
// the stream passed in, but the implementation of GZipStream.Flush will not flush underlying
// stream, so we need to flush here.
stream.Flush();
}
}
}
// This is constants for GZip message encoding policy.
static class GZipMessageEncodingPolicyConstants
{
public const string GZipEncodingName = "GZipEncoding";
public const string GZipEncodingNamespace = "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1";
public const string GZipEncodingPrefix = "gzip";
}
//This is the binding element that, when plugged into a custom binding, will enable the GZip encoder
public sealed class GZipMessageEncodingBindingElement
: MessageEncodingBindingElement //BindingElement
, IPolicyExportExtension
{
//We will use an inner binding element to store information required for the inner encoder
MessageEncodingBindingElement innerBindingElement;
//By default, use the default text encoder as the inner encoder
public GZipMessageEncodingBindingElement()
: this(new TextMessageEncodingBindingElement()) { }
public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
{
this.innerBindingElement = messageEncoderBindingElement;
}
public MessageEncodingBindingElement InnerMessageEncodingBindingElement
{
get { return innerBindingElement; }
set { innerBindingElement = value; }
}
//Main entry point into the encoder binding element. Called by WCF to get the factory that will create the
//message encoder
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
}
public override MessageVersion MessageVersion
{
get { return innerBindingElement.MessageVersion; }
set { innerBindingElement.MessageVersion = value; }
}
public override BindingElement Clone()
{
return new GZipMessageEncodingBindingElement(this.innerBindingElement);
}
public override T GetProperty<T>(BindingContext context)
{
if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
{
return innerBindingElement.GetProperty<T>(context);
}
else
{
return base.GetProperty<T>(context);
}
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
}
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.CanBuildInnerChannelListener<TChannel>();
}
void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
{
if (policyContext == null)
{
throw new ArgumentNullException("policyContext");
}
XmlDocument document = new XmlDocument();
policyContext.GetBindingAssertions().Add(document.CreateElement(
GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
GZipMessageEncodingPolicyConstants.GZipEncodingName,
GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
}
}
#endregion
static Binding GetBinding()
{
CustomBinding custom = new CustomBinding(new WebHttpBinding());
for (int i = 0; i < custom.Elements.Count; i++)
{
if (custom.Elements[i] is WebMessageEncodingBindingElement)
{
WebMessageEncodingBindingElement webBE = (WebMessageEncodingBindingElement)custom.Elements[i];
custom.Elements[i] = new GZipMessageEncodingBindingElement(webBE);
break;
}
}
return custom;
}
class MyContentEncodingBehavior : IEndpointBehavior, IDispatchMessageInspector
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
public void Validate(ServiceEndpoint endpoint)
{
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpResponseMessageProperty resp = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
resp.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var endpoint = host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
endpoint.Behaviors.Add(new MyContentEncodingBehavior());
host.Open();
Console.WriteLine("Host opened");
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/GetLargeData?size=1000");
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException ex)
{
resp = (HttpWebResponse)ex.Response;
}
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
foreach (var header in resp.Headers.AllKeys)
{
Console.WriteLine("{0}: {1}", header, resp.Headers[header]);
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
My code at the moment looks like this:
Server side:
#region IClientCallback interface
interface IClientCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveWcfElement(WcfElement wcfElement);
}
#endregion
#region IService interface
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientCallback))]
interface IService
{
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void ReadyToReceive(string userName, int source, string ostatniTypWiadomosci);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
bool SendWcfElement(WcfElement wcfElement);
[OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
List<int> Login(Client name, string password, bool isAuto, bool isSuperMode);
}
#endregion
#region Public enums/event args
public delegate void WcfElementsReceivedFromClientEventHandler(object sender, WcfElementsReceivedFromClientEventArgs e);
public class WcfElementsReceivedFromClientEventArgs : EventArgs
{
public string UserName;
}
public class ServiceEventArgs : EventArgs
{
public WcfElement WcfElement;
public Client Person;
}
#endregion
#region Service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IService
{
#region Instance fields
//thread sync lock object
private static readonly Object SyncObj = new Object();
//callback interface for clients
IClientCallback _callback;
//delegate used for BroadcastEvent
public delegate void ChatEventHandler(object sender, ServiceEventArgs e);
public static event ChatEventHandler ChatEvent;
private ChatEventHandler _myEventHandler;
//holds a list of clients, and a delegate to allow the BroadcastEvent to work
//out which chatter delegate to invoke
static readonly Dictionary<Client, ChatEventHandler> Clients = new Dictionary<Client, ChatEventHandler>();
//current person
private Client _client;
#endregion
#region Helpers
private bool CheckIfPersonExists(string name)
{
return Clients.Keys.Any(p => p.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
private ChatEventHandler getPersonHandler(string name)
{
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
ChatEventHandler chatTo;
Clients.TryGetValue(c, out chatTo);
return chatTo;
}
return null;
}
private Client GetPerson(string name)
{
return Clients.Keys.FirstOrDefault(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
#endregion
#region IService implementation
public List<int> Login(Client client, string password, bool isAuto, bool isSuperMode)
{
if (client.ElementsVersions == null)
{
client.ElementsVersions = new WcfElement(WcfElement.RodzajWiadomosci.VersionControl, client.UserName);
}
//create a new ChatEventHandler delegate, pointing to the MyEventHandler() method
_myEventHandler = MyEventHandler;
lock (SyncObj)
{
if (!CheckIfPersonExists(client.UserName))
{
_client = client;
Clients.Add(client, _myEventHandler);
}
else
{
_client = client;
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(client.UserName)))
{
ChatEvent -= Clients[c];
Clients.Remove(c);
break;
}
Clients[client] = _myEventHandler;
}
_client.LockObj = new object();
}
_callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
ChatEvent += _myEventHandler;
var rValue = isAuto ? bazaDanych.Login(client.UserName, isSuperMode) : bazaDanych.Login(client.UserName, password);
return rValue;
}
public void PerformDataSync(Client c)
{
WcfElement wcfDelete = null;
WcfElement wcfUpdate = null;
//...
//this method prepares elements for client
//when done it adds them to clients queue (List<WcfElement)
try
{
var counter = 0;
if (wcfDelete != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfDelete, false))//split message into small ones
{
c.AddElementToQueue(wcf, counter++);
}
}
if (wcfUpdate != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfUpdate, true))
{
c.AddElementToQueue(wcf, counter++);
}
}
SendMessageToGui(string.Format("Wstępna synchronizacja użytkownika {0} zakończona.", c.UserName));
c.IsSynchronized = true;
}
catch (Exception e)
{
}
}
private void SendMessageToClient(object sender, EventArgs e)
{
var c = (Client) sender;
if (c.IsReceiving || c.IsSending)
{
return;
}
c.IsReceiving = true;
var wcfElement = c.GetFirstElementFromQueue();
if (wcfElement == null)
{
c.IsReceiving = false;
return;
}
Clients[c].Invoke(this, new ServiceEventArgs { Person = c, WcfElement = wcfElement });
}
public void ReadyToReceive(string userName)
{
var c = GetPerson(userName);
c.IsSending = false;
c.IsReceiving = false;
if (c.IsSynchronized)
{
SendMessageToClient(c, null);
}
else
{
PerformDataSync(c);
}
}
public bool SendWcfElement(WcfElement wcfElement)
{
var cl = GetPerson(wcfElement.UserName);
cl.IsSending = true;
if (wcfElement.WcfElementVersion != bazaDanych.WcfElementVersion) return false;
//method processes messages and if needed creates creates WcfElements which are added to every clients queue
return ifSuccess;
}
#endregion
#region private methods
private void MyEventHandler(object sender, ServiceEventArgs e)
{
try
{
_callback.ReceiveWcfElement(e.WcfElement);
}
catch (Exception ex)
{
}
}
#endregion
}
#endregion
Client side in a moment
#region Client class
[DataContract]
public class Client
{
#region Instance Fields
/// <summary>
/// The UserName
/// </summary>
[DataMember]
public string UserName { get; set; }
[DataMember]
public WcfElement ElementsVersions { get; set; }
private bool _isSynchronized;
public bool IsSynchronized
{
get { return _isSynchronized; }
set
{
_isSynchronized = value;
}
}
public bool IsSending { get; set; }
public bool IsReceiving { get; set; }
private List<WcfElement> ElementsQueue { get; set; }
public object LockObj { get; set; }
public void AddElementToQueue(WcfElement wcfElement, int position = -1)
{
try
{
lock (LockObj)
{
if (ElementsQueue == null) ElementsQueue = new List<WcfElement>();
if (position != -1 && position <= ElementsQueue.Count)
{
try
{
ElementsQueue.Insert(position, wcfElement);
}
catch (Exception e)
{
}
}
else
{
try
{
//dodaje na koncu
ElementsQueue.Add(wcfElement);
}
catch (Exception e)
{
}
}
}
}
catch (Exception e)
{
}
}
public WcfElement GetFirstElementFromQueue()
{
if (ElementsQueue == null) return null;
if (ElementsQueue.Count > 0)
{
var tmp = ElementsQueue[0];
ElementsQueue.RemoveAt(0);
return tmp;
}
return null;
}
#endregion
#region Ctors
/// <summary>
/// Assign constructor
/// </summary>
/// <param name="userName">The userName to use for this client</param>
public Client(string userName)
{
UserName = userName;
}
#endregion
}
#endregion
ProxySingletion:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public sealed class ProxySingleton : IClientCallback
{
#region Instance Fields
private static ProxySingleton _singleton;
public static bool IsConnected;
private static readonly object SingletonLock = new object();
private ServiceProxy _proxy;
private Client _myPerson;
private delegate void HandleDelegate(Client[] list);
private delegate void HandleErrorDelegate();
//main proxy event
public delegate void ProxyEventHandler(object sender, ProxyEventArgs e);
public static event ProxyEventHandler ProxyEvent;
//callback proxy event
public delegate void ProxyCallBackEventHandler(object sender, ProxyCallBackEventArgs e);
public static event ProxyCallBackEventHandler ProxyCallBackEvent;
#endregion
#region Ctor
/// <summary>
/// Blank constructor
/// </summary>
private ProxySingleton()
{
}
#endregion
#region Public Methods
#region IClientCallback implementation
public void ReceiveWcfElement(WcfElement wcfElement)
{
//process received data
//...
ReadyToReceive();
}
#endregion
public void ReadyToReceive()
{
try
{
if (bazaDanych.Dane.Client.IsSending) return;
var w = bazaDanych.Dane.Client.GetFirstElementFromQueue();
if (w != null)
{
SendWcfElement(w);
return;
}
_proxy.ReadyToReceive(bazaDanych.Dane.Client.UserName, source, ostatniTypWiadomosci);
}
catch (Exception)
{
IsConnected = false;
}
}
public static WcfElement CurrentWcfElement;
public bool SendWcfElement(WcfElement wcfElement)
{
if (bazaDanych.Dane.Client.IsReceiving)
{
bazaDanych.Dane.Client.AddElementToQueue(wcfElement);
return true;
}
bazaDanych.Dane.Client.IsSending = true;
foreach (var wcfElementSplited in WcfElement.SplitWcfElement(wcfElement, true))
{
CurrentWcfElement = wcfElementSplited;
try
{
var r = _proxy.SendWcfElement(wcfElementSplited);
CurrentWcfElement = null;
}
catch (Exception e)
{
IsConnected = false;
return false;
}
}
bazaDanych.Dane.Client.IsSending = false;
ReadyToReceive();
return true;
}
public void ListenForConnectOrReconnect(EventArgs e)
{
SendWcfElement(WcfElement.GetVersionElement());//send wcfelement for perform PerformDataSync
ReadyToReceive();
}
public static bool IsReconnecting;
public bool ConnectOrReconnect(bool shouldRaiseEvent = true)
{
if (IsReconnecting)
{
return IsConnected;
}
if (IsConnected) return true;
IsReconnecting = true;
bazaDanych.Dane.Client.IsReceiving = false;
bazaDanych.Dane.Client.IsSending = false;
bazaDanych.Dane.Client.IsSynchronized = false;
try
{
var site = new InstanceContext(this);
_proxy = new ServiceProxy(site);
var list = _proxy.Login(bazaDanych.Dane.Client, bazaDanych.Dane.UserPassword, bazaDanych.Dane.UserIsAuto, bazaDanych.Dane.UserIsSuperMode);
bazaDanych.Dane.UserRights.Clear();
bazaDanych.Dane.UserRights.AddRange(list);
IsConnected = true;
if (shouldRaiseEvent) ConnectOrReconnectEvent(null);
}
catch (Exception e)
{
IsConnected = false;
}
IsReconnecting = false;
return IsConnected;
}
}
#endregion
At the moment my app works like this:
After successful login every client sends WcfElements(which contains bunch of list with ids and versions of elements). Then it sends ReadyToReceive one way message which after login fires performsync method. That method prepares data for client and sends first of them using one way receive method. IF there is more than one wcfelement to send then only last one is marked as last. Client responds with ReadyToReceive after every successful receive from Server. All up to this point works quite well. Problem starts later. Mostly packages are lost (method receiveWcfElement). Server has marked that client is receiving and maybe processing message and is waitng for readytoreceive packet, which will never be send because of lost element.
I've made it like this because as far as I know client can't send and receive at the same time. I've tried this and got this problem:
If client send wcfElement with SendWcfElement method and server due to processing this element created another element which was supposed to be ssend back to client then client whoud have faulted proxy if callback was send before sendWcfElement returned true indicating that method was completed.
Now I wonder if it is possible for client to send and receive at the same time using two way methods ?
I ended up with to services(two connections). One for connection from client to server and another with callback which handles connection from server to client.