Skip to content

Instantly share code, notes, and snippets.

@akrisanov
Created December 26, 2024 14:34
Show Gist options
  • Save akrisanov/77468471e371da3f7ff1d367be05dc6b to your computer and use it in GitHub Desktop.
Save akrisanov/77468471e371da3f7ff1d367be05dc6b to your computer and use it in GitHub Desktop.
The application downloads a list of URLs
using System.Net.Http;
namespace UrlDownloader;
public class Downloader
{
private readonly string _downloadPath = "downloads";
private readonly HttpClient _httpClient;
private readonly SemaphoreSlim _semaphore;
public Downloader(int maxConcurrentDownloads = 3)
{
_semaphore = new SemaphoreSlim(maxConcurrentDownloads);
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("CSharp-Downloader/1.0");
// Ensure downloads directory exists
Directory.CreateDirectory(_downloadPath);
}
public async Task DownloadUrlsAsync(IEnumerable<string> urls)
{
var downloadTasks = urls.Select(DownloadFileAsync);
await Task.WhenAll(downloadTasks);
Console.WriteLine("All downloads completed!");
}
private async Task DownloadFileAsync(string url)
{
try
{
await _semaphore.WaitAsync();
var fileName = GetFileName(url);
var filePath = Path.Combine(_downloadPath, fileName);
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
await using var downloadStream = await response.Content.ReadAsStreamAsync();
await downloadStream.CopyToAsync(fileStream);
Console.WriteLine($"Successfully downloaded: {url}");
}
catch (Exception ex)
{
Console.WriteLine($"Error downloading {url}: {ex.Message}");
}
finally
{
_semaphore.Release();
}
}
private static string GetFileName(string url)
{
var uri = new Uri(url);
var fileName = Path.GetFileName(uri.LocalPath);
return string.IsNullOrEmpty(fileName)
? "index.html"
: string.Join("_", fileName.Split(Path.GetInvalidFileNameChars()));
}
public void Dispose()
{
_httpClient.Dispose();
_semaphore.Dispose();
}
}
public class Program
{
public static async Task Main()
{
var urls = new[]
{
"https://golang.org/doc/gopher/frontpage.png",
"https://golang.org/doc/gopher/doc.png",
"https://golang.org/doc/gopher/pkg.png"
};
await using var downloader = new Downloader();
await downloader.DownloadUrlsAsync(urls);
}
}
package main
import (
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"sync"
)
func main() {
// List of URLs to download
urls := []string{
"https://golang.org/doc/gopher/frontpage.png",
"https://golang.org/doc/gopher/doc.png",
"https://golang.org/doc/gopher/pkg.png",
}
// Create downloads directory if it doesn't exist
if err := os.MkdirAll("downloads", 0755); err != nil {
fmt.Printf("Error creating downloads directory: %v\n", err)
return
}
// Create a wait group to track goroutines
var wg sync.WaitGroup
// Create a semaphore to limit concurrent downloads
semaphore := make(chan struct{}, 3) // Limit to 3 concurrent downloads
// Process each URL
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
// Acquire semaphore
semaphore <- struct{}{}
defer func() { <-semaphore }()
if err := downloadFile(url); err != nil {
fmt.Printf("Error downloading %s: %v\n", url, err)
} else {
fmt.Printf("Successfully downloaded: %s\n", url)
}
}(url)
}
// Wait for all downloads to complete
wg.Wait()
fmt.Println("All downloads completed!")
}
func downloadFile(url string) error {
// Create HTTP client with timeout
client := &http.Client{}
// Create request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
// Add User-Agent header to avoid being blocked
req.Header.Set("User-Agent", "Go-Downloader/1.0")
// Get the data
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error downloading: %v", err)
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
// Create the file
fileName := getFileName(url)
filePath := filepath.Join("downloads", fileName)
out, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("error creating file: %v", err)
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
return fmt.Errorf("error writing to file: %v", err)
}
return nil
}
func getFileName(url string) string {
// Extract filename from URL
fileName := path.Base(url)
// If the URL ends with a slash, use a default name
if fileName == "/" || fileName == "." {
fileName = "index.html"
}
// Clean the filename
fileName = strings.ReplaceAll(fileName, "?", "_")
fileName = strings.ReplaceAll(fileName, "&", "_")
return fileName
}
import kotlinx.coroutines.*
import java.io.File
import java.net.URL
import java.nio.file.Files
import java.nio.file.Paths
class UrlDownloader {
private val downloadPath = "downloads"
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val semaphore = kotlinx.coroutines.sync.Semaphore(permits = 3) // Limit concurrent downloads
init {
// Create downloads directory if it doesn't exist
Files.createDirectories(Paths.get(downloadPath))
}
suspend fun downloadUrls(urls: List<String>) = coroutineScope {
val jobs = urls.map { url ->
launch {
semaphore.withPermit {
try {
downloadFile(url)
println("Successfully downloaded: $url")
} catch (e: Exception) {
println("Error downloading $url: ${e.message}")
}
}
}
}
jobs.joinAll()
println("All downloads completed!")
}
private suspend fun downloadFile(url: String) {
withContext(Dispatchers.IO) {
val connection = URL(url).openConnection()
connection.setRequestProperty("User-Agent", "Kotlin-Downloader/1.0")
val fileName = getFileName(url)
val filePath = File(downloadPath, fileName)
connection.getInputStream().use { input ->
filePath.outputStream().use { output ->
input.copyTo(output)
}
}
}
}
private fun getFileName(url: String): String {
val baseName = url.substringAfterLast('/')
return when {
baseName.isEmpty() -> "index.html"
else -> baseName.replace(Regex("[?&]"), "_")
}
}
}
suspend fun main() {
val urls = listOf(
"https://golang.org/doc/gopher/frontpage.png",
"https://golang.org/doc/gopher/doc.png",
"https://golang.org/doc/gopher/pkg.png"
)
val downloader = UrlDownloader()
downloader.downloadUrls(urls)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment