Printing using c#/ASP.NET MVC2 - asp.net-mvc-2

I am developping an intranet ASP.NET MVC2 application for a small business. The small business has multiple type of printers and depending on what is required a print request will be sent (from the browser/user) to the server and the server will dispatch the print job to the right printer accordingly. Please note that it is a completely new environment for them and I have control over pretty much everything. I will more than likely use a very lightweight OS (maybe Asus ExpressGate or Chrome OS depending on launch date?) so the users cannot have any printers installed but the server will have everything setup.
Here is my question:
Is there a simple way to print from the server side (without dialog of course because there won't be anyone waiting to click them) a page by using the html link as a parameter and keeping the HTML format of course.
I have seen a few possibilities of COM stuff out there but if there are possibilities to avoid this by using a .net class I would appreciate. I am using .net 4.0. I will however take any suggestions even if it is COM based.
Edit: Please note that any workaround that makes sense would also be taken into consideration, a quick (non studied yet) example would be to transfer this html to a doc file and sending this file to the printer.
Edit-Code taken off for lack of use.
Edits2:
Following this link: Print html document from Windows Service in C# without print dialog
Vadim's holy grail solution DOES work. However, it has limitations like using the default printer only. I am modifying the default printer before the print occurs this causes the print to go to the right printer. I can see some concurrence issues happening here but so far this is the best i came up with (most of this code is from Vadim and I give him the full credit for this):
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short PRINT_WAITFORCOMPLETION = 2;
const int OLECMDID_PRINT = 6;
const int OLECMDEXECOPT_DONTPROMPTUSER = 2;
using(var browser = new WebBrowser())
{
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, PRINT_WAITFORCOMPLETION);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//Verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
ManagementBaseObject outParams =
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
}

A couple of articles for readng which maybe helpful are:
http://msdn.microsoft.com/en-us/library/system.drawing.printing.printersettings.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.printing.printdocument.aspx
Will update if I find anything else. Hope this helps.

This is a parallel helper for .net 4.0 only that manages collision/threads.
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("numberOfThreads");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
}) {IsBackground = true};
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
I use 2 enums, one is for the web browser control the OLECMDID:
public enum OLECMDID
{
OLECMDID_OPEN = 1,
OLECMDID_NEW = 2,
OLECMDID_SAVE = 3,
OLECMDID_SAVEAS = 4,
OLECMDID_SAVECOPYAS = 5,
OLECMDID_PRINT = 6,
OLECMDID_PRINTPREVIEW = 7,
OLECMDID_PAGESETUP = 8,
OLECMDID_SPELL = 9,
OLECMDID_PROPERTIES = 10,
OLECMDID_CUT = 11,
OLECMDID_COPY = 12,
OLECMDID_PASTE = 13,
OLECMDID_PASTESPECIAL = 14,
OLECMDID_UNDO = 15,
OLECMDID_REDO = 16,
OLECMDID_SELECTALL = 17,
OLECMDID_CLEARSELECTION = 18,
OLECMDID_ZOOM = 19,
OLECMDID_GETZOOMRANGE = 20,
OLECMDID_UPDATECOMMANDS = 21,
OLECMDID_REFRESH = 22,
OLECMDID_STOP = 23,
OLECMDID_HIDETOOLBARS = 24,
OLECMDID_SETPROGRESSMAX = 25,
OLECMDID_SETPROGRESSPOS = 26,
OLECMDID_SETPROGRESSTEXT = 27,
OLECMDID_SETTITLE = 28,
OLECMDID_SETDOWNLOADSTATE = 29,
OLECMDID_STOPDOWNLOAD = 30,
OLECMDID_FIND = 32,
OLECMDID_DELETE = 33,
OLECMDID_PRINT2 = 49,
OLECMDID_PRINTPREVIEW2 = 50,
OLECMDID_PAGEACTIONBLOCKED = 55,
OLECMDID_PAGEACTIONUIQUERY = 56,
OLECMDID_FOCUSVIEWCONTROLS = 57,
OLECMDID_FOCUSVIEWCONTROLSQUERY = 58,
OLECMDID_SHOWPAGEACTIONMENU = 59,
OLECMDID_ADDTRAVELENTRY = 60,
OLECMDID_UPDATETRAVELENTRY = 61,
OLECMDID_UPDATEBACKFORWARDSTATE = 62,
OLECMDID_OPTICAL_ZOOM = 63,
OLECMDID_OPTICAL_GETZOOMRANGE = 64,
OLECMDID_WINDOWSTATECHANGED = 65,
OLECMDID_ACTIVEXINSTALLSCOPE = 66,
OLECMDID_UPDATETRAVELENTRY_DATARECOVERY = 67
}
The other is a custom made for whatever you need to print:
public enum PrintDocumentType
{
Bill,
Label //etc...
}
Now, here is the my printer helper that I use which sets the default printer (and prints to it), also changes the margin according to what I need printed:
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice, PrintDocumentType printDocumentType)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
IeSetup(printDocumentType);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short printWaitForCompletion = 2;
const int oleCmdExecOptDontPromptUser = 2;
using(var browser = new WebBrowser())
{
WebBrowserHelper.ClearCache(); /*needed since there is a major cache flaw. The WebBrowserHelper class is available at http://www.gutgames.com/post/Clearing-the-Cache-of-a-WebBrowser-Control.aspx with some slight changes or if website is taken off, it is based heavily on http://support.microsoft.com/kb/326201*/
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
((IWebBrowser2)ie).ExecWB(SHDocVw.OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, oleCmdExecOptDontPromptUser, printWaitForCompletion);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
/// <summary>
/// Making sure the printer doesn't output the default footer and header of Internet Explorer (url, pagenumber, title, etc.).
/// </summary>
public void IeSetup(PrintDocumentType printDocumentType)
{
const string keyName = #"Software\Microsoft\Internet Explorer\PageSetup";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName, true)) {
if (key == null) return;
key.SetValue("footer", "");
key.SetValue("header", "");
switch (printDocumentType)
{
case PrintDocumentType.Label:
key.SetValue("margin_top", "0.12500");
key.SetValue("margin_bottom", "0.12500");
key.SetValue("margin_left", "0.25000");
key.SetValue("margin_right", "0.25000");
break;
case PrintDocumentType.Bill:
key.SetValue("margin_top", "0.75000");
key.SetValue("margin_bottom", "0.75000");
key.SetValue("margin_left", "0.75000");
key.SetValue("margin_right", "0.75000");
break;
}
}
}
}
As you notice, I have a webbrowserhelper that is another class. It is quite big and I will not paste it. However, I input the links in comment next to it where you can get the code. The webbrowser itself has a major cache flaw and even if you force it to refresh the page it will always grab the cache, therefore, a clearcache is in order. I learned the hard way.
I hope this helps everyone out there. Please note that this is for .net 4.0.

Related

Spatial Alignment between Two Hololens 2 Headsets using ARFoundation/Azure Spatial Anchors

I'm working through this tutorial: https://mtaulty.com/2019/07/18/simple-shared-holograms-with-photon-networking-part-1/ with the hope of reproducing the shared coordinate system between two Hololens 2 headsets. I'm using Unity 2020, PUN2, ARFoundation and MRTK.
Because the tutorial is using WorldAnchors (WSA platform), which is a bit old, I'm trying to modify it to use ARFoundation. So far, the code I have as a result, seems to properly have the two headsets communicating via PUN2, but the blue cube as shown in the tutorial does not align between the headsets. The cube simply seems referenced to each headsets initial startup frame of reference. Below is the code. I've kept everything as one-to-one with the tutorial as possible, except where I felt I needed to swap WorldAnchors for ARAnchors and also where I swapped in a SpatialAnchorManager class to handle the Azure Spatial Service session since I found the tutorial's StartSession function didn't seem to work properly. Both AzureSpatialAnchorService.cs and PhotonScript.cs are attached to a root game object in the scene. Picture of the scene attached. Based on the debug logs I'm able to tell that the first headset is creating and saving an anchor to Azure and the second headset is able to find that same anchor. But I apparently am not performing a necessary transformation between headsets?
Can anyone suggest what I'm doing wrong and/or what specific edits that need to be made to get spatial alignment between headsets?
Thanks!
AzureSpatialAnchorService.cs:
using Microsoft.Azure.SpatialAnchors.Unity;
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.WSA;
namespace AzureSpatialAnchors
{
[RequireComponent(typeof(SpatialAnchorManager))]
public class AzureSpatialAnchorService : MonoBehaviour
{
[Serializable]
public class AzureSpatialAnchorServiceProfile
{
[SerializeField]
[Tooltip("The account id from the Azure portal for the Azure Spatial Anchors service")]
string azureAccountId;
public string AzureAccountId => this.azureAccountId;
[SerializeField]
[Tooltip("The access key from the Azure portal for the Azure Spatial Anchors service (for Key authentication)")]
string azureServiceKey;
public string AzureServiceKey => this.azureServiceKey;
}
[SerializeField]
[Tooltip("The configuration for the Azure Spatial Anchors Service")]
AzureSpatialAnchorServiceProfile profile = new AzureSpatialAnchorServiceProfile();
public AzureSpatialAnchorServiceProfile Profile => this.profile;
TaskCompletionSource<CloudSpatialAnchor> taskWaitForAnchorLocation;
//CloudSpatialAnchorSession cloudSpatialAnchorSession;
private SpatialAnchorManager _spatialAnchorManager = null;
public AzureSpatialAnchorService()
{
}
public async Task<string> CreateAnchorOnObjectAsync(GameObject gameObjectForAnchor)
{
string anchorId = string.Empty;
try
{
await this.StartSession();
Debug.Log("Started Session");
//Add and configure ASA components
CloudNativeAnchor cloudNativeAnchor = gameObjectForAnchor.AddComponent<CloudNativeAnchor>();
await cloudNativeAnchor.NativeToCloud();
Debug.Log("After NativeToCloud");
CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);
// As per previous comment.
//Collect Environment Data
while (!_spatialAnchorManager.IsReadyForCreate)
{
float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
}
Debug.Log($"ASA - Saving room cloud anchor... ");
await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);
anchorId = cloudSpatialAnchor?.Identifier;
bool saveSucceeded = cloudSpatialAnchor != null;
if (!saveSucceeded)
{
Debug.LogError("ASA - Failed to save, but no exception was thrown.");
return anchorId;
}
anchorId = cloudSpatialAnchor.Identifier;
Debug.Log($"ASA - Saved room cloud anchor with ID: {anchorId}");
}
catch (Exception exception) // TODO: reasonable exceptions here.
{
Debug.Log("ASA - Failed to save room anchor: " + exception.ToString());
Debug.LogException(exception);
}
return (anchorId);
}
public async Task<bool> PopulateAnchorOnObjectAsync(string anchorId, GameObject gameObjectForAnchor)
{
bool anchorLocated = false;
try
{
await this.StartSession();
this.taskWaitForAnchorLocation = new TaskCompletionSource<CloudSpatialAnchor>();
var watcher = _spatialAnchorManager.Session.CreateWatcher(
new AnchorLocateCriteria()
{
Identifiers = new string[] { anchorId },
BypassCache = true,
Strategy = LocateStrategy.AnyStrategy,
RequestedCategories = AnchorDataCategory.Spatial
}
);
var cloudAnchor = await this.taskWaitForAnchorLocation.Task;
anchorLocated = cloudAnchor != null;
if (anchorLocated)
{
Debug.Log("Anchor located");
gameObjectForAnchor.AddComponent<CloudNativeAnchor>().CloudToNative(cloudAnchor);
Debug.Log("Attached Local Anchor");
}
watcher.Stop();
}
catch (Exception ex) // TODO: reasonable exceptions here.
{
Debug.Log($"Caught {ex.Message}");
}
return (anchorLocated);
}
/// <summary>
/// Start the Azure Spatial Anchor Service session
/// This must be called before calling create, populate or delete methods.
/// </summary>
public async Task<bool> StartSession()
{
//if (this.cloudSpatialAnchorSession == null)
//{
// Debug.Assert(this.cloudSpatialAnchorSession == null);
// this.ThrowOnBadAuthConfiguration();
// // setup the session
// this.cloudSpatialAnchorSession = new CloudSpatialAnchorSession();
// // set the Azure configuration parameters
// this.cloudSpatialAnchorSession.Configuration.AccountId = this.Profile.AzureAccountId;
// this.cloudSpatialAnchorSession.Configuration.AccountKey = this.Profile.AzureServiceKey;
// // register event handlers
// this.cloudSpatialAnchorSession.Error += this.OnCloudSessionError;
// this.cloudSpatialAnchorSession.AnchorLocated += OnAnchorLocated;
// this.cloudSpatialAnchorSession.LocateAnchorsCompleted += OnLocateAnchorsCompleted;
// // start the session
// this.cloudSpatialAnchorSession.Start();
//}
_spatialAnchorManager = GetComponent<SpatialAnchorManager>();
_spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
_spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
_spatialAnchorManager.AnchorLocated += OnAnchorLocated;
//_spatialAnchorManager.LocateAnchorsCompleted += OnLocateAnchorsCompleted;
await _spatialAnchorManager.StartSessionAsync();
return true;
}
/// <summary>
/// Stop the Azure Spatial Anchor Service session
/// </summary>
//public void StopSession()
//{
// if (this.cloudSpatialAnchorSession != null)
// {
// // stop session
// this.cloudSpatialAnchorSession.Stop();
// // clear event handlers
// this.cloudSpatialAnchorSession.Error -= this.OnCloudSessionError;
// this.cloudSpatialAnchorSession.AnchorLocated -= OnAnchorLocated;
// this.cloudSpatialAnchorSession.LocateAnchorsCompleted -= OnLocateAnchorsCompleted;
// // cleanup
// this.cloudSpatialAnchorSession.Dispose();
// this.cloudSpatialAnchorSession = null;
// }
//}
void OnLocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
{
Debug.Log("On Locate Anchors Completed");
Debug.Assert(this.taskWaitForAnchorLocation != null);
if (!this.taskWaitForAnchorLocation.Task.IsCompleted)
{
this.taskWaitForAnchorLocation.TrySetResult(null);
}
}
void OnAnchorLocated(object sender, AnchorLocatedEventArgs args)
{
Debug.Log($"On Anchor Located, status is {args.Status} anchor is {args.Anchor?.Identifier}, pointer is {args.Anchor?.LocalAnchor}");
Debug.Assert(this.taskWaitForAnchorLocation != null);
this.taskWaitForAnchorLocation.SetResult(args.Anchor);
}
void OnCloudSessionError(object sender, SessionErrorEventArgs args)
{
Debug.Log($"On Cloud Session Error: {args.ErrorMessage}");
}
void ThrowOnBadAuthConfiguration()
{
if (string.IsNullOrEmpty(this.Profile.AzureAccountId) ||
string.IsNullOrEmpty(this.Profile.AzureServiceKey))
{
throw new ArgumentNullException("Missing required configuration to connect to service");
}
}
}
}
PhotonScript.cs:
using System;
using System.Threading.Tasks;
using AzureSpatialAnchors;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
public class PhotonScript : MonoBehaviourPunCallbacks
{
enum RoomStatus
{
None,
CreatedRoom,
JoinedRoom,
JoinedRoomDownloadedAnchor
}
public int emptyRoomTimeToLiveSeconds = 120;
RoomStatus roomStatus = RoomStatus.None;
void Start()
{
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
base.OnConnectedToMaster();
var roomOptions = new RoomOptions();
roomOptions.EmptyRoomTtl = this.emptyRoomTimeToLiveSeconds * 1000;
PhotonNetwork.JoinOrCreateRoom(ROOM_NAME, roomOptions, null);
}
public async override void OnJoinedRoom()
{
base.OnJoinedRoom();
// Note that the creator of the room also joins the room...
if (this.roomStatus == RoomStatus.None)
{
this.roomStatus = RoomStatus.JoinedRoom;
}
await this.PopulateAnchorAsync();
}
public async override void OnCreatedRoom()
{
base.OnCreatedRoom();
this.roomStatus = RoomStatus.CreatedRoom;
await this.CreateAnchorAsync();
}
async Task CreateAnchorAsync()
{
// If we created the room then we will attempt to create an anchor for the parent
// of the cubes that we are creating.
var anchorService = this.GetComponent<AzureSpatialAnchorService>();
var anchorId = await anchorService.CreateAnchorOnObjectAsync(this.gameObject);
// Put this ID into a custom property so that other devices joining the
// room can get hold of it.
#if UNITY_2020
PhotonNetwork.CurrentRoom.SetCustomProperties(
new Hashtable()
{
{ ANCHOR_ID_CUSTOM_PROPERTY, anchorId }
}
);
#endif
}
async Task PopulateAnchorAsync()
{
if (this.roomStatus == RoomStatus.JoinedRoom)
{
object keyValue = null;
#if UNITY_2020
// First time around, this property may not be here so we see if is there.
if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(
ANCHOR_ID_CUSTOM_PROPERTY, out keyValue))
{
// If the anchorId property is present then we will try and get the
// anchor but only once so change the status.
this.roomStatus = RoomStatus.JoinedRoomDownloadedAnchor;
// If we didn't create the room then we want to try and get the anchor
// from the cloud and apply it.
var anchorService = this.GetComponent<AzureSpatialAnchorService>();
await anchorService.PopulateAnchorOnObjectAsync(
(string)keyValue, this.gameObject);
}
#endif
}
}
public async override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
base.OnRoomPropertiesUpdate(propertiesThatChanged);
await this.PopulateAnchorAsync();
}
static readonly string ANCHOR_ID_CUSTOM_PROPERTY = "anchorId";
static readonly string ROOM_NAME = "HardCodedRoomName";
}
From reviewing the code and scenario, I am reading that this is in same local area. So, the ASA service will have the shared anchor as detailed in docs:
https://learn.microsoft.com/en-us/windows/mixed-reality/design/shared-experiences-in-mixed-reality
"Shared static holograms (no interactions)
Leverage Azure Spatial Anchors in your app. Enabling and sharing spatial anchors across devices allows you to create an application where users see holograms in the same place at the same time. Additional syncing across devices is needed to enable users to interact with holograms and see movements or state updates of holograms."
The tutorial that Microsoft docs point to is here if it helps comparison to the other sample:
https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/tutorials/mr-learning-sharing-01

Batching futures in Dart

I want to batch many futures into a single request that triggers either when a maximum batch size is reached, or a maximum time since the earliest future was received is reached.
Motivation
In flutter, I have many UI elements which need to display the result of a future, dependent on the data in the UI element.
For instance, I have a widget for a place, and a sub-widget which displays how long it will take to walk to a place. To compute the how long it will take to walk, I issue a request to Google Maps API to get the travel time to the place.
It is more efficient and cost-effective to batch all these API requests into a batch API request. So if there are 100 requests made instantaneously by the widgets, then the futures could be proxied through a single provider, which batches the futures into a single request to Google, and unpacks the result from Google into all the individual requests.
The provider needs to know when to stop waiting for more futures and when to actually issue the request, which should be controllable by the maximum "batch" size (i.e., # of travel time requests), or the maximum amount of time you are willing to wait for batching to take place.
The desired API would be something like:
// Client gives this to tell provider how to compute batch result.
abstract class BatchComputer<K,V> {
Future<List<V>> compute(List<K> batchedInputs);
}
// Batching library returns an object with this interface
// so that client can submit inputs to completed by the Batch provider.
abstract class BatchingFutureProvider<K,V> {
Future<V> submit(K inputValue);
}
// How do you implement this in dart???
BatchingFutureProvider<K,V> create<K,V>(
BatchComputer<K,V> computer,
int maxBatchSize,
Duration maxWaitDuration,
);
Does Dart (or a pub package) already provide this batching functionality, and if not, how would you implement the create function above?
This sounds perfectly reasonable, but also very specialized.
You need a way to represent a query, to combine these queries into a single super-query, and to split the super-result into individual results afterwards, which is what your BatchComputer does. Then you need a queue which you can flush through that under some conditions.
One thing that is clear is that you will need to use Completers for the results because you always need that when you want to return a future before you have the value or future to complete it with.
The approach I would choose would be:
import "dart:async";
/// A batch of requests to be handled together.
///
/// Collects [Request]s until the pending requests are flushed.
/// Requests can be flushed by calling [flush] or by configuring
/// the batch to automatically flush when reaching certain
/// tresholds.
class BatchRequest<Request, Response> {
final int _maxRequests;
final Duration _maxDelay;
final Future<List<Response>> Function(List<Request>) _compute;
Timer _timeout;
List<Request> _pendingRequests;
List<Completer<Response>> _responseCompleters;
/// Creates a batcher of [Request]s.
///
/// Batches requests until calling [flush]. At that pont, the
/// [batchCompute] function gets the list of pending requests,
/// and it should respond with a list of [Response]s.
/// The response to the a request in the argument list
/// should be at the same index in the response list,
/// and as such, the response list must have the same number
/// of responses as there were requests.
///
/// If [maxRequestsPerBatch] is supplied, requests are automatically
/// flushed whenever there are that many requests pending.
///
/// If [maxDelay] is supplied, requests are automatically flushed
/// when the oldest request has been pending for that long.
/// As such, The [maxDelay] is not the maximal time before a request
/// is answered, just how long sending the request may be delayed.
BatchRequest(Future<List<Response>> Function(List<Request>) batchCompute,
{int maxRequestsPerBatch, Duration maxDelay})
: _compute = batchCompute,
_maxRequests = maxRequestsPerBatch,
_maxDelay = maxDelay;
/// Add a request to the batch.
///
/// The request is stored until the requests are flushed,
/// then the returned future is completed with the result (or error)
/// received from handling the requests.
Future<Response> addRequest(Request request) {
var completer = Completer<Response>();
(_pendingRequests ??= []).add(request);
(_responseCompleters ??= []).add(completer);
if (_pendingRequests.length == _maxRequests) {
_flush();
} else if (_timeout == null && _maxDelay != null) {
_timeout = Timer(_maxDelay, _flush);
}
return completer.future;
}
/// Flush any pending requests immediately.
void flush() {
_flush();
}
void _flush() {
if (_pendingRequests == null) {
assert(_timeout == null);
assert(_responseCompleters == null);
return;
}
if (_timeout != null) {
_timeout.cancel();
_timeout = null;
}
var requests = _pendingRequests;
var completers = _responseCompleters;
_pendingRequests = null;
_responseCompleters = null;
_compute(requests).then((List<Response> results) {
if (results.length != completers.length) {
throw StateError("Wrong number of results. "
"Expected ${completers.length}, got ${results.length}");
}
for (int i = 0; i < results.length; i++) {
completers[i].complete(results[i]);
}
}).catchError((error, stack) {
for (var completer in completers) {
completer.completeError(error, stack);
}
});
}
}
You can use that as, for example:
void main() async {
var b = BatchRequest<int, int>(_compute,
maxRequestsPerBatch: 5, maxDelay: Duration(seconds: 1));
var sw = Stopwatch()..start();
for (int i = 0; i < 8; i++) {
b.addRequest(i).then((r) {
print("${sw.elapsedMilliseconds.toString().padLeft(4)}: $i -> $r");
});
}
}
Future<List<int>> _compute(List<int> args) =>
Future.value([for (var x in args) x + 1]);
See https://pub.dev/packages/batching_future/versions/0.0.2
I have almost exactly the same answer as #lrn, but have put some effort to make the main-line synchronous, and added some documentation.
/// Exposes [createBatcher] which batches computation requests until either
/// a max batch size or max wait duration is reached.
///
import 'dart:async';
import 'dart:collection';
import 'package:quiver/iterables.dart';
import 'package:synchronized/synchronized.dart';
/// Converts input type [K] to output type [V] for every item in
/// [batchedInputs]. There must be exactly one item in output list for every
/// item in input list, and assumes that input[i] => output[i].
abstract class BatchComputer<K, V> {
const BatchComputer();
Future<List<V>> compute(List<K> batchedInputs);
}
/// Interface to submit (possible) batched computation requests.
abstract class BatchingFutureProvider<K, V> {
Future<V> submit(K inputValue);
}
/// Returns a batcher which computes transformations in batch using [computer].
/// The batcher will wait to compute until [maxWaitDuration] is reached since
/// the first item in the current batch is received, or [maxBatchSize] items
/// are in the current batch, whatever happens first.
/// If [maxBatchSize] or [maxWaitDuration] is null, then the triggering
/// condition is ignored, but at least one condition must be supplied.
///
/// Warning: If [maxWaitDuration] is not supplied, then it is possible that
/// a partial batch will never finish computing.
BatchingFutureProvider<K, V> createBatcher<K, V>(BatchComputer<K, V> computer,
{int maxBatchSize, Duration maxWaitDuration}) {
if (!((maxBatchSize != null || maxWaitDuration != null) &&
(maxWaitDuration == null || maxWaitDuration.inMilliseconds > 0) &&
(maxBatchSize == null || maxBatchSize > 0))) {
throw ArgumentError(
"At least one of {maxBatchSize, maxWaitDuration} must be specified and be positive values");
}
return _Impl(computer, maxBatchSize, maxWaitDuration);
}
// Holds the input value and the future to complete it.
class _Payload<K, V> {
final K k;
final Completer<V> completer;
_Payload(this.k, this.completer);
}
enum _ExecuteCommand { EXECUTE }
/// Implements [createBatcher].
class _Impl<K, V> implements BatchingFutureProvider<K, V> {
/// Queues computation requests.
final controller = StreamController<dynamic>();
/// Queues the input values with their futures to complete.
final queue = Queue<_Payload>();
/// Locks access to [listen] to make queue-processing single-threaded.
final lock = Lock();
/// [maxWaitDuration] timer, as a stored reference to cancel early if needed.
Timer timer;
/// Performs the input->output batch transformation.
final BatchComputer computer;
/// See [createBatcher].
final int maxBatchSize;
/// See [createBatcher].
final Duration maxWaitDuration;
_Impl(this.computer, this.maxBatchSize, this.maxWaitDuration) {
controller.stream.listen(listen);
}
void dispose() {
controller.close();
}
#override
Future<V> submit(K inputValue) {
final completer = Completer<V>();
controller.add(_Payload(inputValue, completer));
return completer.future;
}
// Synchronous event-processing logic.
void listen(dynamic event) async {
await lock.synchronized(() {
if (event.runtimeType == _ExecuteCommand) {
if (timer?.isActive ?? true) {
// The timer got reset, so ignore this old request.
// The current timer needs to inactive and non-null
// for the execution to be legitimate.
return;
}
execute();
} else {
addPayload(event as _Payload);
}
return;
});
}
void addPayload(_Payload _payload) {
if (queue.isEmpty && maxWaitDuration != null) {
// This is the first item of the batch.
// Trigger the timer so we are guaranteed to start computing
// this batch before [maxWaitDuration].
timer = Timer(maxWaitDuration, triggerTimer);
}
queue.add(_payload);
if (maxBatchSize != null && queue.length >= maxBatchSize) {
execute();
return;
}
}
void execute() async {
timer?.cancel();
if (queue.isEmpty) {
return;
}
final results = await computer.compute(List<K>.of(queue.map((p) => p.k)));
for (var pair in zip<Object>([queue, results])) {
(pair[0] as _Payload).completer.complete(pair[1] as V);
}
queue.clear();
}
void triggerTimer() {
listen(_ExecuteCommand.EXECUTE);
}
}

Windows phone 8 and Unity 5 support for Facebook SDK

This is a request for support for WP8 and Unity 5 for the Facebook SDK.
I am a developer wanting to produce games across many platforms, and being able to publish to the Windows Phone 8 store is a big part of my agenda.
Is support for this platform scheduled to be released any time soon? If so, when are you looking to release it?
Also, is there an imminent release for the Unity SDK that is designed to work with Unity 5? I have managed to get the current release to work, but as it is not yet fully supported, I do not know what will work and what won't.
Any information regarding these issues would be much appreciated!
Update: I ran into this video that explains how the "AndContinue" methods are going away in windows 10 so we can use one single method; the Async methods that already exists on windows 8/8.1 api. Check it out at https://youtu.be/aFVAP3fNJVo?t=23m34s
I was in the same place as you but managed to get it working. When I have time, ill probably write a blog on my experiences.
Here is some of the main code that directly calls on WebAuthenticationBroker:-
static bool isTryingToRegister { get; set; }
private static string _FbToken;
public static string response;
static bool isLoggedIn { get; set; }
static private string AppID { get { return "000000000000000000"; } }
static private Uri callback { get; set; }
static private string permissions { get { return "public_profile, user_friends, email, publish_actions, user_photos"; } }
public static System.Action<object> AuthSuccessCallback;
public static System.Action<object> AuthFailedCallback;
public static string fbToken
{
get
{
return _FbToken;
}
}
static Uri loginUrl
{
get
{
return new Uri(String.Format("https://www.facebook.com/v2.0/dialog/oauth/?client_id={0}&display=popup&response_type=token&redirect_uri={1}&scope={2}",
AppID,
callback,
permissions));
}
}
public static IEnumerator _Run_ConnectWithFacebook()
{
yield return new WaitForEndOfFrame();
#if UNITY_EDITOR
UnityDebugAuthentication();
#endif
#if NETFX_CORE
WindowsStoreAuthenticate();
#endif
}
#if NETFX_CORE
static void WindowsStoreAuthenticate()
{
#if UNITY_WP_8_1 && !UNITY_EDITOR
AuthSuccessCallback = _Run_ConnectWithFacebook_SuccessResponse;
AuthFailedCallback = _Run_ConnectWithFacebook_FailedResponse;
UnityEngine.WSA.Application.InvokeOnUIThread(
() =>
{
callback = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
WebAuthenticationBroker.AuthenticateAndContinue(loginUrl, callback, null, WebAuthenticationOptions.None);
}, true);
#endif
#if UNITY_METRO_8_1 && !UNITY_EDITOR
AuthSuccessCallback = _Run_ConnectWithFacebook_SuccessResponse;
AuthFailedCallback = _Run_ConnectWithFacebook_FailedResponse;
UnityEngine.WSA.Application.InvokeOnUIThread(
async () =>
{
callback = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
WebAuthenticationResult authResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, loginUrl);
}, true);
#endif
}
#endif
Note how I use WebAuthenticationBroker.AuthenticateAndContinue for windows phone and WebAuthenticationBroker.AuthenticateAsync for windows 8. This is necessary for their respective platforms. You may want to look into getting the WebAuthenticationBroker which is a Microsoft class that works on WP8 and WSA (im guessing you are aiming for WSA, so am I):-
https://msdn.microsoft.com/library/windows/apps/windows.security.authentication.web.webauthenticationbroker.aspx?f=255&MSPPError=-2147217396
You will also need to build a c# project in unity so you can implement part 2 of it which includes handling when the token details are returned to the app. Here is my code sample of App.xaml.cs which was inspired by https://msdn.microsoft.com/en-us/library/dn631755.aspx:-
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using UnityPlayer;
using Template.Common;
using Windows.Security.Authentication.Web;
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
namespace Template
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
private WinRTBridge.WinRTBridge _bridge;
private AppCallbacks appCallbacks;
#if UNITY_WP_8_1
public ContinuationManager continuationManager { get; private set; }
#endif
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
appCallbacks = new AppCallbacks();
appCallbacks.RenderingStarted += RemoveSplashScreen;
#if UNITY_WP_8_1
this.Suspending += OnSuspending;
continuationManager = new ContinuationManager();
#endif
}
/// <summary>
/// Invoked when application is launched through protocol.
/// Read more - http://msdn.microsoft.com/library/windows/apps/br224742
/// </summary>
/// <param name="args"></param>
protected override void OnActivated(IActivatedEventArgs args)
{
#if UNITY_WP_8_1
var continuationEventArgs = args as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
return;
//}
}
#endif
string appArgs = "";
Windows.ApplicationModel.Activation.SplashScreen splashScreen = null;
switch (args.Kind)
{
case ActivationKind.Protocol:
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
splashScreen = eventArgs.SplashScreen;
appArgs += string.Format("Uri={0}", eventArgs.Uri.AbsoluteUri);
break;
}
InitializeUnity(appArgs, splashScreen);
}
#if UNITY_WP_8_1
public void ContinueWebAuthentication(WebAuthenticationBrokerContinuationEventArgs args)
{
WebAuthenticationResult result = args.WebAuthenticationResult;
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
string responseData = result.ResponseData.Substring(result.ResponseData.IndexOf("access_token"));
String[] keyValPairs = responseData.Split('&');
string access_token = null;
string expires_in = null;
for (int i = 0; i < keyValPairs.Length; i++)
{
String[] splits = keyValPairs[i].Split('=');
switch (splits[0])
{
case "access_token":
access_token = splits[1]; //you may want to store access_token for further use. Look at Scenario5 (Account Management).
break;
case "expires_in":
expires_in = splits[1];
break;
}
}
AppCallbacks.Instance.UnityActivate(Window.Current.CoreWindow, CoreWindowActivationState.CodeActivated);
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call or variable that accepts the access token
}, false);
//OutputToken(result.ResponseData.ToString());
//await GetFacebookUserNameAsync(result.ResponseData.ToString());
}
else if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
{
//OutputToken("HTTP Error returned by AuthenticateAsync() : " + result.ResponseErrorDetail.ToString());
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call indicating something went wrong
}, false);
}
else if(result.ResponseStatus == WebAuthenticationStatus.UserCancel)
{
//OutputToken("Error returned by AuthenticateAsync() : " + result.ResponseStatus.ToString());
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//function to call indicating something went wrong
}, false);
}
}
#endif
/// <summary>
/// Invoked when application is launched via file
/// Read more - http://msdn.microsoft.com/library/windows/apps/br224742
/// </summary>
/// <param name="args"></param>
protected override void OnFileActivated(FileActivatedEventArgs args)
{
string appArgs = "";
Windows.ApplicationModel.Activation.SplashScreen splashScreen = null;
splashScreen = args.SplashScreen;
appArgs += "File=";
bool firstFileAdded = false;
foreach (var file in args.Files)
{
if (firstFileAdded) appArgs += ";";
appArgs += file.Path;
firstFileAdded = true;
}
InitializeUnity(appArgs, splashScreen);
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
InitializeUnity(args.Arguments, args.SplashScreen);
}
private void InitializeUnity(string args, Windows.ApplicationModel.Activation.SplashScreen splashScreen)
{
#if UNITY_WP_8_1
ApplicationView.GetForCurrentView().SuppressSystemOverlays = true;
#pragma warning disable 4014
StatusBar.GetForCurrentView().HideAsync();
#pragma warning restore 4014
#endif
appCallbacks.SetAppArguments(args);
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null && !appCallbacks.IsInitialized())
{
var mainPage = new MainPage(splashScreen);
Window.Current.Content = mainPage;
Window.Current.Activate();
// Setup scripting bridge
_bridge = new WinRTBridge.WinRTBridge();
appCallbacks.SetBridge(_bridge);
#if !UNITY_WP_8_1
appCallbacks.SetKeyboardTriggerControl(mainPage);
#endif
appCallbacks.SetSwapChainPanel(mainPage.GetSwapChainPanel());
appCallbacks.SetCoreWindowEvents(Window.Current.CoreWindow);
appCallbacks.InitializeD3DXAML();
}
Window.Current.Activate();
#if UNITY_WP_8_1
SetupLocationService();
#endif
}
private void RemoveSplashScreen()
{
// This will fail if you change main window class
// Make sure to adjust accordingly if you do something like this
MainPage page = (MainPage)Window.Current.Content;
page.RemoveSplashScreen();
}
#if UNITY_WP_8_1
// This is the default setup to show location consent message box to the user
// You can customize it to your needs, but do not remove it completely if your application
// uses location services, as it is a requirement in Windows Store certification process
private async void SetupLocationService()
{
if (!appCallbacks.IsLocationCapabilitySet())
{
return;
}
const string settingName = "LocationContent";
bool userGaveConsent = false;
object consent;
var settings = Windows.Storage.ApplicationData.Current.LocalSettings;
var userWasAskedBefore = settings.Values.TryGetValue(settingName, out consent);
if (!userWasAskedBefore)
{
var messageDialog = new Windows.UI.Popups.MessageDialog("Can this application use your location?", "Location services");
var acceptCommand = new Windows.UI.Popups.UICommand("Yes");
var declineCommand = new Windows.UI.Popups.UICommand("No");
messageDialog.Commands.Add(acceptCommand);
messageDialog.Commands.Add(declineCommand);
userGaveConsent = (await messageDialog.ShowAsync()) == acceptCommand;
settings.Values.Add(settingName, userGaveConsent);
}
else
{
userGaveConsent = (bool)consent;
}
if (userGaveConsent)
{ // Must be called from UI thread
appCallbacks.SetupGeolocator();
}
}
#endif
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
await SuspensionManager.SaveAsync();
deferral.Complete();
}
}
}
You will also need the ContinuationManager.cs class and the SuspensionManager.cs class but mainly for WP8.1 Universal. This isnt needed for W8.1 and so you should ensure that you are using #defines to keep them within their own contexts
If you want, instead of calling on WebAuthenticationBroker, you could call on the facebook app directly but I don't know of all the details. You can read up on that on facebook's website developers.facebook.com/docs/facebook-login/login-for-windows-phone. That method isnt recommended incase the user doesn't have it installed.
Long (rushed) story short, call WebAuthenticationBroker, handle the continuation Event in OnActivated event to catch the WebAuthenticationBrokerContinuationEventArgs object coming through and use the data however you see fit. Be sure to use the following if you want to call any code in the unity side. Note that you can directly access the c# code from App.xaml.cs:-
AppCallbacks.Instance.InvokeOnAppThread(() =>
{
// back to Unity
//your code here
}, false);
Also note that this is mainly to get the access token. Once we have that, we can make basic WWW calls directly to facebook and get data back from it. The data will be returned as a JSON format (ps this is such an awesome clean format!) you can use the .NET json library to serialize it into a class. I use json2csharp.com to convert any example output into a class which I just parse into the json library.

Powershell - How to print rendered HTML to a network printer? [duplicate]

I would like to create a function in C# that takes a specific webpage and coverts it to a JPG image from within ASP.NET. I don't want to do this via a third party or thumbnail service as I need the full image. I assume I would need to somehow leverage the webbrowser control from within ASP.NET but I just can't see where to get started. Does anyone have examples?
Ok, this was rather easy when I combined several different solutions:
These solutions gave me a thread-safe way to use the WebBrowser from ASP.NET:
http://www.beansoftware.com/ASP.NET-Tutorials/Get-Web-Site-Thumbnail-Image.aspx
http://www.eggheadcafe.com/tutorials/aspnet/b7cce396-e2b3-42d7-9571-cdc4eb38f3c1/build-a-selfcaching-asp.aspx
This solution gave me a way to convert BMP to JPG:
Bmp to jpg/png in C#
I simply adapted the code and put the following into a .cs:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
public class WebsiteToImage
{
private Bitmap m_Bitmap;
private string m_Url;
private string m_FileName = string.Empty;
public WebsiteToImage(string url)
{
// Without file
m_Url = url;
}
public WebsiteToImage(string url, string fileName)
{
// With file
m_Url = url;
m_FileName = fileName;
}
public Bitmap Generate()
{
// Thread
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Capture
var browser = (WebBrowser)sender;
browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ScrollBarsEnabled = false;
m_Bitmap = new Bitmap(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.BringToFront();
browser.DrawToBitmap(m_Bitmap, browser.Bounds);
// Save as file?
if (m_FileName.Length > 0)
{
// Save
m_Bitmap.SaveJPG100(m_FileName);
}
}
}
public static class BitmapExtensions
{
public static void SaveJPG100(this Bitmap bmp, string filename)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(filename, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static void SaveJPG100(this Bitmap bmp, Stream stream)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(stream, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageDecoders();
foreach (var codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
// Return
return null;
}
}
And can call it as follows:
WebsiteToImage websiteToImage = new WebsiteToImage( "http://www.cnn.com", #"C:\Some Folder\Test.jpg");
websiteToImage.Generate();
It works with both a file and a stream. Make sure you add a reference to System.Windows.Forms to your ASP.NET project. I hope this helps.
UPDATE: I've updated the code to include the ability to capture the full page and not require any special settings to capture only a part of it.
Good solution by Mr Cat Man Do.
I've needed to add a row to suppress some errors that came up in some webpages
(with the help of an awesome colleague of mine)
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.ScriptErrorsSuppressed = true; // <--
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
}
...
Thanks Mr Do
Here is my implementation using extension methods and task factory instead thread:
/// <summary>
/// Convert url to bitmap byte array
/// </summary>
/// <param name="url">Url to browse</param>
/// <param name="width">width of page (if page contains frame, you need to pass this params)</param>
/// <param name="height">heigth of page (if page contains frame, you need to pass this params)</param>
/// <param name="htmlToManipulate">function to manipulate dom</param>
/// <param name="timeout">in milliseconds, how long can you wait for page response?</param>
/// <returns>bitmap byte[]</returns>
/// <example>
/// byte[] img = new Uri("http://www.uol.com.br").ToImage();
/// </example>
public static byte[] ToImage(this Uri url, int? width = null, int? height = null, Action<HtmlDocument> htmlToManipulate = null, int timeout = -1)
{
byte[] toReturn = null;
Task tsk = Task.Factory.StartNew(() =>
{
WebBrowser browser = new WebBrowser() { ScrollBarsEnabled = false };
browser.Navigate(url);
browser.DocumentCompleted += (s, e) =>
{
var browserSender = (WebBrowser)s;
if (browserSender.ReadyState == WebBrowserReadyState.Complete)
{
if (htmlToManipulate != null) htmlToManipulate(browserSender.Document);
browserSender.ClientSize = new Size(width ?? browser.Document.Body.ScrollRectangle.Width, height ?? browser.Document.Body.ScrollRectangle.Bottom);
browserSender.ScrollBarsEnabled = false;
browserSender.BringToFront();
using (Bitmap bmp = new Bitmap(browserSender.Document.Body.ScrollRectangle.Width, browserSender.Document.Body.ScrollRectangle.Bottom))
{
browserSender.DrawToBitmap(bmp, browserSender.Bounds);
toReturn = (byte[])new ImageConverter().ConvertTo(bmp, typeof(byte[]));
}
}
};
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
tsk.Wait(timeout);
return toReturn;
}
There is a good article by Peter Bromberg on this subject here. His solution seems to do what you need...
The solution is perfect, just needs a fixation in the line which sets the WIDTH of the image. For pages with a LARGE HEIGHT, it does not set the WIDTH appropriately:
//browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ClientSize = new Size(1000, browser.Document.Body.ScrollRectangle.Bottom);
And for adding a reference to System.Windows.Forms, you should do it in .NET-tab of ADD REFERENCE instead of COM -tab.
You could use WatiN to open a new browser, then capture the screen and crop it appropriately.

Powershell monitoring for file system changes [duplicate]

I have an application where I am looking for a text file and if there are any changes made to the file I am using the OnChanged eventhandler to handle the event. I am using the NotifyFilters.LastWriteTime but still the event is getting fired twice. Here is the code.
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
}
In my case the OnChanged is called twice, when I change the text file version.txt and save it.
I am afraid that this is a well-known bug/feature of the FileSystemWatcher class. This is from the documentation of the class:
You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.
Now this bit of text is about the Created event, but the same thing applies to other file events as well. In some applications you might be able to get around this by using the NotifyFilter property, but my experience is says that sometimes you have to do some manual duplicate filtering (hacks) as well.
A while ago I bookedmarked a page with a few FileSystemWatcher tips. You might want to check it out.
I've "fixed" that problem using the following strategy in my delegate:
// fsw_ is the FileSystemWatcher instance used by my application.
private void OnDirectoryChanged(...)
{
try
{
fsw_.EnableRaisingEvents = false;
/* do my stuff once asynchronously */
}
finally
{
fsw_.EnableRaisingEvents = true;
}
}
Any duplicated OnChanged events from the FileSystemWatcher can be detected and discarded by checking the File.GetLastWriteTime timestamp on the file in question. Like so:
DateTime lastRead = DateTime.MinValue;
void OnChanged(object source, FileSystemEventArgs a)
{
DateTime lastWriteTime = File.GetLastWriteTime(uri);
if (lastWriteTime != lastRead)
{
doStuff();
lastRead = lastWriteTime;
}
// else discard the (duplicated) OnChanged event
}
Here is my solution which helped me to stop the event being raised twice:
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
Here I have set the NotifyFilter property with only Filename and size.
watcher is my object of FileSystemWatcher. Hope this will help.
Here's my approach :
// Consider having a List<String> named _changedFiles
private void OnChanged(object source, FileSystemEventArgs e)
{
lock (_changedFiles)
{
if (_changedFiles.Contains(e.FullPath))
{
return;
}
_changedFiles.Add(e.FullPath);
}
// do your stuff
System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
{
lock (_changedFiles)
{
_changedFiles.Remove(e.FullPath);
}
};
timer.Start();
}
This is the solution I used to solve this issue on a project where I was sending the file as attachment in a mail.
It will easily avoid the twice fired event even with a smaller timer interval but in my case 1000 was alright since I was happier with missing few changes than with flooding the mailbox with > 1 message per second.
At least it works just fine in case several files are changed at the exact same time.
Another solution I've thought of would be to replace the list with a dictionary mapping files to their respective MD5, so you wouldn't have to choose an arbitrary interval since you wouldn't have to delete the entry but update its value, and cancel your stuff if it hasn't changed.
It has the downside of having a Dictionary growing in memory as files are monitored and eating more and more memory, but I've read somewhere that the amount of files monitored depends on the FSW's internal buffer, so maybe not that critical.
Dunno how MD5 computing time would affect your code's performances either, careful =\
My scenario is that I have a virtual machine with a Linux server in it. I am developing files on the Windows host. When I change something in a folder on the host I want all the changes to be uploaded, synced onto the virtual server via Ftp. This is how I do eliminate the duplicate change event when I write to a file ( which flags the folder containing the file to be modified as well ) :
private Hashtable fileWriteTime = new Hashtable();
private void fsw_sync_Changed(object source, FileSystemEventArgs e)
{
string path = e.FullPath.ToString();
string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString();
// if there is no path info stored yet
// or stored path has different time of write then the one now is inspected
if ( !fileWriteTime.ContainsKey(path) ||
fileWriteTime[path].ToString() != currentLastWriteTime
)
{
//then we do the main thing
log( "A CHANGE has occured with " + path );
//lastly we update the last write time in the hashtable
fileWriteTime[path] = currentLastWriteTime;
}
}
Mainly I create a hashtable to store file write time information. Then if the hashtable has the filepath that is modified and it's time value is the same as the currently notified file's change then I know it is the duplicate of the event and ignore it.
I have created a Git repo with a class that extends FileSystemWatcher to trigger the events only when copy is done. It discards all the changed events exept the last and it raise it only when the file become available for read.
Download FileSystemSafeWatcher and add it to your project.
Then use it as a normal FileSystemWatcher and monitor when the events are triggered.
var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Try with this code:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
I know this is an old issue, but had the same problem and none of the above solution really did the trick for the problem I was facing. I have created a dictionary which maps the file name with the LastWriteTime. So if the file is not in the dictionary will go ahead with the process other wise check to see when was the last modified time and if is different from what it is in the dictionary run the code.
Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>();
private void OnChanged(object source, FileSystemEventArgs e)
{
if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
{
dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
//your code here
}
}
One possible 'hack' would be to throttle the events using Reactive Extensions for example:
var watcher = new FileSystemWatcher("./");
Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
.Throttle(new TimeSpan(500000))
.Subscribe(HandleChangeEvent);
watcher.EnableRaisingEvents = true;
In this case I'm throttling to 50ms, on my system that was enough, but higher values should be safer. (And like I said, it's still a 'hack').
I spent some significant amount of time using the FileSystemWatcher, and some of the approaches here will not work. I really liked the disabling events approach, but unfortunately, it doesn't work if there is >1 file being dropped, second file will be missed most if not all times.
So I use the following approach:
private void EventCallback(object sender, FileSystemEventArgs e)
{
var fileName = e.FullPath;
if (!File.Exists(fileName))
{
// We've dealt with the file, this is just supressing further events.
return;
}
// File exists, so move it to a working directory.
File.Move(fileName, [working directory]);
// Kick-off whatever processing is required.
}
I have a very quick and simple workaround here, it does work for me, and no matter the event would be triggered once or twice or more times occasionally, check it out:
private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
fireCount++;
if (fireCount == 1)
{
MessageBox.Show("Fired only once!!");
dowork();
}
else
{
fireCount = 0;
}
}
}
Here is a new solution you can try. Works well for me. In the event handler for the changed event programmatically remove the handler from the designer output a message if desired then programmatically add the handler back. example:
public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
{
fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
MessageBox.Show( "File has been uploaded to destination", "Success!" );
fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
}
The main reason was
first event's last access time was current time(file write or changed time).
then second event was file's original last access time.
I solve under code.
var lastRead = DateTime.MinValue;
Watcher = new FileSystemWatcher(...)
{
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
Filter = "*.dll",
IncludeSubdirectories = false,
};
Watcher.Changed += (senderObject, ea) =>
{
var now = DateTime.Now;
var lastWriteTime = File.GetLastWriteTime(ea.FullPath);
if (now == lastWriteTime)
{
return;
}
if (lastWriteTime != lastRead)
{
// do something...
lastRead = lastWriteTime;
}
};
Watcher.EnableRaisingEvents = true;
This code worked for me.
private void OnChanged(object source, FileSystemEventArgs e)
{
string fullFilePath = e.FullPath.ToString();
string fullURL = buildTheUrlFromStudyXML(fullFilePath);
System.Diagnostics.Process.Start("iexplore", fullURL);
Timer timer = new Timer();
((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
private void t_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer)sender).Stop();
theWatcher.Changed += new FileSystemEventHandler(OnChanged);
}
mostly for future me :)
I wrote a wrapper using Rx:
public class WatcherWrapper : IDisposable
{
private readonly FileSystemWatcher _fileWatcher;
private readonly Subject<FileSystemEventArgs> _infoSubject;
private Subject<FileSystemEventArgs> _eventSubject;
public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
{
_fileWatcher = new FileSystemWatcher(path, nameFilter);
if (notifyFilters != null)
{
_fileWatcher.NotifyFilter = notifyFilters.Value;
}
_infoSubject = new Subject<FileSystemEventArgs>();
_eventSubject = new Subject<FileSystemEventArgs>();
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
// this takes care of double events and still works with changing the name of the same file after a while
_infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
.Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
infos =>
{
if (infos != null)
foreach (var info in infos)
{
{
_eventSubject.OnNext(info);
}
}
});
_fileWatcher.EnableRaisingEvents = true;
}
public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;
public void Dispose()
{
_fileWatcher?.Dispose();
_eventSubject.Dispose();
_infoSubject.Dispose();
}
}
Usage:
var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)
Try this, It's working fine
private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
static void Main(string[] args)
{
Console.WriteLine("Watching....");
Watcher.Path = #"D:\Temp\Watcher";
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
Console.ReadKey();
}
static void OnChanged(object sender, FileSystemEventArgs e)
{
try
{
Watcher.Changed -= OnChanged;
Watcher.EnableRaisingEvents = false;
Console.WriteLine($"File Changed. Name: {e.Name}");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
finally
{
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
}
}
I wanted to react only on the last event, just in case, also on a linux file change it seemed that the file was empty on the first call and then filled again on the next and did not mind loosing some time just in case the OS decided to do some file/attribute change.
I am using .NET async here to help me do the threading.
private static int _fileSystemWatcherCounts;
private async void OnChanged(object sender, FileSystemEventArgs e)
{
// Filter several calls in short period of time
Interlocked.Increment(ref _fileSystemWatcherCounts);
await Task.Delay(100);
if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
DoYourWork();
}
I think the best solution to solve the issue is to use reactive extensions
When you transform event into observable, then you can just add Throttling(..) (originally called Debounce(..))
Sample code here
var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
{
NotifyFilter = NotifyFilters.LastWrite,
IncludeSubdirectories = true
};
templatesWatcher.EnableRaisingEvents = true;
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
addHandler => templatesWatcher.Changed += addHandler,
removeHandler => templatesWatcher.Changed -= removeHandler)
.Throttle(TimeSpan.FromSeconds(5))
.Subscribe(args =>
{
_logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
//TODO do something
});
You could try to open it for write, and if successful then you could assume the other application is done with the file.
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
using (var fs = File.OpenWrite(e.FullPath))
{
}
//do your stuff
}
catch (Exception)
{
//no write access, other app not done
}
}
Just opening it for write appears not to raise the changed event. So it should be safe.
FileReadTime = DateTime.Now;
private void File_Changed(object sender, FileSystemEventArgs e)
{
var lastWriteTime = File.GetLastWriteTime(e.FullPath);
if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
{
// code
FileReadTime = DateTime.Now;
}
}
Sorry for the grave dig, but I've been battling this issue for a while now and finally came up with a way to handle these multiple fired events. I would like to thank everyone in this thread as I have used it in many references when battling this issue.
Here is my complete code. It uses a dictionary to track the date and time of the last write of the file. It compares that value, and if it is the same, it suppresses the events. It then sets the value after starting the new thread.
using System.Threading; // used for backgroundworker
using System.Diagnostics; // used for file information
private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events
private void fswFileWatch_Changed( object sender, FileSystemEventArgs e )
{
try
{
//check if we already have this value in our dictionary.
if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) )
{
//compare timestamps
if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() )
{
//lock the table
lock ( fileModifiedTable )
{
//make sure our file is still valid
if ( File.Exists( e.FullPath ) )
{
// create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers
BackgroundWorker newThreadWork = new BackgroundWorker();
newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork );
newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted );
// capture the path
string eventFilePath = e.FullPath;
List<object> arguments = new List<object>();
// add arguments to pass to the background worker
arguments.Add( eventFilePath );
arguments.Add( newEvent.File_Modified );
// start the new thread with the arguments
newThreadWork.RunWorkerAsync( arguments );
fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file.
FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on.
}
}
}
}
}
catch ( IOException IOExcept )
{
//catch any errors
postError( IOExcept, "fswFileWatch_Changed" );
}
}
Event if not asked, it is a shame there are no ready solution samples for F#.
To fix this here is my recipe, just because I can and F# is a wonderful .NET language.
Duplicated events are filtered out using FSharp.Control.Reactive package, which is just a F# wrapper for reactive extensions. All that can be targeted to full framework or netstandard2.0:
let createWatcher path filter () =
new FileSystemWatcher(
Path = path,
Filter = filter,
EnableRaisingEvents = true,
SynchronizingObject = null // not needed for console applications
)
let createSources (fsWatcher: FileSystemWatcher) =
// use here needed events only.
// convert `Error` and `Renamed` events to be merded
[| fsWatcher.Changed :> IObservable<_>
fsWatcher.Deleted :> IObservable<_>
fsWatcher.Created :> IObservable<_>
//fsWatcher.Renamed |> Observable.map renamedToNeeded
//fsWatcher.Error |> Observable.map errorToNeeded
|] |> Observable.mergeArray
let handle (e: FileSystemEventArgs) =
printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath
let watch path filter throttleTime =
// disposes watcher if observer subscription is disposed
Observable.using (createWatcher path filter) createSources
// filter out multiple equal events
|> Observable.distinctUntilChanged
// filter out multiple Changed
|> Observable.throttle throttleTime
|> Observable.subscribe handle
[<EntryPoint>]
let main _args =
let path = #"C:\Temp\WatchDir"
let filter = "*.zip"
let throttleTime = TimeSpan.FromSeconds 10.
use _subscription = watch path filter throttleTime
System.Console.ReadKey() |> ignore
0 // return an integer exit code
In my case need to get the last line of a text file that is inserted by other application, as soon as insertion is done. Here is my solution. When the first event is raised, i disable the watcher from raising others, then i call the timer TimeElapsedEvent because when my handle function OnChanged is called i need the size of the text file, but the size at that time is not the actual size, it is the size of the file imediatelly before the insertion. So i wait for a while to proceed with the right file size.
private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
...
private void OnChanged(object source, FileSystemEventArgs e)
{
System.Timers.Timer t = new System.Timers.Timer();
try
{
watcher.Changed -= new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = false;
t.Interval = 500;
t.Elapsed += (sender, args) => t_Elapsed(sender, e);
t.Start();
}
catch(Exception ex) {
;
}
}
private void t_Elapsed(object sender, FileSystemEventArgs e)
{
((System.Timers.Timer)sender).Stop();
//.. Do you stuff HERE ..
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
Here is another approach. Instead of propagating the first event of a quick succession of events and suppressing all that follow, now all are suppressed except from the last one. I think that the scenarios that can benefit from this approach are more common.
To make this happen we must use a sliding delay. Every incoming event cancels the timer that would fire the previous event, and restarts the timer. This opens the possibility that a never-ending series of events will delay the propagation forever. To keep things simple, there is no provision for this abnormal case in the extension methods below.
public static class FileSystemWatcherExtensions
{
public static IDisposable OnAnyEvent(this FileSystemWatcher source,
WatcherChangeTypes changeTypes, FileSystemEventHandler handler, int delay)
{
var cancellations = new Dictionary<string, CancellationTokenSource>(
StringComparer.OrdinalIgnoreCase);
var locker = new object();
if (changeTypes.HasFlag(WatcherChangeTypes.Created))
source.Created += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Deleted))
source.Deleted += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Changed))
source.Changed += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Renamed))
source.Renamed += FileSystemWatcher_Event;
return new Disposable(() =>
{
source.Created -= FileSystemWatcher_Event;
source.Deleted -= FileSystemWatcher_Event;
source.Changed -= FileSystemWatcher_Event;
source.Renamed -= FileSystemWatcher_Event;
});
async void FileSystemWatcher_Event(object sender, FileSystemEventArgs e)
{
var key = e.FullPath;
var cts = new CancellationTokenSource();
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing))
{
existing.Cancel();
}
cancellations[key] = cts;
}
try
{
await Task.Delay(delay, cts.Token);
// Omitting ConfigureAwait(false) is intentional here.
// Continuing in the captured context is desirable.
}
catch (TaskCanceledException)
{
return;
}
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing)
&& existing == cts)
{
cancellations.Remove(key);
}
}
cts.Dispose();
handler(sender, e);
}
}
public static IDisposable OnAllEvents(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.All, handler, delay);
public static IDisposable OnCreated(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Created, handler, delay);
public static IDisposable OnDeleted(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Deleted, handler, delay);
public static IDisposable OnChanged(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Changed, handler, delay);
public static IDisposable OnRenamed(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Renamed, handler, delay);
private struct Disposable : IDisposable
{
private readonly Action _action;
internal Disposable(Action action) => _action = action;
public void Dispose() => _action?.Invoke();
}
}
Usage example:
myWatcher.OnAnyEvent(WatcherChangeTypes.Created | WatcherChangeTypes.Changed,
MyFileSystemWatcher_Event, 100);
This line combines the subscription to two events, the Created and the Changed. So it is roughly equivalent to these:
myWatcher.Created += MyFileSystemWatcher_Event;
myWatcher.Changed += MyFileSystemWatcher_Event;
The difference is that the two events are regarded as a single type of event, and in case of a quick succession of these events only the last one will be propagated. For example if a Created event is followed by two Changed events, and there is no time gap larger than 100 msec between these three events, only the second Changed event will be propagated by invoking the MyFileSystemWatcher_Event handler, and the previous ones will be discarded.
I have changed the way I monitor files in directories. Instead of using the FileSystemWatcher I poll locations on another thread and then look at the LastWriteTime of the file.
DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);
Using this information and keeping an index of a file path and it's latest write time I can determine files that have changed or that have been created in a particular location. This removes me from the oddities of the FileSystemWatcher. The main downside is that you need a data structure to store the LastWriteTime and the reference to the file, but it is reliable and easy to implement.
I was able to do this by added a function that checks for duplicates in an buffer array.
Then perform the action after the array has not been modified for X time using a timer:
- Reset timer every time something is written to the buffer
- Perform action on tick
This also catches another duplication type. If you modify a file inside a folder, the folder also throws a Change event.
Function is_duplicate(str1 As String) As Boolean
If lb_actions_list.Items.Count = 0 Then
Return False
Else
Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim
If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
Return False
Else
Return True
End If
End If
End Function
Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module
This solution worked for me on production application:
Environment:
VB.Net Framework 4.5.2
Set manually object properties: NotifyFilter = Size
Then use this code:
Public Class main
Dim CalledOnce = False
Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
If (CalledOnce = False) Then
CalledOnce = True
If (e.ChangeType = 4) Then
' Do task...
CalledOnce = False
End If
End Sub
End Sub
Try this!
string temp="";
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
if(temp=="")
{
//do thing you want.
temp = e.name //name of text file.
}else if(temp !="" && temp != e.name)
{
//do thing you want.
temp = e.name //name of text file.
}else
{
//second fire ignored.
}
}
I had to combine several ideas from the posts above and add file locking check to get it working for me:
FileSystemWatcher fileSystemWatcher;
private void DirectoryWatcher_Start()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
{
Path = #"c:\mypath",
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.*",
EnableRaisingEvents = true
};
fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}
private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
Int32 waitMS = 250;
Int32 currentMS = 0;
FileInfo file = new FileInfo(fullPath);
FileStream stream = null;
do
{
try
{
stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
stream.Close();
callback(fullPath);
return;
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Dispose();
}
Thread.Sleep(waitMS);
currentMS += waitMS;
} while (currentMS < timeoutMS);
}
private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();
private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
try
{
lock (DirectoryWatcher_fileLastWriteTimeCache)
{
DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
{
if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
return; // file was already handled
}
DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
}
Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
{
// do the job with fullPath...
}));
}
catch (Exception e)
{
// handle exception
}
}