I have problem with downloading files through C#.
I have a class which handles downloading like this:
namespace Ultra_Script
{
class FileDownloader
{
private readonly string _url;
private readonly string _fullPathWheretoSave;
private bool _result = false;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0);
public FileDownloader(string url, string fullPathWheretoSave)
{
if (string.IsNullOrEmpty(url)) throw new ArgumentNullException("url");
if (string.IsNullOrEmpty(fullPathWheretoSave)) throw new ArgumentNullException("fullPathWhereToSave");
this._url = url;
this._fullPathWheretoSave = fullPathWheretoSave;
}
public bool StartDownload(int timeout)
{
try
{
System.IO.Directory.CreateDirectory(Path.GetDirectoryName(_fullPathWheretoSave));
if (File.Exists(_fullPathWheretoSave))
{
File.Delete(_fullPathWheretoSave);
}
using (WebClient client = new WebClient())
{
var ur = new Uri(_url);
//client.Credentials = new NetworkCredential("username", "password");
client.DownloadProgressChanged += WebClientDownloadProgressChanged;
client.DownloadFileCompleted += WebClientDownloadCompleted;
Console.WriteLine(#"Downloading File:");
client.DownloadFileAsync(ur, _fullPathWheretoSave);
_semaphore.Wait(timeout);
return _result && File.Exists(_fullPathWheretoSave);
}
}
catch (Exception e)
{
Console.WriteLine("Cant download file");
Console.Write(e);
return false;
}
finally
{
this._semaphore.Dispose();
}
}
private void WebClientDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Console.Write("/r --> {0}%", e.ProgressPercentage);
}
private void WebClientDownloadCompleted(object sender, AsyncCompletedEventArgs args)
{
_result = !args.Cancelled;
if (!_result)
{
Console.Write(args.Error.ToString());
}
Console.WriteLine(Environment.NewLine + "Download Finished!");
_semaphore.Release();
}
public static bool DownloadFile(string url, string fullPathWhereToSave, int timeoutInMilliSec)
{
return new FileDownloader(url, fullPathWhereToSave).StartDownload(timeoutInMilliSec);
}
}
}
And i called it like this:
public static void InstallBasicSW()
{
var succes = FileDownloader.DownloadFile("https://github.com/Corbieman/Basic_SW/raw/master/JaVa.exe", "C:\\Windows", 99999999);
Console.WriteLine("Done - Succes: " + succes);
Console.ReadLine();
}
But only what im getting in Console is:
Download Finished!
Done - Succes: False;
I don't get any error message or progress bar. This message just pops up instantly. And file doesn't download into that path. Anybody know or have idea where can be the problem?
The parameter of method DownloadFile need the full file's path.
Try this :
public static void InstallBasicSW()
{
var succes = FileDownloader.DownloadFile("https://github.com/Corbieman/Basic_SW/raw/master/JaVa.exe", #"C:\Temps\JaVa.exe", 99999999);
Console.WriteLine("Done - Succes: " + succes);
Console.ReadLine();
}
Why have you this result?
I think it's because you pass a directory path instead of a file path.
The download cancel and finish immediately.
A explicit exception will be more helpful...
So problem was i had to run it as admin and correct the path like you said guys. Its working now, Thanks.
Related
I created a new class named SiteDownload and added some links to download images:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
public class SiteDownload
{
public static List<string> Sites()
{
List<string> list = new List<string>();
list.Add("mysite.com/sites/default/files/1231105.gif");
list.Add("mysite.com/sites/default/files/1231040.gif");
return list;
}
public static async Task<List<Website>> ParallelDownload(IProgress<ProgressReport> progress, CancellationTokenSource cancellationTokenSource)
{
List<string> sites = Sites();
List<Website> list = new List<Website>();
ProgressReport progressReport = new ProgressReport();
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 8;
parallelOptions.CancellationToken = cancellationTokenSource.Token;
await Task.Run(() =>
{
try
{
Parallel.ForEach<string>(sites, parallelOptions, (site) =>
{
Website results = Download(site);
list.Add(results);
progressReport.SitesDownloaded = list;
progressReport.PercentageComplete = (list.Count * 100) / sites.Count;
progress.Report(progressReport);
parallelOptions.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException ex)
{
throw ex;
}
});
return list;
}
private static Website Download(string url)
{
Website website = new Website();
WebClient client = new WebClient();
website.Url = url;
website.Data = client.DownloadString(url);
return website;
}
public class Website
{
public string Url { get; set; }
public string Data { get; set; }
}
public class ProgressReport
{
public int PercentageComplete { get; set; }
public List<Website> SitesDownloaded { get; set; }
}
}
in form1:
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using static HttpClientFilesDownloader.SiteDownload;
namespace HttpClientFilesDownloader
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
void PrintResults(List<Website> results)
{
richTextBox1.Text = string.Empty;
foreach (var item in results)
richTextBox1.Text += $"{item.Url} downloaded: {item.Data.Length} characters long.{Environment.NewLine}";
}
void ReportProgress(object sender, ProgressReport e)
{
progressBar1.Value = e.PercentageComplete;
label1.Text = $"Completed: {e.PercentageComplete} %";
PrintResults(e.SitesDownloaded);
}
CancellationTokenSource cancellationTokenSource;
private async void button1_Click(object sender, EventArgs e)
{
try
{
cancellationTokenSource = new CancellationTokenSource();
Progress<ProgressReport> progress = new Progress<ProgressReport>();
progress.ProgressChanged += ReportProgress;
var watch = Stopwatch.StartNew();
var results = await SiteDownload.ParallelDownload(progress, cancellationTokenSource);
PrintResults(results);
watch.Stop();
var elapsed = watch.ElapsedMilliseconds;
richTextBox1.Text += $"Total execution time: {elapsed}";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
cancellationTokenSource.Dispose();
}
}
private void button2_Click(object sender, EventArgs e)
{
if (cancellationTokenSource != null)
cancellationTokenSource.Cancel();
}
}
}
The desiger
When I click the START button, nothing happens. I don't see the progressBar get any process and the label1 is not updating and nothing in the RichTextBox. It's just not downloading the images.
I'm not getting any errors, it's just not downloading.
I took this example from this site just instead downloading site/s I'm trying to download images files and save them on the hard disk as images:
example
I also need to add header like i did with webclient:
webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0 Chrome");
but not sure how to add the headers to the HttpClient.
An example of a HTTP resource downloader. This class is meant to target .NET 6+, since it's using Parallel.ForEachAsync(). The record keyword requires C# 9+. Nullable enabled
I tried to keep the structure you have used in the OP as much as possible
To start the download of a collection of resources, call the static Download() method, passing an IProgress<ProgressReport> delegate, a collection of strings representing the URLs of the resources and a CancellationTokenSource
The ReportProgress() method marshals to the UI Thread a ProgressReport record. It references a WebData record, which contains the URL of the current resource, the image (in this case) bytes, the Completed status and the Exception thrown in case the resource failed to download for some reason. If the download is canceled in the meantime, the Exception reason is going to be The operation was canceled.
It also returns the overall progress of the downloads, in the form of a percentage.
Note that the progress procedure is completed also when you cancel the operation, since you probably want to know which resource was completed before the operation was canceled and which one couldn't complete
Note: the static Download() method is not Thread-Safe, i.e., you cannot call this method concurrently, e.g., to download multiple lists of resources at the same time (though it can be easily refactored, making it non-static).
Check the IsBusy Property before you call that method again.
public class ResourceDownloader {
private static readonly Lazy<HttpClient> client = new(() => {
HttpClientHandler handler = CreateHandler(autoRedirect: true);
var client = new HttpClient(handler, true) { Timeout = TimeSpan.FromSeconds(60) };
client.DefaultRequestHeaders.Add("User-Agent", #"Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0");
client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
// Keep true if you download resources from different collections of URLs each time
// Remove or set to false if you use the same URLs multiple times and frequently
client.DefaultRequestHeaders.ConnectionClose = true;
return client;
}, true);
private static HttpClientHandler CreateHandler(bool autoRedirect)
{
return new HttpClientHandler() {
AllowAutoRedirect = autoRedirect,
CookieContainer = new CookieContainer(),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
}
public record WebData(string Url, byte[]? Data, bool Completed = true, Exception? Ex = null);
public record ProgressReport(WebData Site, int PercentageComplete);
private static object syncObj = new object();
private static ConcurrentBag<WebData> processed = default!;
private static int progressCount = 0;
private static int totalCount = 0;
public static bool IsBusy { get; internal set; } = false;
public static async Task<List<WebData>> Download(IProgress<ProgressReport> progress, IList<string> sites, CancellationTokenSource cts)
{
IsBusy = true;
processed = new ConcurrentBag<WebData>();
progressCount = 0;
totalCount = sites.Count;
try {
ParallelOptions options = new() {
// If it's a single web site, set a value that doesn't get you black-listed
// Otherwise, increase the value
MaxDegreeOfParallelism = 8,
CancellationToken = cts.Token
};
await Parallel.ForEachAsync(sites, options, async (site, token) => {
try {
var dataBytes = await client.Value.GetByteArrayAsync(site, token);
ReportProgress(progress, dataBytes, site, null);
}
catch (Exception ex) {
ReportProgress(progress, null, site, ex);
}
});
}
// To Debug / Log
catch (TaskCanceledException) { Debug.Print("The operation was canceled"); }
finally { IsBusy = false; }
return processed.ToList();
}
private static void ReportProgress(IProgress<ProgressReport> progress, byte[]? data, string site, Exception? ex) {
lock (syncObj) {
progressCount += 1;
var percentage = progressCount * 100 / totalCount;
WebData webData = new(site, data, ex is null, ex);
processed.Add(webData);
progress.Report(new ProgressReport(webData, percentage));
}
}
}
You can setup a Form like this:
Add a TextBox (here, named logger) to show the status of the resources that are being downloaded
A Button used to start the download (named btnStartDownload)
A Button to cancel the download (named btnStopDownload)
A ProgressBar (named progressBar) used to show the overall progress
Note that with an active (not configured) debugger, you may have notifications that Exceptions are thrown, so maybe run the Project with CTRL + F5
public partial class SomeForm : Form {
public SomeForm() => InitializeComponent();
internal List<string> Sites()
{
var list = new List<string>();
list.Add("https://somesite/someresource.jpg");
// [...] add more URLs
return list;
}
IProgress<ResourceDownloader.ProgressReport>? downloadProgress = null;
CancellationTokenSource? cts = null;
private void Updater(ResourceDownloader.ProgressReport progress)
{
StringBuilder log = new(1024);
if (progress.Site.Completed) {
log.Append($"Success! \t {progress.Site.Url}\r\n");
}
else {
log.Append($"Failed! \t {progress.Site.Url}\r\n");
log.Append($"\tReason: {progress.Site.Ex?.Message}\r\n");
}
logger.AppendText(log.ToString());
progressBar.Value = progress.PercentageComplete;
}
private async void btnStartDownload_Click(object sender, EventArgs e)
{
if (ResourceDownloader.IsBusy) return;
var sites = Sites();
// This collection will contain the status (and data) of all downloads in th end
List<ResourceDownloader.WebData>? downloads = null;
using (cts = new CancellationTokenSource()) {
downloadProgress = new Progress<ResourceDownloader.ProgressReport>(Updater);
downloads = await ResourceDownloader.Download(downloadProgress, sites, cts);
}
}
private void btnStopDownload_Click(object sender, EventArgs e) => cts?.Cancel();
}
This is how it works:
My apps in xamarin form is populate by a json file i download online then i need to be sure device have internet access.
I try this but it freeze UI and timeout not seam to be used then i like to make it async.
private void CheckClicked(object sender, EventArgs e)
{
if (CheckForInternetConnection() == true)
{
isinternet.Text = "Internet ok";
}
else
{
isinternet.Text = "Internet down";
}
}
public static bool CheckForInternetConnection()
{
try
{
using (var client = new MyWebClient(5000))
using (client.OpenRead("http://google.com/generate_204"))
return true;
}
catch
{
return false;
}
}
with this class
public class MyWebClient : WebClient
{
private int timeout;
public int Timeout
{
get
{
return timeout;
}
set
{
timeout = value;
}
}
public MyWebClient()
{
this.timeout = 10000;
}
public MyWebClient(int timeout)
{
this.timeout = timeout;
}
}
You should use HttpClient and the asynchronous methods it implements. Try to stay away from legacy HTTP client implementations such as WebClient.
A quick example would be:
private static readonly HttpClient _httpClient = new HttpClient();
private async void CheckClicked(object sender, EventArgs e)
{
var isConnected = await CheckForInternetConnectionAsync();
if(isConnected)
{
isinternet.Text = "Internet ok";
}
else
{
isinternet.Text = "Internet down";
}
}
private static async Task<bool> CheckForInternetConnectionAsync()
{
using(var tokSource = new CancellationTokenSource(5000))
{
try
{
await _httpClient.GetAsync("https://www.example.com", tokSource.Token);
}
catch(OperationCanceledException)
{
return false;
}
}
return true;
}
This will leave your UI responsive, but at the same time accomplish making a request.
I created a method that append data to file .txt and it executes on Button_Click method. But I want to save the data to file not when I click the button but after time automatically (ex. after couple minutes starting from Application_Start). How can I solve my problem?
static public bool appendToFileTxt(string input)
{
try
{
if (File.Exists(nameOfFile))
File.Copy(nameOfFile, nameOfFile + ".bak", true);
using (StreamWriter sw = new StreamWriter(nameOfFile, true)){
sw.WriteLine(input.Replace("\r", ""));
}
return true;
}
catch{
return false;
}
}
protected void Button1_Click(object sender, EventArgs e)
{
appendToFileTxt(newInput);
}
You can use the Timer class for this usage.
// Simulate Application_Start
public static void Main()
{
var appendToFileTimer = new Timer(AppendToFile, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
public static void AppendToFile(Object state)
{
Console.WriteLine("Append to file");
}
I have the following code:
namespace SSS.RemoteTruckService
{
public partial class Startup : Form
{
private Timer _gpsTimer;
private Timer _ppsTimer;
private Timer _creditCardTimer;
private Timer _iniTimer;
public string Message
{
get { return richTextBox_Message.Text; }
set
{
richTextBox_Message.Invoke((MethodInvoker)(()
=> richTextBox_Message.Text = DateTime.Now + " " +
value + Environment.NewLine + richTextBox_Message.Text));
}
}
public Startup()
{
InitializeComponent();
}
private void ButtonStartClick(object sender, EventArgs e)
{
StartRemoteTruck();
}
private void ButtonPauseClick(object sender, EventArgs e)
{
if (_gpsTimer.Enabled) _gpsTimer.Enabled = false;
if (_ppsTimer.Enabled) _ppsTimer.Enabled = false;
if (_creditCardTimer.Enabled) _creditCardTimer.Enabled = false;
if (_iniTimer.Enabled) _iniTimer.Enabled = false;
ProcessIniFile.StopProcess();
}
public void StartRemoteTruck()
{
Message = "RemoteTruck started.";
if (Settings.GlobalSettings == null)
{
Message = "GlobalSettings was null or not loaded. Cannot continue.";
Logging.Log("GlobalSettings was null or not loaded. Cannot continue.", "RemoteTruck", Apps.RemoteTruckService);
Environment.Exit(0);
}
if (Settings.GlobalSettings.IniFileWatcherEnabled)
{
ProcessIniFile.StartProcess();
}
CreateTimers();
}
And in the ProcessIniFile.StartProcess() I have the code:
namespace SSS.RemoteTruckService.inifile
{
public static class ProcessIniFile
{
private static DateTime _iniLastWriteTime;
private static readonly string Inifile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "sss.ini");
private static FileSystemWatcher _watcher;
public static void StartProcess()
{
ReadIniFile();
SaveCurrentIniReadings();
CreateIniFileWatcher();
}
public static void StopProcess()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher = null;
}
}
private static void CreateIniFileWatcher()
{
_watcher = new FileSystemWatcher
{
Path = Environment.GetFolderPath(Environment.SpecialFolder.Windows),
NotifyFilter = NotifyFilters.LastWrite,
Filter = "sss.ini"
};
_watcher.Changed += SssIniWatcherChanged;
_watcher.EnableRaisingEvents = true;
}
I'd like to pass back the the calling form the status of the reads of the file watcher.
Maybe I'm overthinking this, but if I want to add to the Message on the main form, how do I get to it?
You can use Events for that. Your process can send events and your form can handle them.
More info: http://msdn.microsoft.com/en-us/library/awbftdfh.aspx
The simple but not pretty way I like to use is to make that part of the form static as well. For example, creating a static variable WriteMessage, and in your Form Load or Startup(), you can set it:
WriteMessage = (s) => Message = s;
Sure this has some issues, but it's a quick way to get it done. One of those issues is that, you may need to use Dispatcher.invoke if you're not on the UI thread.
I got this error
Method name expected.
for this code:
thSystem[index] = new Thread(new ThreadStart(cls.StartProcess(out string)));
How to do the overload for output value?
I want to take out string from StartProcess.
Below is my code:
//declare function
clsCallProcess cls = new clsCallProcess(index, url, name, timer);
thSystem[index] = new Thread(new ThreadStart(cls.StartProcess(out string)));
thSystem[index].Start();
//function
public class clsCallProcess
{
private int mindex;
private string murl;
private string mname;
private int mtimer;
bool IsRunning = true;
// The constructor obtains the state information.
public clsCallProcess(int index, string url, string name, int timer)
{
mindex = index;
murl = url;
mname = name;
mtimer = timer;
}
public void StartProcess(out string result)
{
//First run checking
result = "Connection Success";
while (IsRunning)
{
Thread.Sleep(mtimer);
IsRunning = false;
try
{
//FileManager.WriteActivityLog("Process Start", mname);
DateTime start = DateTime.Now;
string html = Utility.RequestWebpage(murl, string.Empty, true);
TimeSpan minisecond = DateTime.Now - start;
FileManager.WriteActivityLog("time:" + minisecond.Milliseconds.ToString() + ",html length:" + html.Length.ToString(), mname);
//FileManager.WriteActivityLog("html:" + html, mname);
//FileManager.WriteActivityLog("Process End", mname);
}
catch (Exception ex)
{
ExceptionManager.WriteErrorLog(ex.Message, true, mname);
result = ex.Message;
}
IsRunning = true;
}
}
}
Apparently you run something in a loop forever (right now your thread loop will never end as IsRunning will always be true) to check for some webpage periodically.
One way to get the periodic results back is an event handler which gets raised every time you have made a connection attempt. It could look like this:
public class PageWatcher
{
private int mindex;
private string murl;
private string mname;
private int mtimer;
bool IsRunning = true;
public event EventHandler<ConnectionAttemptEventArgs> ConnectionAttempt;
// The constructor obtains the state information.
public PageWatcher(int index, string url, string name, int timer)
{
mindex = index;
murl = url;
mname = name;
mtimer = timer;
}
public void StartProcess()
{
//First run checking
string lastResult = "Connection Success";
while (IsRunning)
{
Thread.Sleep(mtimer);
IsRunning = false;
try
{
//FileManager.WriteActivityLog("Process Start", mname);
DateTime start = DateTime.Now;
string html = Utility.RequestWebpage(murl, string.Empty, true);
TimeSpan minisecond = DateTime.Now - start;
FileManager.WriteActivityLog("time:" + minisecond.Milliseconds.ToString() + ", html length:" + html.Length.ToString(), mname);
//FileManager.WriteActivityLog("html:" + html, mname);
//FileManager.WriteActivityLog("Process End", mname);
lastResult = "Connection Success";
}
catch (Exception ex)
{
ExceptionManager.WriteErrorLog(ex.Message, true, mname);
lastResult = ex.Message;
}
IsRunning = true;
OnConnectionAttempt(result);
}
}
private void OnConnectionAttempt(string result)
{
var handler = ConnectionAttempt;
if (handler != null)
{
handler(this, new ConnectionAttemptEventArgs(mindex, result));
}
}
}
public class ConnectionAttemptEventArgs : EventArgs
{
public readonly int Index;
public readonly string Result;
public ConnectionAttemptEventArgs(int index, string result)
{
Index = index;
Result = result;
}
}
Then use it like this:
PageWatcher watcher = new PageWatcher(index, url, name, timer);
watcher.ConnectionAttempt += HandleConnectionAttempt;
thSystem[index] = new Thread(new ThreadStart(watcher.StartProcess));
thSystem[index].Start();
...
void HandleConnectionAttempt(object sender, ConnectionAttemptEventArgs e)
{
Console.WriteLine("Connection attempt watcher {0}, result {1}", e.Index, e.Result);
}
I also renamed your class as clsCallProcess is not really a good name. Prefixing it with cls to indicate it's a class is rather pointless and CallProcess is what it's doing but not what its intent is - which is to watch a connection to a webpage.
Have a "continuation" delegate that takes the result. The delegate would be called at the end of the method.
clsCallProcess cls = new clsCallProcess(index, url, name, timer);
thSystem[index] = new Thread(new ThreadStart(
() =>
cls.StartProcess(result => Console.WriteLine (result))));
// ---------------------------^
// If you want to do something other than write it to the console, do that here.
thSystem[index].Start();
Here's the framework of the StartProcess (
public void StartProcess(Action<string> continueWith)
{
string result;
while (IsRunning)
{
// do stuff
result = "success";
}
// all done... call continuation delegate.
continueWith (result);
}
For an alternate, perhaps more friendly way of doing it (if you have .net 4), use: Continuations with Task Parallel Library