Howto Cancel Async Download of one file witihn mutiple file downloads? - c#

i use some code to download mutiple files, which works good.
while downloading a lot of files, i would like to be able to cancel them.
Besides the UI with a cancel button i use as global variables:
private WebClient client = null;
private CancellationToken cts = new CancellationToken();
Within the download function i use:
// The Object SourceId is only used to be able
// to pass the ID (index) of datasource to callback functions
var current = new SourceId();
current.Id = sourceList.IndexOf(current);
cts = new CancellationToken();
//i tried to use (cancellationToken.Register(() => webClient.CancelAsync())
using (client = new WebClient())
using (cts.Register(() => wc_Cancel()))
{
client.DownloadProgressChanged += wc_DownloadProgressChanged;
client.DownloadFileCompleted += wc_DownloadFileCompleted;
client.DownloadFileAsync(new Uri(driver.Filelink), targetFile, current);
}
"current" is an object, which contains the source ID of the original Datasource, which i use within wc_DownloadProgressChanged and wc_DownloadFileCompleted to determine the correct Data from my datasource.
Within the function wc_DownloadFileCompleted i use if (e.Cancelled) to determine if the download was canceled.
I try to cancel a specific Download. The cancelDownload Function is called by a button after getting the corresponding source Id.
How can i cancel one specificed async download, which means how to pass the needed argument for cancel function ??
private void cancelDownload(int id)
{
// Here i would like to send the ID to cancel to specific download
// and wait for it to finish. How can i do this ?
wc_Cancel();
}
public virtual void wc_Cancel()
{
if (this.client != null)
this.client.CancelAsync();
}
How can i achieve that ?

finally i found a solution for this. For those with the same problem here is my solution.
The use of the CancellationToken is not necessary to get this work. Also the functions cancelDownload and wc_Cancel are not needed. I use a download class, with all needed informations like FileLink, Name etc. I did expand that class with these properties:
// HelperClass to pass for callbacks
public class DownId
{
public int Id { get; set; }
}
// DownloadClass with all needed properties. Should be expanded for your needs.
public class Downloads
{
public Driver()
{
}
public string Title { get; set; }
public string Filelink { get; set; }
public string FileName { get; set; }
public bool IsCanceling { get; set; }
public WebClient Client { get; set; }
public int Retry { get; set; }
public int Progress { get; set; }
public long valBytesRec { get; set; }
public long valBytesTotal { get; set; }
}
The Class now contains a property for the WebClient and bool parameter for canceling. A global Variable downloadlist of type List<Downloads> is used to access the needed properties within all functions.
In the download function I use a parameter currentdriver, which is the indexnumber of the current download within the list (used as paramter for download):
DownId currentSourceId = new DownId();
currentSourceId.Id = currentdriver;
using (downloadlist[current].Client = new WebClient())
{
downloadlist[current].Client.DownloadProgressChanged += wc_DownloadProgressChanged;
downloadlist[current].Client.DownloadFileCompleted += wc_DownloadFileCompleted;
downloadlist[current].Client.DownloadFileAsync(new Uri(driver.Filelink), targetFile, currentSourceId);
}
To be able to pass the currentdrive to the DownloadFileAsync, it must be used within an object. for this purpose is the DownId Class is used.
This way every download will use its own WebClient, which can be access quite easy.
For canceling it is only necessary to set the IsCanceling property within the button_Click event handler:
driverlist[currentdriver].IsCanceling = true;
In the wc_DownloadProgressChanged, I use this code to determine, if Canceling is necessary:
var currentDriver = e.UserState as DownId;
if (downloadlist[current].IsCanceling)
{
downloadlist[current].IsCanceling = false;
downloadlist[current].Client.CancelAsync();
}
else
{
//Update progress etc.
// beware of not using UI Elements here, caused UI Lags
// and can be a problem within threads
// if necessary use Invoke(new MethodInvoker(delegate { ...}));
}
Hope this helps.. I did search for quite a while to find a solution.

Related

Using parent to call a method from the components Blazor

I have this method in my components called ClearFiles(string PathName) which I currently call by clicking one of the buttons on the component, but now I'm trying to call of them at once but I cannot figure out how to do this. Calling a single ClearFiles method from the component works perfectly, but I'm not quite sure how to call all these methods at once using the parent class. I have made a button in the parent class with the name of ForceClean which I'm hoping can be pressed to call all of the components clear files function.
I create the components in the parent class using a for loop in my PageFileCleaner.razor file like this:
#foreach (var d in filters)
{
<CompFileFilter FileFilter="d" OnDelete="Delete" ></CompFileFilter>
}
Where filters is a List of type FileFilter which is in the namespace Common.DB. This is how it is declared in the parent class:
private List<FileFilter> filters { get; set; }
And is populated with data from my database using:
using var context = _db.CreateDbContext();
filters = await context.FileFilters.AsNoTracking().ToListAsync();
foreach( var filter in filters)
{
FileFilter f = new FileFilter();
}
And this Parameter (in the child class) is what i use to get the values from that row in the database using FileFilter.Pathname or FileFilter.ID for example:
[Parameter]
public FileFilter FileFilter { get; set; }
Database / FileFilter object class:
namespace Common.DB
{
public class FileFilter
{
[Key]
public int ID { get; set; }
public string TimerChoice { get; set; }
public bool IsLoading;
public string PathName { get; set; } = "";
public string Extension { get; set; }
//public string[] extensionList { get; set; }
...
}
}
The clear files function is in my component/child CompFileFilter.razor file (took the code out for this example because its over a hundred lines of code). I'm creating the components in the parent class and each one will have this ClearFiles() method.
public async Task ClearFiles(string PathName)
{
filtering code...
}
How can I loop through my components from the parent class and call all of there ClearFiles() method?
You can use a dictionary and store the reference of each CompFileFilter component. Then use the references to call the ClearFiles method. You can use the ID property of FileFilter for the dictionary keys.
PageFileCleaner.razor
#foreach (var d in filters)
{
<CompFileFilter #key="d.ID" #ref="_fileFilterRefs[d.ID]" FileFilter="d" OnDelete="Delete"></CompFileFilter>
}
#code {
private Dictionary<int, CompFileFilter> _fileFilterRefs = new Dictionary<int, CompFileFilter>();
private async Task ForceClean()
{
// Execute ClearFiles one at a time.
foreach (var item in _fileFilterRefs)
{
await item.Value.ClearFiles();
}
// Or execute them in parallel.
//var tasks = _fileFilterRefs.Select(item => Task.Run(item.Value.ClearFiles));
//await Task.WhenAll(tasks);
}
}
ClearFiles method doesn't need PathName parameter. You can get the PathName from the FileFilter parameter.
CompFileFilter.razor
#code {
[Parameter]
public FileFilter FileFilter { get; set; }
public async Task ClearFiles()
{
var pathName = FileFilter.PathName;
// filtering code...
}
}
Edit:
You should manually update the _fileFilterRefs dictionary whenever you remove an item from filters list to keep them synchronized. E.g.
private void Delete(FileFilter fileFilter)
{
_filters.Remove(fileFilter);
_fileFilterRefs.Remove(fileFilter.ID);
}
You should clear the _fileFilterRefs dictionary whenever you set the filters list to a new list. E.g.
private async Task RefreshFilters()
{
_fileFilterRefs.Clear();
_filters = await filterService.GetFileFilters();
}
But you don't have to do the same thing when you add a new item to the filters list (_filters.Add(...)). In that case it's handled automatically.
In summary:
filters.Add(new FileFilter()) -> do nothing.
filters.Remove(fileFilter) -> remove fileFilter.ID key from
_fileFilterRefs
filters = new List() -> clear _fileFilterRefs before setting the list
https://github.com/dotnet/aspnetcore/issues/17361#issuecomment-558138782

Send request to websocket server using JSON data

Basically, I have OBS running on a computer and I need to interact with it through a second computer.
I am using an OBS-WebSocket plugin which is creating a websocket server on OBS to send remote commands.
My goal is to set visible / invisible a source on OBS whenever I press a key on the second (remote) computer.
I have no issue with that key press part. My issue is that I have no idea how to correctly send a request/command to the websocket plugin (it requires JSON format as this is how data is formatted).
Main code (C#)
static void Main(string[] args)
{
using (var ws = new WebSocket("ws://192.168.1.1:1122"))
{
ws.Connect();
ws.OnMessage += (sender, e) =>
Console.WriteLine(e.Data);
ws.Send("REPLACE WITH JSON REQUEST DATA");
Console.ReadKey(true);
}
}
Down below is an example of data received from the websocket whenever I turn on or off a source in OBS.
You can clearly see that the 'visibility' is either false or true.
*This 'item-visible' is the setting I need to mess with to tell OBS to hide or show the source.*
↓ Source is visible in OBS ↓
{
"item-id": 10,
"item-name": "map",
"item-visible": true,
"scene-name": "ONHOLD",
"update-type": "SceneItemVisibilityChanged"
}
↓ Source is hidden in OBS ↓
{
"item-id": 10,
"item-name": "map",
"item-visible": false,
"scene-name": "ONHOLD",
"update-type": "SceneItemVisibilityChanged"
}
You can see example test usages in the official Github repo.
If you look there, you'd see that that e.Data should be parsed. Then, you can assign it as needed, for example:
public string SetIsVisible(MessageEventArgs e, bool isVisible)
{
JObject body = JObject.Parse(e.Data);
body["item-visible"] = isVisible;
return body.ToString();
}
... and then just send the returned string into your websocket:
ws.Send(SetIsVisible(e, isVisible)); // isVisible is boolean you should assign
P.S. If you'd like to work with a static-type DTO, simply generate one (e.g. here):
public partial class OBSSource
{
[JsonProperty("item-id")]
public long ItemId { get; set; }
[JsonProperty("item-name")]
public string ItemName { get; set; }
[JsonProperty("item-visible")]
public bool ItemVisible { get; set; }
[JsonProperty("scene-name")]
public string SceneName { get; set; }
[JsonProperty("update-type")]
public string UpdateType { get; set; }
}
... and then:
public string SetIsVisible(MessageEventArgs e, bool isVisible)
{
OBSSource obsSource = JsonConvert.DeserializeObject<OBSSource>(e.Data);
obsSource.ItemVisible = isVisible;
return JsonConvert.SerializeObject(obsSource);
}
... and then send it serialized into the websocket as above.

Setting private set fields from a post request on backend server

I am making my way through various todo list tutorials while learning react and entity framework. As some background I have made my way though Microsoft's todo list todo tutorial; although I have replaced the front end part of that with my own front end. It was all working fine, until I've tried to extend it and hit the issue I will outline below.
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc). This is shown in the code below.
public class TodoItem
{
public long id { get; private set; }
public string title { get; private set; }
public bool IsComplete { get; private set; }
// Define constructor
public TodoItem(long newId, string newTitle)
{
id = newId;
title = newTitle;
IsComplete = false;
}
public void ToggleComplete()
{
IsComplete = !IsComplete;
}
}
The post action from the controller is shown below. I have included some debug printouts as these show where the field is already showing the title as null.
I believe this is the section of code I am struggling with and would like to know what mistakes I am making or what the best practices are!
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
// returns null if model field set to private
System.Diagnostics.Debug.WriteLine("item title: " + item.title);
// Create new item passing in arguments for constructor
TodoItem newItem = new TodoItem(item.id, item.title);
_context.TodoItems.Add(newItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = newItem.id }, newItem);
}
The frontend method (js) where the post request is made is shown below:
const addTodoMethod = (title) => {
// Create new item
const item = {
title: title,
id: Date.now(),
isComplete: false,
}
// Update state
const newTodos = [...todos, item];
setTodos(newTodos);
// Can use POST requiest to add to db
axios.post('https://localhost:44371/api/todo/',
item)
.then(res=> {
console.log("Added item. Title: ", title);
})
.catch(function (error) {
console.log(error);
})}
I hope I've explained the problem well enough. Let me know if there is anything else needed!
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc).
There are two problems in what you did. The first one is that the Models must have a parameter-less constructor, and the second one that the properties must be public, both getter and setter.
The best you can do right now is to stop using your database entity for user input and create a ViewModel class:
public class TodoItemViewModel
{
public long id { get; set; }
public string title { get; set; }
public bool IsComplete { get; set; }
}
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItemViewModel model)
{
var item = new TodoItem(item.id, item.title);
...
}

Return actual patched property value from patchcommand in RavenDb

A have a "Billing" class that's describe my finance account model in multithreading application. If i want to transfer money from one billing to another i make patchcommand that is increment one property and decrement another. What is the right way to return actual property value after patching without redundant Load-query?
Billing class
public class Billing
{
public string Id { get; set; }
public decimal Balance { get; set; }
}
My patch method
public void TransferMoney(string billingSrc, string billingDst, decimal money)
{
_ctx.DatabaseCommands.Batch(new ICommandData[]
{
new ScriptedPatchCommandData
{
Key = billingSrc,
Patch = new ScriptedPatchRequest
{
Script = #"this.Balance -= money;",
Values = {{"money", money}}
}
},
new ScriptedPatchCommandData
{
Key = billingDst,
Patch = new ScriptedPatchRequest
{
Script = #"this.Balance += money;",
Values = {{"money", money}}
}
}
});
}
You can use the output() method to send values back to the user.
Since RavenDb v3, the set of predefined JavaScript functions available in ScriptedPatchRequest has method output(message), which allows to debug your patch and print passed messages in the output. See documentation for v3.0, v3.5, v4.0.
See example below:
var balancePatch = dbSession.Advanced
.DocumentStore
.DatabaseCommands
.Patch(
billingDst /* document ID */,
new ScriptedPatchRequest
{
Script = #"this.Balance += money; output(this.Balance);",
Values = {{"money", money}}
});
// Reading the debug output
var balance = balancePatch["Debug"].Values().First().Value<decimal>();

WCF task scheduler, or timer like in OGame, Travian etc

I'm looking for resources, or anyone who have writting scheduler in WCF.
What I want to achive is basically what can you see in OGame, or Travian or doznes of other text browser based game.
Player click and send task to server to make building for him, or unit or something else.
From what I figured out I need to run some kind if scheduler on server that will gather all tasks, and will track them, until some perdiod of time will pass (2 mins, 10 mins, 3 days etc.), and after that period end service should call an action, like send data to database.
Well. I've been trying to make something very simply, that I can start from and ended with this:
public class BuildingScheduler
{
public int TaskID { get; set; }
public string UserName { get; set; }
public DateTime TaskEnd { get; set; }
public string BuildingName { get; set; }
public bool TaskDone { get; set; }
public DateTime RemainingTime { get; set; }
TestDBModelContainer _ctx;
TestData _testData;
public IDuplexClient Client { get; set; }
public BuildingScheduler()
{
TaskDone = false;
}
public void MakeBuilding()
{
while (DateTime.Now <= TaskEnd)
{
//Client.DisplayMessage(DateTime.Now.ToString());
RemainingTime = DateTime.Now;
}
_testData = new TestData { DataName = BuildingName, Created = DateTime.Now };
_ctx = new TestDBModelContainer();
_ctx.TestDataSet.AddObject(_testData);
_ctx.SaveChanges();
TaskDone = true;
//Client.DisplayMessage("Building completed!");
}
}
static List<UserChannel> _userChannels = new List<UserChannel>();
static List<BuildingScheduler> _buildingSchedules = new List<BuildingScheduler>();
List<BuildingScheduler> _buildingSchedulesToRemove = new List<BuildingScheduler>();
[OperationContract]
public void DoWork()
{
IDuplexClient client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();
UserChannel userChannel = new UserChannel { Client = client, ClientName = "TestClient" };
string clientName = (from p in _userChannels
where p.ClientName == "TestClient"
select p.ClientName).FirstOrDefault();
lock (((ICollection)_userChannels).SyncRoot)
{
if (clientName == null)
{
_userChannels.Add(userChannel);
}
}
CheckBuilding();
}
private void CheckBuilding()
{
BuildingScheduler bs = (from p in _buildingSchedules
where p.UserName == "TestClient"
select p).FirstOrDefault();
IDuplexClient client = (from p in _userChannels
where p.ClientName == "TestClient"
select p.Client).FirstOrDefault();
if (bs != null)
{
client.DisplayMessage(bs.RemainingTime);
}
}
private void StartBuilding()
{
foreach (BuildingScheduler bs in _buildingSchedules)
{
if (bs.TaskDone == false)
{
bs.MakeBuilding();
}
else if (bs.TaskDone == true)
{
_buildingSchedulesToRemove.Add(bs);
}
}
for(int i = 0; i <= _buildingSchedulesToRemove.Count; i++)
{
BuildingScheduler bs = _buildingSchedulesToRemove.Where(p => p.TaskDone == true).Select(x => x).FirstOrDefault();
_buildingSchedules.Remove(bs);
_buildingSchedulesToRemove.Remove(bs);
}
CheckBuilding();
}
[OperationContract]
public void MakeBuilding(string name)
{
BuildingScheduler _buildng = new BuildingScheduler();
//_buildng.Client = client;
_buildng.TaskID = 1;
_buildng.UserName = "TestClient";
_buildng.TaskEnd = DateTime.Now.AddSeconds(50);
_buildng.BuildingName = name;
_buildingSchedules.Add(_buildng);
StartBuilding();
}
I have hardcoded most values, for testing.
InstanceContextMode is set for PerCall.
Anyway. This code is working. At least to some point. If we ignore zylion exceptions from Entity Framework, I can add tasks from multiple clients and they are added to db in order from newset to oldest (or shortest to longest ?).
The point is, user CANT track his tasks. When I change page I don't see how much time remaining till task done. This code essentialy do not provide time tracking because i got rid of it as it wasn't working anyway.
I guess I should store my task in some presitant data storage and everytime user check page newset state should be draw from that storage and send to him.
I'm not and expert but i think best option here is to store all data in Memory until task is done.
Any relational database will be probably to slow, if I will have to constantly update records with newset state of task.
I know I should just synchronize client side timer with server and do not stream constatly time from server. But Big question is here how to get newset state of task pogress when user come back to page after 3 second or 3 hours ?
Any advices how to make it work as expected.
And btw. I'm using pollingduplex and Silverlight.
It sounds like you are using WCF to do something for which it wasn't designed (but I understand the need). I would suggest you look at Workflow Foundation (WF) as a possible solution to your problem. Here is a good explanation:
http://msdn.microsoft.com/library/dd851337.aspx
Here is also a good intro video:
http://channel9.msdn.com/Blogs/mwink/Introduction-to-Workflow-Services-building-WCF-Services-with-WF
Workflow can consume WCF services and it is designed to work over time. It holds data in state until something changes (regardless of time) without consuming excess resources. Also, it allows for persistance and parallel processes.

Categories