I recently came across this code:
public static class ClientBaseExtender
{
/// <summary>
/// Tries to execute async service call. If <see cref="TimeoutException"/> occured retries again.
/// </summary>
/// <typeparam name="TChannel">ServiceClient class.</typeparam>
/// <typeparam name="TArgs">Type of service client method return argument.</typeparam>
/// <param name="client">ServiceClient instance.</param>
/// <param name="tryExecute">Delegate that execute starting of service call.</param>
/// <param name="onCompletedSubcribe">Delegate that subcribes an event handler to the OnCompleted event of the service client method.</param>
/// <param name="onCompleted">Delegate that executes when service call is succeeded.</param>
/// <param name="onError">Delegate that executes when service call fails.</param>
/// <param name="maxAttempts">Maximum attempts to execute service call before error if <see cref="TimeoutException"/> occured (by default 5).</param>
public static void ExecuteAsyncRepeatedly<TChannel, TArgs>(this ClientBase<TChannel> client, Action tryExecute,
Action<EventHandler<TArgs>> onCompletedSubcribe, EventHandler<TArgs> onCompleted,
EventHandler<TArgs> onError, int maxAttempts)
where TChannel : class
where TArgs : AsyncCompletedEventArgs
{
int attempts = 0;
var serviceName = client.GetType().Name;
onCompletedSubcribe((s, e) =>
{
if (e.Error == null) // Everything is OK
{
if (onCompleted != null)
onCompleted(s, e);
((ICommunicationObject)client).Close();
Debug.WriteLine("[{1}] Service '{0}' closed.", serviceName, DateTime.Now);
}
else if (e.Error is TimeoutException)
{
attempts++;
if (attempts >= maxAttempts) // Final timeout after n attempts
{
Debug.WriteLine("[{2}], Final Timeout occured in '{0}' service after {1} attempts.", serviceName, attempts, DateTime.Now);
if (onError != null)
onError(s, e);
client.Abort();
Debug.WriteLine("[{1}] Service '{0}' aborted.", serviceName, DateTime.Now);
return;
}
// Local timeout
Debug.WriteLine("[{2}] Timeout occured in '{0}' service (attempt #{1}).", serviceName, attempts, DateTime.Now);
Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
tryExecute(); // Try again.
}
else
{
if (onError != null)
onError(s, e);
client.Abort();
Debug.WriteLine("[{1}] Service '{0}' aborted.", serviceName, DateTime.Now);
}
});
Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
tryExecute(); // First attempt to execute
}
}
public void GetData()
{
var client = new MyServiceClient();
client.ExecuteAsyncRepeatedly(() => client.MyOperationAsync(...),
(EventHandler<MyOperationCompletedEventArgs> handler) =>client.MyOperationCompleted += handler,
(s, e) => // OnCompleted
{
Do(e.Result);
},
(s, e) => // OnError
{
HandleError(e.Error);
}
);
}
The problem is, I have a button that fires this code off. When the button is pushed more than once the handler gets added again and again. This is a problem because the code will fire as many times as the user has pushed the button. How can I remove the handler created with the lambda expression in this code so it will only run once?
Thanks!
EDIT:
I'm calling the code like this from my button click command:
_dataService.GetData(GetDataCompleted);
private void GetDataComplete(Data data)
{
//do something with data }
I think that you can solve it by implementing a push-pull strategy in your code-behind. I propose something similar to this:
bool _requestPending;
readonly object _lock = new object();
void OnClick (...)
{
lock(_lock)
{
if (_requestPending == false)
{
_dataService.GetData(GetDataCompleted);
_requestPending = true;
}
}
}
private void GetDataComplete(Data data)
{
lock(_lock)
{
try
{
//do something with data
}
finally
{
_requestPending = false;
}
}
}
Even better, disable the UI button when you have a pending request. You wouldn't have any concurrency issues with accessing and modifying the _requestPending from different threads, but still you could suffer a race condition if the service response is fast enough, so still better to synchronize the two blocks of code.
Anyway, personally I don't like this implementation for what you're trying to achieve. The code is quite confusing, and makes it hard to foresee problems that may arise. Make sure that:
you provide a way to abort a request
and reenable the button again
the code that updates the screen is
executed by the UI thread
Related
Here's a demo of code that is calling an "old library" which if successful, returns nothing, and if error, throws an exception.
public void ExampleCallOldCode()
{
List<string> users = new List<string>() {"user123", "user456"};
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
DoSomethingSynchronously(userId);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void DoSomethingSynchronously(string userId)
{
if (userId.Contains("123"))
Console.WriteLine($"Doing something with {userId}");
else
throw new Exception("UserId needs to contain 123.");
}
We are now upgrading/integrating to a "new library" which performs the work asynchronously (which uses batching/queuing logic behind the scenese) and uses callbacks to notify of success or failure.
FYI: The "old library" is a wrapper that send emails. The new library is Segment's Analytics.Net package
I don't want to change too much of the old code (it is used in MANY places).
How do I wait synchronously for the new library to complete and invoke the callback function?
e.g. Should I use AutoResetEvents and call WaitOne?
Or are there better options?
How do I handle errors?
Would I create a wrapper and throw an exception in the failure callback function?
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
// ****
// the old code structure I want to preserve. HOW DO I MAKE THIS WORK ???
// ****
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
If I understand your question correctly then you can solve this problem by using TaskCompletionSource.
For the sake of simplicity I've created a dummy class which might raise an OnSuccess or an OnFailure event depending on the provided parameter:
public class Dummy
{
public event EventHandler OnFailure;
public event EventHandler OnSuccess;
public void DoWork(int i)
{
if (i % 2 == 0) OnFailure?.Invoke(this, null);
else OnSuccess?.Invoke(this, null);
}
}
On the consumer side you can do the following:
private static TaskCompletionSource<object> signalling = new TaskCompletionSource<object>();
public static async Task Main(string[] args)
{
Console.WriteLine("Calling new code");
var dummy = new Dummy();
dummy.OnSuccess += Dummy_OnSuccess;
dummy.OnFailure += Dummy_OnFailure;
dummy.DoWork(2);
try
{
await signalling.Task;
Console.WriteLine("New code has finished");
}
catch (Exception)
{
Console.WriteLine("New code has failed");
}
Console.WriteLine("Calling old code");
}
private static void Dummy_OnFailure(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetException(new Exception("Operation failed"));
}
private static void Dummy_OnSuccess(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetResult(null);
}
Whenever the OnFailure event is emitted then call the TrySetException on the TaskCompletionSource to indicate that the operation has been finished without luck
Whenever the OnSuccess event is emitted the we call the TrySetResult on the TaskCompletionSource to indicate that the operation has been finished with luck
We are await-ing the TaskCompletionSource that's why we can be sure that either the requested operation succeeded or failed after the line of await
Regarding using the segment library, the way you get to know if an action (Identify, Group, Track, Alias, Page or Screen) succeeds or fails when being sent to Segment Server is setting up callback handlers like you did (Failed, Succeeded) and also the Logger Handler.
As your example above, both Succeeded and Failed callbacks receive by parameter the BaseAction which allows you to have information regarding the Action that succeeded or failed.
As you have mentioned, Analytics.NET library sends it Actions asynchronously enqueuing them and when the queue is full then it proceeds to flush those actions and send them in a batch to the segment server. So if you want to send automatically to Segment's server once you invoke any of the Actions, you should call Analytics.Client.Flush() so as to get inmediate feedback on the Succeeded or Failed Callback and the Loggers handler. In your code it will be:
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Analytics.Client.Flush();
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
By adding a while(ele == null) loop, I got the code to run and work most of the time out of the debugger. Pretty ugly. This leads me to think that I need to over ride the FindElements() function using a wrapper but have no idea how to do this to add some delay. There is an example at Explicit Wait for findElements in Selenium Webdriver but it's written in JavaScript. I put that example in the code below. Can some one guide me on this?
public void WriteAPost()
{
ele = driver.FindElements(By.CssSelector(".a8c37x1j.ni8dbmo4.stjgntxs.l9j0dhe7.ltmttdrg.g0qnabr5.ojkyduve")).FirstOrDefault(x => x.Text == "Create Post");
while(ele == null)
{
ele = driver.FindElements(By.CssSelector(".a8c37x1j.ni8dbmo4.stjgntxs.l9j0dhe7.ltmttdrg.g0qnabr5.ojkyduve")).FirstOrDefault(x => x.Text == "Create Post");
}
ele.Click();
Thread.Sleep(3000);
ele = driver.SwitchTo().ActiveElement();
PClipboard.SetText("Post text to use for Text Area");
ele.SendKeys(OpenQA.Selenium.Keys.Control + 'v');
Thread.Sleep(3000);
ele = driver.FindElements(By.XPath("//div[#role = 'button']")).FirstOrDefault(x => x.Text == "Post");
while (ele == null)
{
ele = driver.FindElements(By.XPath("//div[#role = 'button']")).FirstOrDefault(x => x.Text == "Post");
}
ele.Click();
Thread.Sleep(3000);
driver.Quit();
}
static class PClipboard
{
public static void SetText(string p_Text)
{
Thread STAThread = new Thread(
delegate ()
{
// Use a fully qualified name for Clipboard otherwise it
// will end up calling itself.
System.Windows.Forms.Clipboard.SetText(p_Text);
});
STAThread.SetApartmentState(ApartmentState.STA);
STAThread.Start();
STAThread.Join();
}
}
}
// Javascript FindElements() wrapper
/// <summary>
/// Allows you to execute the FindElements call but specify your own timeout explicitly for this single lookup
/// </summary>
/// <remarks>
/// If you want no timeout, you can pass in TimeSpan.FromSeconds(0) to return an empty list if no elements match immediately. But then you may as well use the original method
/// </remarks>
/// <param name="driver">The IWebDriver instance to do the lookup with</param>
/// <param name="findBy">The By expression to use to find matching elements</param>
/// <param name="timeout">A timespan specifying how long to wait for the element to be available</param>
public static ReadOnlyCollection<IWebElement> FindElements(this IWebDriver driver, By findBy, TimeSpan timeout)
{
var wait = new WebDriverWait(driver, timeout);
return wait.Until((d) =>
{
var elements = d.FindElements(findBy);
return (elements.Count > 0)
? elements
: null;
});
}
You can wait for a condition to be met:
new WebDriverWait(driver, timeout).Until(ExpectedConditions.ElementExists((By.Id(id))));
or you can wait implicitly:
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(timeInSeconds);
I just don't know where in the code to wait.
Personally, my Selenium code is full of "test failure reporting" tests and try-catch blocks which report where the failure happened. If you model your code this way, it should narrow down the problem and clue you to where waits are needed.
I'm writing a Portable Class Library that is going to be used by WPF, Windows Phone and possibly WinRT apps and I'm doing some work on background threads that occasionally need to call back to the UI. I instantiate the classes doing this in the UI thread, so I can easily save the SynchronizationContext and use it to call back to the UI.
However, in PCL, SynchronizationContext.Send() is obsolete, because it's not supported by WinRT and SynchronizationContext.Post() (which runs asynchronously) is not always appropriate.
I figured I'd just wait until the delegate passed to Post() is run, but all my attempts ended with a deadlock if Post() was invoked from the same thread the saved SynchronizationContext referred to.
Now I've managed to fix this by checking if it's the same thread and just simply calling my delegate if it is, but the checks are incredibly ugly involving reflecting out the value of private fields of the API, so I thought someone could help me find a more proper way.
Here is my current code if you'd like to see some gore:
/// <summary>
/// Invokes the passed callback on this SynchronizationContext and waits for its execution. Can be used even if
/// SynchronizationContext.Send is not available. Throws the exceptions thrown in the delegate.
/// </summary>
/// <param name="context">the context to run the method</param>
/// <param name="d">the method to run</param>
/// <param name="state">the parameter of the method to run</param>
public static void InvokeSynchronized( this SynchronizationContext context, SendOrPostCallback d, object state )
{
if ( !context.Match( SynchronizationContext.Current ) )
{
ManualResetEvent waitHandle = new ManualResetEvent( false );
Exception error = null;
// replicate SynchronizationContext.Send with .Post as Send is obsolete in the Portable Class Library
context.Post( ( o ) =>
{
try
{
d( o );
}
catch ( Exception exc )
{
error = exc;
}
finally
{
waitHandle.Set();
}
},
state );
waitHandle.WaitOne();
if ( error != null )
{
throw error;
}
}
else
{
d( state );
}
}
/// <summary>
/// Checks if the two SynchronizationContexts refer to the same thread
/// </summary>
/// <param name="sc1"></param>
/// <param name="sc2"></param>
/// <returns></returns>
public static bool Match(this SynchronizationContext sc1, SynchronizationContext sc2)
{
if ( sc2 == null )
{
return false;
}
else if ( sc1 == sc2 || sc1.Equals(sc2) )
{
return true;
}
// check if the two contexts run on the same thread
// proper equality comparison is generally not supported, so some hacking is required
return sc1.FindManagedThreadId() == sc2.FindManagedThreadId();
}
/// <summary>
/// Finds the ManagedThreadId of the thread associated with the passed SynchronizationContext
/// </summary>
/// <param name="sc"></param>
/// <returns></returns>
public static int FindManagedThreadId(this SynchronizationContext sc)
{
// here be dragons
try
{
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
switch ( sc.GetType().FullName )
{
case "System.Windows.Threading.DispatcherSynchronizationContext":
// sc._dispatcher.Thread.ManagedThreadId
var _dispatcher_field = sc.GetType().GetField( "_dispatcher", bindFlags );
var _dispatcher_value = _dispatcher_field.GetValue( sc );
var Thread_property = _dispatcher_value.GetType().GetProperty( "Thread", bindFlags );
var Thread_value = Thread_property.GetValue( _dispatcher_value, null ) as Thread;
return Thread_value.ManagedThreadId;
}
}
catch ( Exception e )
{
throw new InvalidOperationException( "ManagedThreadId could not be obtained for SynchronizationContext of type " + sc.GetType().FullName, e );
}
throw new InvalidOperationException( "ManagedThreadId not found for SynchronizationContext of type " + sc.GetType().FullName );
}
Thanks!
I think that SynchronizationContext.Send is being deprecated by Microsoft for a good reason. They really want the new Windows Store and WP8 apps to fully embrace asynchronous programming model and make the blocking code a thing of the past.
So, something that was:
void SendDataToUI()
{
_synchronizationContext.Send(_callback, data);
}
Should now become:
async Task SendDataToUIAsync()
{
var tcs = new TaskCompletionSource<object>();
_synchronizationContext.Post(a =>
{
try
{
_callback(a);
tcs.SetResult(Type.Missing);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, data);
await tcs.Task;
}
That said, I suppose you have your own good reasons to use SynchronizationContext.Send in your PCL library.
The first part of your logic looks good, and you could cut off the reflection part of it by simply memorizing the Thread.CurrentThread.ManagedThreadId of the UI thread, at the same place where you memorize the SynchronizationContext.Current of the UI thread. Then in your implementation of InvokeSynchronized you just compare it to the Thread.CurrentThread.ManagedThreadId of the current thread, and use waitHandle.WaitOne() if your are on non-UI thread.
I can't work out how to resume an interrupted upload in V3 of the C# YouTube API.
My existing code uses V1 and works fine but I'm switching to V3.
If I call UploadAsync() without changing anything, it starts from the beginning. Using Fiddler, I can see the protocol given here is not followed and the upload restarts.
I've tried setting the position within the stream as per V1 but there is no ResumeAsync() method available.
The Python example uses NextChunk but the SendNextChunk method is protected and not available in C#.
In the code below, both UploadVideo() and Resume() work fine if I leave them to completion but the entire video is uploaded instead of just the remaining parts.
How do I resume an interrupted upload using google.apis.youtube.v3?
Here is the C# code I have tried so far.
private ResumableUpload<Video> UploadVideo(
YouTubeService youTubeService, Video video, Stream stream, UserCredential userCredentials)
{
var resumableUpload = youTubeService.Videos.Insert(video,
"snippet,status,contentDetails", stream, "video/*");
resumableUpload.OauthToken = userCredentials.Token.AccessToken;
resumableUpload.ChunkSize = 256 * 1024;
resumableUpload.ProgressChanged += resumableUpload_ProgressChanged;
resumableUpload.ResponseReceived += resumableUpload_ResponseReceived;
resumableUpload.UploadAsync();
return resumableUpload;
}
private void Resume(ResumableUpload<Video> resumableUpload)
{
//I tried seeking like V1 but it doesn't work
//if (resumableUpload.ContentStream.CanSeek)
// resumableUpload.ContentStream.Seek(resumableUpload.ContentStream.Position, SeekOrigin.Begin);
resumableUpload.UploadAsync(); // <----This restarts the upload
}
void resumableUpload_ResponseReceived(Video obj)
{
Debug.WriteLine("Video status: {0}", obj.Status.UploadStatus);
}
void resumableUpload_ProgressChanged(IUploadProgress obj)
{
Debug.WriteLine("Position: {0}", (resumableUploadTest == null) ? 0 : resumableUploadTest.ContentStream.Position);
Debug.WriteLine("Status: {0}", obj.Status);
Debug.WriteLine("Bytes sent: {0}", obj.BytesSent);
}
private void button2_Click(object sender, EventArgs e)
{
Resume(resumableUploadTest);
}
Any solution/suggestion/demo or a link to the "google.apis.youtube.v3" source code will be very helpful.
Thanks in Advance !
EDIT: New information
I'm still working on this and I believe the API simply isn't finished. Either that or I'm missing something simple.
I still can't find the "google.apis.youtube.v3" source code so I downloaded the latest "google-api-dotnet-client" source code. This contains the ResumableUpload class used by the YouTube API.
I managed to successfully continue an upload by skipping the first four lines of code in the UploadAsync() method. I created a new method called ResumeAsync(), a copy of UploadAsync() with the first four lines of initialization code removed. Everything worked and the upload resumed from where it was and completed.
I'd rather not be changing code in the API so if anyone knows how I should be using this, let me know.
I'll keep plugging away and see if I can work it out.
This is the original UploadAsync() method and my ResumeAsync() hack.
public async Task<IUploadProgress> UploadAsync(CancellationToken cancellationToken)
{
try
{
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
Logger.Debug("MediaUpload[{0}] - Start uploading...", UploadUri);
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Task was canceled", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Exception occurred while uploading media", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
public async Task<IUploadProgress> ResumeAsync(CancellationToken cancellationToken)
{
try
{
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
These are the Fiddler records showing the upload resuming.
After a fair bit of deliberation, I've decided to modify the API code. My solution maintains backwards compatibility.
I've documented my changes below but I don't recommend using them.
In the UploadAsync() method in the ResumableUpload Class in "Google.Apis.Upload", I replaced this code.
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
with this code
UpdateProgress(new ResumableUploadProgress(
BytesServerReceived == 0 ? UploadStatus.Starting : UploadStatus.Resuming, BytesServerReceived));
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
if (UploadUri == null) UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
I also made the UploadUri and BytesServerReceived properties public. This allows an upload to be continued after the ResumableUpload object has been destroyed or after an application restart.
You just recreate the ResumableUpload as per normal, set these two fields and call UploadAsync() to resume an upload. Both fields need to be saved during the original upload.
public Uri UploadUri { get; set; }
public long BytesServerReceived { get; set; }
I also added "Resuming" to the UploadStatus enum in the IUploadProgress class.
public enum UploadStatus
{
/// <summary>
/// The upload has not started.
/// </summary>
NotStarted,
/// <summary>
/// The upload is initializing.
/// </summary>
Starting,
/// <summary>
/// Data is being uploaded.
/// </summary>
Uploading,
/// <summary>
/// Upload is being resumed.
/// </summary>
Resuming,
/// <summary>
/// The upload completed successfully.
/// </summary>
Completed,
/// <summary>
/// The upload failed.
/// </summary>
Failed
};
Nothing has changed for starting an upload.
Provided the ResumableUpload Oject and streams have not been destroyed, call UploadAsync() again to resume an interrupted upload.
If they have been destroyed, create new objects and set the UploadUri and BytesServerReceived properties. These two properties can be saved during the original upload. The video details and content stream can be configured as per normal.
These few changes allow an upload to be resumed even after restarting your application or rebooting. I'm not sure how long before an upload expires but I'll report back when I've done some more testing with my real application.
Just for completeness, this is the test code I've been using, which happily resumes an interrupted upload after restarting the application multiple times during an upload. The only difference between resuming and restarting, is setting the UploadUri and BytesServerReceived properties.
resumableUploadTest = youTubeService.Videos.Insert(video, "snippet,status,contentDetails", fileStream, "video/*");
if (resume)
{
resumableUploadTest.UploadUri = Settings.Default.UploadUri;
resumableUploadTest.BytesServerReceived = Settings.Default.BytesServerReceived;
}
resumableUploadTest.ChunkSize = ResumableUpload<Video>.MinimumChunkSize;
resumableUploadTest.ProgressChanged += resumableUpload_ProgressChanged;
resumableUploadTest.UploadAsync();
I hope this helps someone. It took me much longer than expected to work it out and I'm still hoping I've missed something simple. I messed around for ages trying to add my own error handlers but the API does all that for you. The API does recover from minor short hiccups but not from an application restart, reboot or prolonged outage.
Cheers. Mick.
This issue has been resolved in version "1.8.0.960-rc" of the Google.Apis.YouTube.v3 Client Library.
They've added a new method called ResumeAsync and it works fine. I wish I'd known they were working on it.
One minor issue I needed to resolve was resuming an upload after restarting the application or rebooting. The current api does not allow for this but two minor changes resolved the issue.
I added a new signature for the ResumeAsync method, which accepts and sets the original UploadUri. The StreamLength property needs to be initialised to avoid an overflow error.
public Task<IUploadProgress> ResumeAsync(Uri uploadUri, CancellationToken cancellationToken)
{
UploadUri = uploadUri;
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
return ResumeAsync(cancellationToken);
}
I also exposed the getter for UploadUri so it can be saved from the calling application.
public Uri UploadUri { get; private set; }
I've managed to get this to work using reflection and avoided the need to modify the API at all. For completeness, I'll document the process but it isn't recommended. Setting private properties in the resumable upload object is not a great idea.
When your resumeable upload object has been destroyed after an application restart or reboot, you can still resume an upload using version "1.8.0.960-rc" of the Google.Apis.YouTube.v3 Client Library.
private static void SetPrivateProperty<T>(Object obj, string propertyName, object value)
{
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (propertyInfo == null) return;
propertyInfo.SetValue(obj, value, null);
}
private static object GetPrivateProperty<T>(Object obj, string propertyName)
{
if (obj == null) return null;
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
return propertyInfo == null ? null : propertyInfo.GetValue(obj, null);
}
You need to save the UploadUri during the ProgressChanged event.
Upload.ResumeUri = GetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri") as Uri;
You need to set the UploadUri and StreamLength before calling ResumeAsync.
private const long UnknownSize = -1;
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri", Upload.ResumeUri);
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "StreamLength", fileStream.CanSeek ? fileStream.Length : Constants.UnknownSize);
Task = InsertMediaUpload.ResumeAsync(CancellationTokenSource.Token);
Let me preface this by saying I now realize how stupid I am and was. I have been developing for 1 year (to the day) and this was the first thing I wrote. I now have come back to it and I can't make heads or tails of it. It worked at one point on a very simple app but that was a while ago.
Specifically I am having problems with LocalDBConn which uses out but for the life of me I can't remember why.
Guidance, pointer's, refactoring, slaps up side the head are ALL welcome and appreciated!
public class MergeRepl
{
// Declare nessesary variables
private string subscriberName;
private string publisherName;
private string publicationName;
private string subscriptionDbName;
private string publicationDbName;
private MergePullSubscription mergeSubscription;
private MergePublication mergePublication;
private ServerConnection subscriberConn;
private ServerConnection publisherConn;
private Server theLocalSQLServer;
private ReplicationDatabase localRepDB;
public MergeRepl(string subscriber, string publisher, string publication, string subscriptionDB, string publicationDB)
{
subscriberName = subscriber;
publisherName = publisher;
publicationName = publication;
subscriptionDbName = subscriptionDB;
publicationDbName = publicationDB;
//Create connections to the Publisher and Subscriber.
subscriberConn = new ServerConnection(subscriberName);
publisherConn = new ServerConnection(publisherName);
// Define the pull mergeSubscription
mergeSubscription = new MergePullSubscription
{
ConnectionContext = subscriberConn,
DatabaseName = subscriptionDbName,
PublisherName = publisherName,
PublicationDBName = publicationDbName,
PublicationName = publicationName
};
// Ensure that the publication exists and that it supports pull subscriptions.
mergePublication = new MergePublication
{
Name = publicationName,
DatabaseName = publicationDbName,
ConnectionContext = publisherConn
};
// Create the local SQL Server instance
theLocalSQLServer = new Server(subscriberConn);
// Create a Replication DB Object to initiate Replication settings on local DB
localRepDB = new ReplicationDatabase(subscriptionDbName, subscriberConn);
// Check that the database exists locally
CreateDatabase(subscriptionDbName);
}
public void RunDataSync()
{
// Keep program from appearing 'Not Responding'
///// Application.DoEvents();
// Does the needed Databases exist on local SQLExpress Install
/////CreateDatabase("ContactDB");
try
{
// Connect to the Subscriber
subscriberConn.Connect();
// if the Subscription exists, then start the sync
if (mergeSubscription.LoadProperties())
{
// Check that we have enough metadata to start the agent
if (mergeSubscription.PublisherSecurity != null || mergeSubscription.DistributorSecurity != null)
{
// Synchronously start the merge Agent for the mergeSubscription
// lblStatus.Text = "Data Sync Started - Please Be Patient!";
mergeSubscription.SynchronizationAgent.Synchronize();
}
else
{
throw new ApplicationException("There is insufficient metadata to synchronize the subscription." +
"Recreate the subscription with the agent job or supply the required agent properties at run time.");
}
}
else
{
// do something here if the pull mergeSubscription does not exist
// throw new ApplicationException(String.Format("A mergeSubscription to '{0}' does not exist on {1}", publicationName, subscriberName));
CreateMergeSubscription();
}
}
catch (Exception ex)
{
// Implement appropriaate error handling here
throw new ApplicationException("The subscription could not be synchronized. Verify that the subscription has been defined correctly.", ex);
//CreateMergeSubscription();
}
finally
{
subscriberConn.Disconnect();
}
}
public void CreateMergeSubscription()
{
// Keep program from appearing 'Not Responding'
// Application.DoEvents();
try
{
if (mergePublication.LoadProperties())
{
if ((mergePublication.Attributes & PublicationAttributes.AllowPull) == 0)
{
mergePublication.Attributes |= PublicationAttributes.AllowPull;
}
// Make sure that the agent job for the mergeSubscription is created.
mergeSubscription.CreateSyncAgentByDefault = true;
// Create the pull mergeSubscription at the Subscriber.
mergeSubscription.Create();
Boolean registered = false;
// Verify that the mergeSubscription is not already registered.
foreach (MergeSubscription existing in mergePublication.EnumSubscriptions())
{
if (existing.SubscriberName == subscriberName
&& existing.SubscriptionDBName == subscriptionDbName
&& existing.SubscriptionType == SubscriptionOption.Pull)
{
registered = true;
}
}
if (!registered)
{
// Register the local mergeSubscription with the Publisher.
mergePublication.MakePullSubscriptionWellKnown(
subscriberName, subscriptionDbName,
SubscriptionSyncType.Automatic,
MergeSubscriberType.Local, 0);
}
}
else
{
// Do something here if the publication does not exist.
throw new ApplicationException(String.Format(
"The publication '{0}' does not exist on {1}.",
publicationName, publisherName));
}
}
catch (Exception ex)
{
// Implement the appropriate error handling here.
throw new ApplicationException(String.Format("The subscription to {0} could not be created.", publicationName), ex);
}
finally
{
publisherConn.Disconnect();
}
}
/// <summary>
/// This will make sure the needed DataBase exists locally before allowing any interaction with it.
/// </summary>
/// <param name="whichDataBase">The name of the DataBase to check for.</param>
/// <returns>True if the specified DataBase exists, False if it doesn't.</returns>
public void CreateDatabase(string whichDataBase)
{
Database db;
LocalDBConn(whichDataBase, out theLocalSQLServer, out localRepDB, out db);
if (!theLocalSQLServer.Databases.Contains(whichDataBase))
{
//Application.DoEvents();
// Create the database on the instance of SQL Server.
db = new Database(theLocalSQLServer, whichDataBase);
db.Create();
}
localRepDB.Load();
localRepDB.EnabledMergePublishing = false;
localRepDB.CommitPropertyChanges();
if (!mergeSubscription.LoadProperties())
{
CreateMergeSubscription();
}
}
private void LocalDBConn(string databaseName, out Server server, out ReplicationDatabase replicationDatabase, out Database db)
{
db = server.Databases[replicationDatabase.Name];
}
/// <summary>
/// Checks for the existince of the Publication. If there is one it verify's Allow Pull is set
/// </summary>
/// <returns>True if Publication is present. False if not.</returns>
public bool CheckForPublication()
{
// If LoadProperties() returns TRUE then the Publication exists and is reachable
if (mergePublication.LoadProperties())
return true;
if ((mergePublication.Attributes & PublicationAttributes.AllowPull) == 0)
{
mergePublication.Attributes |= PublicationAttributes.AllowPull;
}
return false;
} // end CheckForPublication()
/// <summary>
/// Checks for the existence of a Subscription.
/// </summary>
/// <returns>True if a Subscription is present. False if not</returns>
public bool CheckForSubscription()
{
// Check for the existence of the Subscription
return mergeSubscription.IsExistingObject;
} // end CheckForSubscription()
}
Edit 1
Opps, I forgot the specific errors. On server and replicationDatabase I am getting a "Out parameter might not be initialized before accessing"
private void LocalDBConn(string databaseName, out Server server, out ReplicationDatabase replicationDatabase, out Database db)
{
db = server.Databases[replicationDatabase.Name];
}
Looks like you'd be safe to remove the out's.
I'm not sure how that even compiled, unless maybe it was with VS2003 and the compiler didn't check for this type of error.
From MSDN: Although variables passed as out arguments do not have to be initialized before being passed, the called method is required to assign a value before the method returns.
private void LocalDBConn(string databaseName, Server server,
ReplicationDatabase replicationDatabase, out Database db)
{
db = server.Databases[replicationDatabase.Name];
}
or
private Database LocalDBConn(string databaseName, Server server,
ReplicationDatabase replicationDatabase)
{
return server.Databases[replicationDatabase.Name];
}
Then update your code in CreateDatabase to:
Database db;
LocalDBConn(whichDataBase, theLocalSQLServer, localRepDB, out db);
or
Database db = LocalDBConn(whichDataBase, theLocalSQLServer, localRepDB);
private void LocalDBConn(string databaseName, out Server server, out ReplicationDatabase replicationDatabase, out Database db)
{
db = server.Databases[replicationDatabase.Name];
}
This method will not build because the out parameters are not initialized before the method returns. An out parameter allows the caller to pass an uninstantiated object into the method, which the method must then initialize through all paths before it returns (excluding if the method throws an exception). Basically, an out parameter is a statement by the method saying "I will instantiate this object before returning". In LocalDBConn, you are not doing this.
In the C# 3.0 language specification this detailed in section 5.1.6.
All the compiler is telling you is that the Server and ReplicationDatabase are output parameters and yet you are not assigning anything to them before returning from the LocalDBConn method.
When using out parameters you should perform a null check on them to ensure they have been initialized outside the method, if not initialize them.