Cutting a live stream into separate mp4 files - c#

I am doing a research for cutting a live stream in piece and save it as mp4 files. I am using this source for the proof of concept:
https://learn.microsoft.com/en-us/azure/media-services/media-services-dotnet-creating-live-encoder-enabled-channel#download-sample
And this is the example code I use:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MediaServices.Client;
using Newtonsoft.Json.Linq;
namespace AMSLiveTest
{
class Program
{
private const string StreamingEndpointName = "streamingendpoint001";
private const string ChannelName = "channel001";
private const string AssetlName = "asset001";
private const string ProgramlName = "program001";
// Read values from the App.config file.
private static readonly string _mediaServicesAccountName =
ConfigurationManager.AppSettings["MediaServicesAccountName"];
private static readonly string _mediaServicesAccountKey =
ConfigurationManager.AppSettings["MediaServicesAccountKey"];
// Field for service context.
private static CloudMediaContext _context = null;
private static MediaServicesCredentials _cachedCredentials = null;
static void Main(string[] args)
{
// Create and cache the Media Services credentials in a static class variable.
_cachedCredentials = new MediaServicesCredentials(
_mediaServicesAccountName,
_mediaServicesAccountKey);
// Used the cached credentials to create CloudMediaContext.
_context = new CloudMediaContext(_cachedCredentials);
IChannel channel = CreateAndStartChannel();
// Set the Live Encoder to point to the channel's input endpoint:
string ingestUrl = channel.Input.Endpoints.FirstOrDefault().Url.ToString();
// Use the previewEndpoint to preview and verify
// that the input from the encoder is actually reaching the Channel.
string previewEndpoint = channel.Preview.Endpoints.FirstOrDefault().Url.ToString();
IProgram program = CreateAndStartProgram(channel);
ILocator locator = CreateLocatorForAsset(program.Asset, program.ArchiveWindowLength);
IStreamingEndpoint streamingEndpoint = CreateAndStartStreamingEndpoint();
GetLocatorsInAllStreamingEndpoints(program.Asset);
// Once you are done streaming, clean up your resources.
Cleanup(streamingEndpoint, channel);
}
public static IChannel CreateAndStartChannel()
{
//If you want to change the Smooth fragments to HLS segment ratio, you would set the ChannelCreationOptions’s Output property.
IChannel channel = _context.Channels.Create(
new ChannelCreationOptions
{
Name = ChannelName,
Input = CreateChannelInput(),
Preview = CreateChannelPreview()
});
//Starting and stopping Channels can take some time to execute. To determine the state of operations after calling Start or Stop, query the IChannel.State .
channel.Start();
return channel;
}
private static ChannelInput CreateChannelInput()
{
return new ChannelInput
{
StreamingProtocol = StreamingProtocol.RTMP,
AccessControl = new ChannelAccessControl
{
IPAllowList = new List<IPRange>
{
new IPRange
{
Name = "TestChannelInput001",
// Setting 0.0.0.0 for Address and 0 for SubnetPrefixLength
// will allow access to IP addresses.
Address = IPAddress.Parse("0.0.0.0"),
SubnetPrefixLength = 0
}
}
}
};
}
private static ChannelPreview CreateChannelPreview()
{
return new ChannelPreview
{
AccessControl = new ChannelAccessControl
{
IPAllowList = new List<IPRange>
{
new IPRange
{
Name = "TestChannelPreview001",
// Setting 0.0.0.0 for Address and 0 for SubnetPrefixLength
// will allow access to IP addresses.
Address = IPAddress.Parse("0.0.0.0"),
SubnetPrefixLength = 0
}
}
}
};
}
public static void UpdateCrossSiteAccessPoliciesForChannel(IChannel channel)
{
var clientPolicy =
#"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers=""*"" http-methods=""*"">
<domain uri=""*""/>
</allow-from>
<grant-to>
<resource path=""/"" include-subpaths=""true""/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>";
var xdomainPolicy =
#"<?xml version=""1.0"" ?>
<cross-domain-policy>
<allow-access-from domain=""*"" />
</cross-domain-policy>";
channel.CrossSiteAccessPolicies.ClientAccessPolicy = clientPolicy;
channel.CrossSiteAccessPolicies.CrossDomainPolicy = xdomainPolicy;
channel.Update();
}
public static IProgram CreateAndStartProgram(IChannel channel)
{
IAsset asset = _context.Assets.Create(AssetlName, AssetCreationOptions.None);
// Create a Program on the Channel. You can have multiple Programs that overlap or are sequential;
// however each Program must have a unique name within your Media Services account.
IProgram program = channel.Programs.Create(ProgramlName, TimeSpan.FromHours(3), asset.Id);
program.Start();
return program;
}
public static ILocator CreateLocatorForAsset(IAsset asset, TimeSpan ArchiveWindowLength)
{
// You cannot create a streaming locator using an AccessPolicy that includes write or delete permissions.
var locator = _context.Locators.CreateLocator
(
LocatorType.OnDemandOrigin,
asset,
_context.AccessPolicies.Create
(
"Live Stream Policy",
ArchiveWindowLength,
AccessPermissions.Read
)
);
return locator;
}
public static IStreamingEndpoint CreateAndStartStreamingEndpoint()
{
var options = new StreamingEndpointCreationOptions
{
Name = StreamingEndpointName,
ScaleUnits = 1,
AccessControl = GetAccessControl(),
CacheControl = GetCacheControl()
};
IStreamingEndpoint streamingEndpoint = _context.StreamingEndpoints.Create(options);
streamingEndpoint.Start();
return streamingEndpoint;
}
private static StreamingEndpointAccessControl GetAccessControl()
{
return new StreamingEndpointAccessControl
{
IPAllowList = new List<IPRange>
{
new IPRange
{
Name = "Allow all",
Address = IPAddress.Parse("0.0.0.0"),
SubnetPrefixLength = 0
}
},
AkamaiSignatureHeaderAuthenticationKeyList = new List<AkamaiSignatureHeaderAuthenticationKey>
{
new AkamaiSignatureHeaderAuthenticationKey
{
Identifier = "My key",
Expiration = DateTime.UtcNow + TimeSpan.FromDays(365),
Base64Key = Convert.ToBase64String(GenerateRandomBytes(16))
}
}
};
}
private static byte[] GenerateRandomBytes(int length)
{
var bytes = new byte[length];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(bytes);
}
return bytes;
}
private static StreamingEndpointCacheControl GetCacheControl()
{
return new StreamingEndpointCacheControl
{
MaxAge = TimeSpan.FromSeconds(1000)
};
}
public static void UpdateCrossSiteAccessPoliciesForStreamingEndpoint(IStreamingEndpoint streamingEndpoint)
{
var clientPolicy =
#"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers=""*"" http-methods=""*"">
<domain uri=""*""/>
</allow-from>
<grant-to>
<resource path=""/"" include-subpaths=""true""/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>";
var xdomainPolicy =
#"<?xml version=""1.0"" ?>
<cross-domain-policy>
<allow-access-from domain=""*"" />
</cross-domain-policy>";
streamingEndpoint.CrossSiteAccessPolicies.ClientAccessPolicy = clientPolicy;
streamingEndpoint.CrossSiteAccessPolicies.CrossDomainPolicy = xdomainPolicy;
streamingEndpoint.Update();
}
public static void GetLocatorsInAllStreamingEndpoints(IAsset asset)
{
var locators = asset.Locators.Where(l => l.Type == LocatorType.OnDemandOrigin);
var ismFile = asset.AssetFiles.AsEnumerable().FirstOrDefault(a => a.Name.EndsWith(".ism"));
var template = new UriTemplate("{contentAccessComponent}/{ismFileName}/manifest");
var urls = locators.SelectMany(l =>
_context
.StreamingEndpoints
.AsEnumerable()
.Where(se => se.State == StreamingEndpointState.Running)
.Select(
se =>
template.BindByPosition(new Uri("http://" + se.HostName),
l.ContentAccessComponent,
ismFile.Name)))
.ToArray();
}
public static void Cleanup(IStreamingEndpoint streamingEndpoint,
IChannel channel)
{
if (streamingEndpoint != null)
{
streamingEndpoint.Stop();
streamingEndpoint.Delete();
}
IAsset asset;
if (channel != null)
{
foreach (var program in channel.Programs)
{
asset = _context.Assets.Where(se => se.Id == program.AssetId)
.FirstOrDefault();
program.Stop();
program.Delete();
if (asset != null)
{
foreach (var l in asset.Locators)
l.Delete();
asset.Delete();
}
}
channel.Stop();
channel.Delete();
}
}
}
}
Now I want to make a method to cut a live stream for example every 15 minutes and save it as mp4 but don't know where to start.
Can someone point me in the right direction?
Kind regards
UPDATE:
I want to save the mp4 files on my hard disk.

You can use ffmpeg to save the stream on your hard drive. Please see the code:
ffmpeg -i InputStreamURL -acodec aac -strict -2 -vcodec libx264 -hls_wrap 100 -f hls -hls_time 20 /var/www/html/ts/1.m3u8
The -hls_time 20 use for the time of your saved data. In fact you use ffmpeg for a HLS stream but its doing the things you want. You could access the saved data on your Hard drive in /var/www/html/ts/ (and you could change this as you want).
Or could use VLC :
cvlc -vvv rtp://#239.1.2.1:60001
--sout '#std{access=livehttp{seglen=5,delsegs=true,numsegs=5,
index=/path/to/stream.m3u8,
index-url=http://example.org/stream-########.ts},
mux=ts{use-key-frames},
dst=/path/to/stream-########.ts}'
In above command either you could change the duration of the saved data in hard drive. But I personally do not use VLC and prefer use ffmpeg
Finally you could call one of above command in your C# application and see the result. Good Luck.

Related

AmazonSQSClient not refreshing AWSCredentials when Credentials File is updated

When my AWS Credentials File (see docs) is updated by an external process the AmazonSQSClient doesn't re-read it, SendMessageAsync fails with a security/token error.
We use a custom powershell script to refresh the local AWS cred's file periodically. The script works fine, the file is refreshed prior to the credentials expiring on AWS. However, if my app is running when the file is refreshed the new credentials are not re-read from the file, the "client" will show that the previous credentials are still in use.
The AWS docs list several AWSCredential providers but none of them seem to be the correct choice...I think..
Restarting the app works, the new credentials are read correctly and messages are sent until the next time the cred's file is updated.
using (var client = new AmazonSQSClient(Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
I don't think there is a way for a running app to pick up the default credentials being refreshed in credentials file. There is a solution for Node.js loading credentials from a JSON file. You can create a similar solution in C#. You can also run a local DB to store credentials so whenever credentials file is updated DB table or JSON file is also updated. You will need to use access key and secret key in your SQS client constructor as opposed to using default credentials.
// Load these from JSON file or DB.
var accessKey = "";
var secretKey = "";
using (var client = new AmazonSQSClient(accessKey, secretKey, Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
The following works "ok" but I've only tested it with one profile and the file watcher is not as timely as you'd like so I'd recommend you wrap your usage inside a Retry mechanism.
// Usage..
var credentials = new AwsCredentialsFile();
using (var client = new AmazonSQSClient(credentials, Amazon.RegionEndpoint.EUWest1))
{
return client.SendMessageAsync(request);
}
public class AwsCredentialsFile : AWSCredentials
{
// https://docs.aws.amazon.com/sdk-for-net/v2/developer-guide/net-dg-config-creds.html#creds-file
private const string DefaultProfileName = "default";
private static ConcurrentDictionary<string, ImmutableCredentials> _credentials = new ConcurrentDictionary<string, ImmutableCredentials>(StringComparer.OrdinalIgnoreCase);
private static FileSystemWatcher _watcher = BuildFileSystemWatcher();
private readonly System.Text.Encoding _encoding;
private readonly string _profileName;
public AwsCredentialsFile()
: this(AwsCredentialsFile.DefaultProfileName, System.Text.Encoding.UTF8)
{
}
public AwsCredentialsFile(string profileName)
: this(profileName, System.Text.Encoding.UTF8)
{
}
public AwsCredentialsFile(string profileName, System.Text.Encoding encoding)
{
_profileName = profileName;
_encoding = encoding;
}
private static FileSystemWatcher BuildFileSystemWatcher()
{
var watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(GetDefaultCredentialsFilePath()),
NotifyFilter = NotifyFilters.LastWrite,
Filter = "credentials"
};
watcher.Changed += (object source, FileSystemEventArgs e) => { _credentials?.Clear(); };
watcher.EnableRaisingEvents = true;
return watcher;
}
public static string GetDefaultCredentialsFilePath()
{
return System.Environment.ExpandEnvironmentVariables(#"C:\Users\%USERNAME%\.aws\credentials");
}
public static (string AccessKey, string SecretAccessKey, string Token) ReadCredentialsFromFile(string profileName, System.Text.Encoding encoding)
{
var profile = $"[{profileName}]";
string awsAccessKeyId = null;
string awsSecretAccessKey = null;
string token = null;
var lines = File.ReadAllLines(GetDefaultCredentialsFilePath(), encoding);
for (int i = 0; i < lines.Length; i++)
{
var text = lines[i];
if (text.Equals(profile, StringComparison.OrdinalIgnoreCase))
{
awsAccessKeyId = lines[i + 1].Replace("aws_access_key_id = ", string.Empty);
awsSecretAccessKey = lines[i + 2].Replace("aws_secret_access_key = ", string.Empty);
if (lines.Length >= i + 3)
{
token = lines[i + 3].Replace("aws_session_token = ", string.Empty);
}
break;
}
}
var result = (AccessKey: awsAccessKeyId, SecretAccessKey: awsSecretAccessKey, Token: token);
return result;
}
public override ImmutableCredentials GetCredentials()
{
if (_credentials.TryGetValue(_profileName, out ImmutableCredentials value))
{
return value;
}
else
{
var (AccessKey, SecretAccessKey, Token) = ReadCredentialsFromFile(_profileName, _encoding);
var credentials = new ImmutableCredentials(AccessKey, SecretAccessKey, Token);
_credentials.TryAdd(_profileName, credentials);
return credentials;
}
}
}

The 'await' operator can only be used within an async > method

I have the following controller:
[Authorize]
public class SetupController : ApiController
{
[HttpPost]
public Task async SetupPartnerPackAsync(SetupInformation info)
{
if (info.SslCertificateGenerate)
{
SetupManager.CreateX509Certificate(info);
}
else
{
SetupManager.LoadX509Certificate(info);
}
info.SslCertificateThumbprint = SetupManager.GetX509CertificateThumbprint(info);
info.AzureAppKeyCredential = SetupManager.GetX509CertificateInformation(info);
await SetupManager.RegisterAzureADApplication(info);
}
}
But I have the following 2 error which seems simple:
Severity Code Description Project File Line Suppression State
Error CS1520 Method must have a return
type InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 24 Active
Severity Code Description Project File Line Suppression State
Error CS4033 The 'await' operator can only be used within an async
method. Consider marking this method with the 'async' modifier and
changing its return type to
'Task'. InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 39 Active
However I am not sure how to fix this, as the operation can take some time to complete, it really needs to be asybnc
and the setupmanager
using CERTENROLLLib;
using Microsoft.Identity.Client;
using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;
using OfficeDevPnP.Core;
using OfficeDevPnP.Core.Entities;
using OfficeDevPnP.Core.Framework.Provisioning.Model;
using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Resources;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
namespace InnovationInABoxWebApi.Components
{
public static class SetupManager
{
public static String GetX509CertificateThumbprint(SetupInformation info)
{
var certificate = info.AuthenticationCertificate;
return (certificate.Thumbprint.ToUpper());
}
public static String GetX509CertificateInformation(SetupInformation info)
{
// var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
var certificate = info.AuthenticationCertificate;
//var certificate = new X509Certificate2();
//if (info.SslCertificateGenerate)
//{
// certificate.Import($#"{basePath}{info.SslCertificateCommonName}.cer");
//}
//else
//{
// certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
//}
var rawCert = certificate.GetRawCertData();
var base64Cert = System.Convert.ToBase64String(rawCert);
var rawCertHash = certificate.GetCertHash();
var base64CertHash = System.Convert.ToBase64String(rawCertHash);
var KeyId = System.Guid.NewGuid().ToString();
var keyCredential =
"{" +
"\"customKeyIdentifier\": \"" + base64CertHash + "\"," +
"\"keyId\": \"" + KeyId + "\"," +
"\"type\": \"AsymmetricX509Cert\"," +
"\"usage\": \"Verify\"," +
"\"key\": \"" + base64Cert + "\"" +
"}";
return (keyCredential);
}
public static void CreateX509Certificate(SetupInformation info)
{
var certificate = CreateSelfSignedCertificate(info.SslCertificateCommonName.ToLower(),
info.SslCertificateStartDate, info.SslCertificateEndDate, info.SslCertificatePassword);
SaveCertificateFiles(info, certificate);
}
public static void LoadX509Certificate(SetupInformation info)
{
var certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
info.AuthenticationCertificate = certificate;
info.SslCertificateCommonName = certificate.SubjectName.Name;
}
public static void SaveCertificateFiles(SetupInformation info, X509Certificate2 certificate)
{
info.AuthenticationCertificate = certificate;
//var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
//info.SslCertificateFile = $#"{basePath}{info.SslCertificateCommonName}.pfx";
//var pfx = certificate.Export(X509ContentType.Pfx, info.SslCertificatePassword);
//System.IO.File.WriteAllBytes(info.SslCertificateFile, pfx);
//var cer = certificate.Export(X509ContentType.Cert);
//System.IO.File.WriteAllBytes($#"{basePath}{info.SslCertificateCommonName}.cer", cer);
}
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, DateTime startDate, DateTime endDate, String password)
{
// Create DistinguishedName for subject and issuer
var name = new CX500DistinguishedName();
name.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// Create a new Private Key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft RSA SChannel Cryptographic Provider";
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.Length = 2048;
privateKey.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)";
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
privateKey.Create();
// Define the hashing algorithm
var serverauthoid = new CObjectId();
serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // Server Authentication
var ekuoids = new CObjectIds();
ekuoids.Add(serverauthoid);
var ekuext = new CX509ExtensionEnhancedKeyUsage();
ekuext.InitializeEncode(ekuoids);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, String.Empty);
cert.Subject = name;
cert.Issuer = cert.Subject;
cert.NotBefore = startDate;
cert.NotAfter = endDate;
cert.X509Extensions.Add((CX509Extension)ekuext);
cert.Encode();
// Enroll the certificate
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert);
string certData = enroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64HEADER);
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
certData, EncodingType.XCN_CRYPT_STRING_BASE64HEADER, String.Empty);
var base64encoded = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainWithRoot);
// Instantiate the target class with the PKCS#12 data
return new X509Certificate2(
System.Convert.FromBase64String(base64encoded), password,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
}
public async static Task RegisterAzureADApplication(SetupInformation info)
{
// Fix the App URL
if (!info.AzureWebAppUrl.EndsWith("/"))
{
info.AzureWebAppUrl = info.AzureWebAppUrl + "/";
}
// Load the App Manifest template
//Stream stream = typeof(SetupManager)
// .Assembly
// .GetManifestResourceStream("OfficeDevPnP.PartnerPack.Setup.Resources.azure-ad-app-manifest.json");
using (StreamReader sr = new StreamReader("Resources\azure-ad-app-manifest.json"))
{
// Get the JSON manifest
var jsonApplication = sr.ReadToEnd();
var application = JsonConvert.DeserializeObject<AzureAdApplication>(jsonApplication);
var keyCredential = JsonConvert.DeserializeObject<KeyCredential>(info.AzureAppKeyCredential);
application.displayName = info.ApplicationName;
application.homepage = info.AzureWebAppUrl;
application.identifierUris = new List<String>();
application.identifierUris.Add(info.ApplicationUniqueUri);
application.keyCredentials = new List<KeyCredential>();
application.keyCredentials.Add(keyCredential);
application.replyUrls = new List<String>();
application.replyUrls.Add(info.AzureWebAppUrl);
// Generate the Application Shared Secret
var startDate = DateTime.Now;
Byte[] bytes = new Byte[32];
using (var rand = System.Security.Cryptography.RandomNumberGenerator.Create())
{
rand.GetBytes(bytes);
}
info.AzureAppSharedSecret = System.Convert.ToBase64String(bytes);
application.passwordCredentials = new List<object>();
application.passwordCredentials.Add(new AzureAdApplicationPasswordCredential
{
CustomKeyIdentifier = null,
StartDate = startDate.ToString("o"),
EndDate = startDate.AddYears(2).ToString("o"),
KeyId = Guid.NewGuid().ToString(),
Value = info.AzureAppSharedSecret,
});
// Get an Access Token to create the application via Microsoft Graph
var office365AzureADAccessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
AzureManagementUtility.MicrosoftGraphResourceId,
ConfigurationManager.AppSettings["O365:ClientId"]);
var azureAdApplicationCreated = false;
// Create the Azure AD Application
try
{
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
catch (ApplicationException ex)
{
var graphError = JsonConvert.DeserializeObject<GraphError>(((HttpException)ex.InnerException).Message);
if (graphError != null && graphError.error.code == "Request_BadRequest" &&
graphError.error.message.Contains("identifierUris already exists"))
{
// We need to remove the existing application
// Thus, retrieve it
String jsonApplications = await HttpHelper.MakeGetRequestForStringAsync(
String.Format("{0}applications?$filter=identifierUris/any(c:c+eq+'{1}')",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
HttpUtility.UrlEncode(info.ApplicationUniqueUri)),
office365AzureADAccessToken);
var applications = JsonConvert.DeserializeObject<AzureAdApplications>(jsonApplications);
var applicationToUpdate = applications.Applications.FirstOrDefault();
if (applicationToUpdate != null)
{
// Remove it
await HttpHelper.MakeDeleteRequestAsync(
String.Format("{0}applications/{1}",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
applicationToUpdate.Id),
office365AzureADAccessToken);
// And add it again
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
}
}
if (azureAdApplicationCreated)
{
// TODO: We should upload the logo
// property mainLogo: stream of the application via PATCH
}
}
}
public static async Task CreateAzureADApplication(SetupInformation info, AzureAdApplication application, string office365AzureADAccessToken)
{
String jsonResponse = await HttpHelper.MakePostRequestForStringAsync(
String.Format("{0}applications",
AzureManagementUtility.MicrosoftGraphBetaBaseUri),
application,
"application/json", office365AzureADAccessToken);
var azureAdApplication = JsonConvert.DeserializeObject<AzureAdApplication>(jsonResponse);
info.AzureAppClientId = azureAdApplication.AppId.HasValue ? azureAdApplication.AppId.Value : Guid.Empty;
}
}
}
You are defining the method with async word after the return type Task, async must be before Task.
public async Task SetupPartnerPackAsync(SetupInformation info)
{
.
.
.

Create new MediaType via C# (For PackageAction)

I have created a package that requires the creation of an extra MediaType, since packaging it is not available in the package screens.
To circumvent this issue I've started researching into creating a new MediaType via Package Actions. Lastly started following this quick tutorial Umbraco V7 Compatible Packages
Have added a class (below), compiled it (in Debug Mode), copied it to the /bin/ folder of Umbraco, then added the dll as a package file, along with everything else.
Then, on another instance of Umbraco, installed it from local package.
Problem is: IT IS NOT WORKING!
At the moment I have no idea of what could be wrong, so if anyone has any suggestions, they're welcome.
Below is the class I have created:
using System;
using System.Collections.Generic;
using System.Xml;
using umbraco.interfaces;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Social_Media_Channels.Installer
{
public class AddMediaAction : IPackageAction
{
public string Alias()
{
return "SocialMediaChannels_AddThemes";
}
public bool Execute(string packageName, XmlNode xmlData)
{
string STEP = string.Empty;
if (UmbracoVersion.Current.Major >= 7)
{
try
{
#region MediaType
STEP = "Adding MediaType";
LogHelper.Info(typeof(AddMediaAction), STEP);
MediaHelper.AddMediaType();
#endregion
#region Theme Images
STEP = "Adding Media Themes";
LogHelper.Info(typeof(AddMediaAction), STEP);
#endregion
return true;
}
catch (Exception ex)
{
var message = string.Concat("Error at install ", Alias(), " package action: " + STEP, ex);
LogHelper.Error(typeof(AddMediaAction), message, ex);
return false;
}
}
return false;
}
public bool Undo(string packageName, XmlNode xmlData)
{
if (UmbracoVersion.Current.Major >= 7)
{
//MediaType mediaType = new MediaType();
}
return true;
}
public XmlNode SampleXml()
{
var xml = string.Format("<Action runat=\"install\" undo=\"true\" alias=\"{0}\" />", Alias());
XmlDocument x = new XmlDocument();
x.LoadXml(xml);
return x;
}
}
}
Below is the Helper Class:
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Social_Media_Channels.Installer
{
public class MediaHelper
{
private readonly static string MediaTypeName = "Social Media Theme";
// LogHelper.Error<TranslationHelper>("Failed to add Opening Soon localization values to language file", ex);
public static void AddMediaType()
{
MediaType mediaType = new MediaType(0);
mediaType.AllowedAsRoot = true;
mediaType.Name = MediaTypeName;
mediaType.Description = "Container for the Social Media Channel Theme Images";
mediaType.IsContainer = true;
//Allowed child nodes
var children = new List<ContentTypeSort>
{
new ContentTypeSort(1031, 0),
new ContentTypeSort(1032, 1)
};
mediaType.AllowedContentTypes = children;
//Add properties
var name = new PropertyType(new DataTypeDefinition(-88, "themeName"));
name.Name = "Theme Name";
name.Description = "Name for the theme";
var url = new PropertyType(new DataTypeDefinition(-88, "themeUrl"));
url.Name = "Theme Url";
url.Description = "Url for the original theme";
var createdBy = new PropertyType(new DataTypeDefinition(-88, "createdBy"));
createdBy.Name = "Created By";
createdBy.Description = "Theme Author";
var createdDate = new PropertyType(new DataTypeDefinition(-41, "createdDate"));
createdDate.Name = "Created Date";
createdDate.Description = "Date the Theme was created";
mediaType.AddPropertyType(name, "Image");
mediaType.AddPropertyType(url, "Image");
mediaType.AddPropertyType(createdBy, "Image");
mediaType.AddPropertyType(createdDate, "Image");
}
public static void RemoveMediaType()
{
}
}
}
And in Package Actions (Umbraco) I have added the following line
<Action runat="install" undo="true" alias="SocialMediaChannels_AddThemes" />
Suggestions or corrections? Anyone?
Turns out there were a lot of things wrong with my original code. Finally got it working after lots of investigation.
Basically the Adding (which was causing it to fail) became:
public static void AddMediaType()
{
MediaType mediaType = new MediaType(-1);
mediaType.AllowedAsRoot = true;
mediaType.Name = NAME;
mediaType.Description = "Container for the Social Media Channel Theme Images";
mediaType.IsContainer = true;
mediaType.Icon = "icon-picture";
mediaType.Alias = ALIAS;
//Allowed child nodes
var children = new List<ContentTypeSort>
{
new ContentTypeSort(FOLDER_ID, 0),
new ContentTypeSort(IMAGE_ID, 1)
};
mediaType.AllowedContentTypes = children;
DataTypeService dataTypeService = (DataTypeService)ApplicationContext.Current.Services.DataTypeService;
//Add properties
var name = new PropertyType(dataTypeService.GetDataTypeDefinitionById(TEXT_ID), "themeName");
name.Name = "Theme Name";
name.Description = "Name for the theme";
name.SortOrder = 0;
var url = new PropertyType(dataTypeService.GetDataTypeDefinitionById(TEXT_ID), "themeUrl");
url.Name = "Theme Url";
url.Description = "Url for the original theme";
url.SortOrder = 1;
var createdBy = new PropertyType(dataTypeService.GetDataTypeDefinitionById(TEXT_ID), "createdBy");
createdBy.Name = "Created By";
createdBy.Description = "Theme Author";
createdBy.SortOrder = 2;
var createdDate = new PropertyType(dataTypeService.GetDataTypeDefinitionById(DATE_ID), "createdDate");
createdDate.Name = "Created Date";
createdDate.Description = "Date the Theme was created";
createdDate.SortOrder = 3;
mediaType.AddPropertyType(name, "Image");
mediaType.AddPropertyType(url, "Image");
mediaType.AddPropertyType(createdBy, "Image");
mediaType.AddPropertyType(createdDate, "Image");
ContentTypeService contentTypeService = (ContentTypeService)ApplicationContext.Current.Services.ContentTypeService;
contentTypeService.Save(mediaType);
}
And now it works!
By comparing with the DAMP source code (DAMP) I noticed that the new MediaType class Umbraco.Core.Models.MediaType is missing the Save() method, so no matter how I tested, it simply would not save the new media type.
Anyway, also by using DAMP's example (good old copy/paste), that uses the old assemblies and shows a message 'OBSOLETE', came up with a new version of the code, which worked as expected.
What the code with the old libraries looks like:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Xml;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.datatype;
using umbraco.cms.businesslogic.datatype.controls;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.packager.standardPackageActions;
using umbraco.DataLayer;
using umbraco.interfaces;
using umbraco.IO;
namespace SocialMediaChannels
{
public class AddSocialMediaThemes : IPackageAction
{
private readonly static string MediaTypeName = "Social Media Theme";
private readonly static int LABEL_ID = -92;
private readonly static int UPLOAD_ID = -90;
private readonly static int TEXT_ID = -88;
private readonly static int DATE_ID = -41;
private readonly static int IMAGE_ID = 1031;
private readonly static int FOLDER_ID = 1032;
public IDataType uploadField = new Factory().GetNewObject(new Guid("5032a6e6-69e3-491d-bb28-cd31cd11086c"));
protected static ISqlHelper SqlHelper
{
get
{
return umbraco.BusinessLogic.Application.SqlHelper;
}
}
public string Alias()
{
return "SocialMediaChannels_AddSocialMediaThemes";
}
public bool Execute(string packageName, XmlNode xmlData)
{
bool flag;
try
{
#region MediaType
User adminUser = new User(0);
MediaType theme = CreateMediaType(adminUser, MediaTypeName);
MediaType folder = MediaType.GetByAlias("Folder");
int[] folderStructure = folder.AllowedChildContentTypeIDs;
int newsize = folderStructure.Length + 1;
Array.Resize(ref folderStructure, newsize);
folderStructure[newsize - 1] = theme.Id;
folder.AllowedChildContentTypeIDs = folderStructure;
MediaType image = MediaType.GetByAlias(MediaTypeName);
#endregion
#region Theme Images
#endregion
flag = true;
}
catch
{
flag = false;
}
return flag;
}
public bool Undo(string packageName, XmlNode xmlData)
{
bool flag;
try
{
//remove themes
flag = true;
}
catch
{
flag = false;
}
return flag;
}
public XmlNode SampleXml()
{
string sample = string.Format("<Action runat=\"install\" undo=\"true/false\" alias=\"{0}\" ></Action>", Alias());
return helper.parseStringToXmlNode(sample);
}
/// <summary>
/// Create a Media Type.
/// </summary>
/// <param name="adminUser"></param>
/// <param name="mediaTypeName"></param>
/// <returns></returns>
private MediaType CreateMediaType(User adminUser, string mediaTypeName)
{
MediaType mediaType = MediaType.MakeNew(adminUser, mediaTypeName);
int[] typeIds = { FOLDER_ID, IMAGE_ID };
mediaType.AllowAtRoot = true;
mediaType.Text = MediaTypeName;
mediaType.Description = "Container for the Social Media Channel Theme Images";
mediaType.IsContainerContentType = true;
mediaType.AllowedChildContentTypeIDs = typeIds;
//Add properties
mediaType.AddPropertyType(new DataTypeDefinition(UPLOAD_ID), "umbracoFile", "Upload image");
mediaType.AddPropertyType(new DataTypeDefinition(LABEL_ID), "umbracoWidth", "Width");
mediaType.AddPropertyType(new DataTypeDefinition(LABEL_ID), "umbracoHeight", "Height");
mediaType.AddPropertyType(new DataTypeDefinition(LABEL_ID), "umbracoBytes", "Size");
mediaType.AddPropertyType(new DataTypeDefinition(LABEL_ID), "umbracoExtension", "Type");
mediaType.AddPropertyType(new DataTypeDefinition(TEXT_ID), "themeName", "Name of the Social Channel Theme");
mediaType.AddPropertyType(new DataTypeDefinition(TEXT_ID), "themeUrl", "Url for the Theme");
mediaType.AddPropertyType(new DataTypeDefinition(TEXT_ID), "createdBy", "Author of the Theme");
mediaType.AddPropertyType(new DataTypeDefinition(DATE_ID), "createdDate", "Date the Theme was created");
mediaType.Text = mediaTypeName;
mediaType.IconUrl = "mediaPhoto.gif";
mediaType.Save();
return mediaType;
}
}
}
And the Action looks like this
<Action runat="install" undo="true" alias="SocialMediaChannels_AddSocialMediaThemes" ></Action>

EntityTooSmall in CompleteMultipartUploadResponse

using .NET SDK v.1.5.21.0
I'm trying to upload a large file (63Mb) and I'm following the example at:
http://docs.aws.amazon.com/AmazonS3/latest/dev/LLuploadFileDotNet.html
But using a helper instead the hole code and using jQuery File Upload
https://github.com/blueimp/jQuery-File-Upload/blob/master/basic-plus.html
what I have is:
string bucket = "mybucket";
long totalSize = long.Parse(context.Request.Headers["X-File-Size"]),
maxChunkSize = long.Parse(context.Request.Headers["X-File-MaxChunkSize"]),
uploadedBytes = long.Parse(context.Request.Headers["X-File-UloadedBytes"]),
partNumber = uploadedBytes / maxChunkSize + 1,
fileSize = partNumber * inputStream.Length;
bool lastPart = inputStream.Length < maxChunkSize;
// http://docs.aws.amazon.com/AmazonS3/latest/dev/LLuploadFileDotNet.html
if (partNumber == 1) // initialize upload
{
iView.Utilities.Amazon_S3.S3MultipartUpload.InitializePartToCloud(fileName, bucket);
}
try
{
// upload part
iView.Utilities.Amazon_S3.S3MultipartUpload.UploadPartToCloud(fs, fileName, bucket, (int)partNumber, uploadedBytes, maxChunkSize);
if (lastPart)
// wrap it up and go home
iView.Utilities.Amazon_S3.S3MultipartUpload.CompletePartToCloud(fileName, bucket);
}
catch (System.Exception ex)
{
// Huston, we have a problem!
//Console.WriteLine("Exception occurred: {0}", exception.Message);
iView.Utilities.Amazon_S3.S3MultipartUpload.AbortPartToCloud(fileName, bucket);
}
and
public static class S3MultipartUpload
{
private static string accessKey = System.Configuration.ConfigurationManager.AppSettings["AWSAccessKey"];
private static string secretAccessKey = System.Configuration.ConfigurationManager.AppSettings["AWSSecretKey"];
private static AmazonS3 client = Amazon.AWSClientFactory.CreateAmazonS3Client(accessKey, secretAccessKey);
public static InitiateMultipartUploadResponse initResponse;
public static List<UploadPartResponse> uploadResponses;
public static void InitializePartToCloud(string destinationFilename, string destinationBucket)
{
// 1. Initialize.
uploadResponses = new List<UploadPartResponse>();
InitiateMultipartUploadRequest initRequest =
new InitiateMultipartUploadRequest()
.WithBucketName(destinationBucket)
.WithKey(destinationFilename.TrimStart('/'));
initResponse = client.InitiateMultipartUpload(initRequest);
}
public static void UploadPartToCloud(Stream fileStream, string destinationFilename, string destinationBucket, int partNumber, long uploadedBytes, long maxChunkedBytes)
{
// 2. Upload Parts.
UploadPartRequest request = new UploadPartRequest()
.WithBucketName(destinationBucket)
.WithKey(destinationFilename.TrimStart('/'))
.WithUploadId(initResponse.UploadId)
.WithPartNumber(partNumber)
.WithPartSize(maxChunkedBytes)
.WithFilePosition(uploadedBytes)
.WithInputStream(fileStream) as UploadPartRequest;
uploadResponses.Add(client.UploadPart(request));
}
public static void CompletePartToCloud(string destinationFilename, string destinationBucket)
{
// Step 3: complete.
CompleteMultipartUploadRequest compRequest =
new CompleteMultipartUploadRequest()
.WithBucketName(destinationBucket)
.WithKey(destinationFilename.TrimStart('/'))
.WithUploadId(initResponse.UploadId)
.WithPartETags(uploadResponses);
CompleteMultipartUploadResponse completeUploadResponse =
client.CompleteMultipartUpload(compRequest);
}
public static void AbortPartToCloud(string destinationFilename, string destinationBucket)
{
// abort.
client.AbortMultipartUpload(new AbortMultipartUploadRequest()
.WithBucketName(destinationBucket)
.WithKey(destinationFilename.TrimStart('/'))
.WithUploadId(initResponse.UploadId));
}
}
my maxChunckedSize is 6Mb (6 * (1024*1024)) as I have read that the minimum is 5Mb...
why am I getting "Your proposed upload is smaller than the minimum allowed size" exception? What am I doing wrong?
The error is:
<Error>
<Code>EntityTooSmall</Code>
<Message>Your proposed upload is smaller than the minimum allowed size</Message>
<ETag>d41d8cd98f00b204e9800998ecf8427e</ETag>
<MinSizeAllowed>5242880</MinSizeAllowed>
<ProposedSize>0</ProposedSize>
<RequestId>C70E7A23C87CE5FC</RequestId>
<HostId>pmhuMXdRBSaCDxsQTHzucV5eUNcDORvKY0L4ZLMRBz7Ch1DeMh7BtQ6mmfBCLPM2</HostId>
<PartNumber>1</PartNumber>
</Error>
How can I get ProposedSize if I'm passing the stream and stream length?
Here is a working solution for the latest Amazon SDK (as today: v.1.5.37.0)
Amazon S3 Multipart Upload works like:
Initialize the request using client.InitiateMultipartUpload(initRequest)
Send chunks of the file (loop until the end) using client.UploadPart(request)
Complete the request using client.CompleteMultipartUpload(compRequest)
If anything goes wrong, remember to dispose the client and request, as well fire the abort command using client.AbortMultipartUpload(abortMultipartUploadRequest)
I keep the client in Session as we need this for each chunk upload as well, keep an hold of the ETags that are now used to complete the process.
You can see an example and simple way of doing this in Amazon Docs itself, I ended up having a class to do everything, plus, I have integrated with the lovely jQuery File Upload plugin (Handler code below as well).
The S3MultipartUpload is as follow
public class S3MultipartUpload : IDisposable
{
string accessKey = System.Configuration.ConfigurationManager.AppSettings.Get("AWSAccessKey");
string secretAccessKey = System.Configuration.ConfigurationManager.AppSettings.Get("AWSSecretKey");
AmazonS3 client;
public string OriginalFilename { get; set; }
public string DestinationFilename { get; set; }
public string DestinationBucket { get; set; }
public InitiateMultipartUploadResponse initResponse;
public List<PartETag> uploadPartETags;
public string UploadId { get; private set; }
public S3MultipartUpload(string destinationFilename, string destinationBucket)
{
if (client == null)
{
System.Net.WebRequest.DefaultWebProxy = null; // disable proxy to make upload quicker
client = Amazon.AWSClientFactory.CreateAmazonS3Client(accessKey, secretAccessKey, new AmazonS3Config()
{
RegionEndpoint = Amazon.RegionEndpoint.EUWest1,
CommunicationProtocol = Protocol.HTTP
});
this.OriginalFilename = destinationFilename.TrimStart('/');
this.DestinationFilename = string.Format("{0:yyyy}{0:MM}{0:dd}{0:HH}{0:mm}{0:ss}{0:fffff}_{1}", DateTime.UtcNow, this.OriginalFilename);
this.DestinationBucket = destinationBucket;
this.InitializePartToCloud();
}
}
private void InitializePartToCloud()
{
// 1. Initialize.
uploadPartETags = new List<PartETag>();
InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest();
initRequest.BucketName = this.DestinationBucket;
initRequest.Key = this.DestinationFilename;
// make it public
initRequest.AddHeader("x-amz-acl", "public-read");
initResponse = client.InitiateMultipartUpload(initRequest);
}
public void UploadPartToCloud(Stream fileStream, long uploadedBytes, long maxChunkedBytes)
{
int partNumber = uploadPartETags.Count() + 1; // current part
// 2. Upload Parts.
UploadPartRequest request = new UploadPartRequest();
request.BucketName = this.DestinationBucket;
request.Key = this.DestinationFilename;
request.UploadId = initResponse.UploadId;
request.PartNumber = partNumber;
request.PartSize = fileStream.Length;
//request.FilePosition = uploadedBytes // remove this line?
request.InputStream = fileStream; // as UploadPartRequest;
var up = client.UploadPart(request);
uploadPartETags.Add(new PartETag() { ETag = up.ETag, PartNumber = partNumber });
}
public string CompletePartToCloud()
{
// Step 3: complete.
CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest();
compRequest.BucketName = this.DestinationBucket;
compRequest.Key = this.DestinationFilename;
compRequest.UploadId = initResponse.UploadId;
compRequest.PartETags = uploadPartETags;
string r = "Something went badly wrong";
using (CompleteMultipartUploadResponse completeUploadResponse = client.CompleteMultipartUpload(compRequest))
r = completeUploadResponse.ResponseXml;
return r;
}
public void AbortPartToCloud()
{
// abort.
client.AbortMultipartUpload(new AbortMultipartUploadRequest()
{
BucketName = this.DestinationBucket,
Key = this.DestinationFilename,
UploadId = initResponse.UploadId
});
}
public void Dispose()
{
if (client != null) client.Dispose();
if (initResponse != null) initResponse.Dispose();
}
}
I use DestinationFilename as the destination file so I can avoid the same name, but I keep the OriginalFilename as I needed later.
Using jQuery File Upload Plugin, all works inside a Generic Handler, and the process is something like this:
// Upload partial file
private void UploadPartialFile(string fileName, HttpContext context, List<FilesStatus> statuses)
{
if (context.Request.Files.Count != 1)
throw new HttpRequestValidationException("Attempt to upload chunked file containing more than one fragment per request");
var inputStream = context.Request.Files[0].InputStream;
string contentRange = context.Request.Headers["Content-Range"]; // "bytes 0-6291455/14130271"
int fileSize = int.Parse(contentRange.Split('/')[1]);,
maxChunkSize = int.Parse(context.Request.Headers["X-Max-Chunk-Size"]),
uploadedBytes = int.Parse(contentRange.Replace("bytes ", "").Split('-')[0]);
iView.Utilities.AWS.S3MultipartUpload s3Upload = null;
try
{
// ######################################################################################
// 1. Initialize Amazon S3 Client
if (uploadedBytes == 0)
{
HttpContext.Current.Session["s3-upload"] = new iView.Utilities.AWS.S3MultipartUpload(fileName, awsBucket);
s3Upload = (iView.Utilities.AWS.S3MultipartUpload)HttpContext.Current.Session["s3-upload"];
string msg = System.String.Format("Upload started: {0} ({1:N0}Mb)", s3Upload.DestinationFilename, (fileSize / 1024));
this.Log(msg);
}
// cast current session object
if (s3Upload == null)
s3Upload = (iView.Utilities.AWS.S3MultipartUpload)HttpContext.Current.Session["s3-upload"];
// ######################################################################################
// 2. Send Chunks
s3Upload.UploadPartToCloud(inputStream, uploadedBytes, maxChunkSize);
// ######################################################################################
// 3. Complete Upload
if (uploadedBytes + maxChunkSize > fileSize)
{
string completeRequest = s3Upload.CompletePartToCloud();
this.Log(completeRequest); // log S3 response
s3Upload.Dispose(); // dispose all objects
HttpContext.Current.Session["s3-upload"] = null; // we don't need this anymore
}
}
catch (System.Exception ex)
{
if (ex.InnerException != null)
while (ex.InnerException != null)
ex = ex.InnerException;
this.Log(string.Format("{0}\n\n{1}", ex.Message, ex.StackTrace)); // log error
s3Upload.AbortPartToCloud(); // abort current upload
s3Upload.Dispose(); // dispose all objects
statuses.Add(new FilesStatus(ex.Message));
return;
}
statuses.Add(new FilesStatus(s3Upload.DestinationFilename, fileSize, ""));
}
Keep in mind that to have a Session object inside a Generic Handler, you need to implement IRequiresSessionState so your handler will look like:
public class UploadHandlerSimple : IHttpHandler, IRequiresSessionState
Inside fileupload.js (under _initXHRData) I have added an extra header called X-Max-Chunk-Size so I can pass this to Amazon and calculate if it's the last part of the uploaded file.
Fell free to comment and make smart edits for everyone to use.
I guess you didn't set the content-length of the part inside the UploadPartToCloud() function.

Detecting if internet connection is busy

We are developing an application that will install on PC and it will perform some background upload and download to/from our server. One of the requirement is to detect if the internet connection is currently busy (say above 50% utilization) and if it is, it needs to back-off and try another time. The main reason is to ensure the app does not interfere with user experience if they are in the middle of gaming, watching online movie or aggressively downloading files
After much thinking and research on google and of course SO, I still haven't found a good way on how to implement this, so decided to throw this out here. The application is implemented in C#, .NET 4.0 and I am looking for all forms of responses - either implementation in C# or other languages, pseudo-logic or approach on how to achieve - measuring of internet traffic utilization on local PC with good enough accuracy.
To avoid duplication of effort, so far I have tried these (and why they aren't suitable)
Use WMI to get network statistic. Most SO posts and solutions out there since to refer to this as the approach but it doesn't meet our requirement as measuring of bytes sent/received against network interface capacity (e.g. 1GB Ethernet Card) for utilisation will yield a good measure for LAN traffic but not for internet traffic (where the actual internet bandwidth might only be say 8Mbps)
Use of .NET Network Information Statistics or performance counter - yield similar readings to the above hence have the same shortcomings
Use ICMP (Ping) and measure RTT. It was suggested that 400ms RTT is considered as slow and good indication for busy network, however I was told that user with modem (yes we have to support that), use of reverse proxy or microwave link often get ping above that hence not a good measure
Start downloading a known file and measure the speed - this itself generate traffic which we are trying to avoid, also if this check is done often enough, our application will end up creating a lot of internet traffic - which again not ideal
MOD: Using BITS - this service can be disabled on user pc, requires group policy changes and assume server to be IIS (with custom configuration) and in our case our server is not IIS
So here it is, I'm all confuse and looking for some advice. I highlighted the question text so that you guys don't get lost reading this and wondering what the question is. Thanks.
You could use UPnP to query the router, and retrive the number of bytes sent and received over the network. You could keep checking this value on the router to determine what the activity is. Unfortunately this functionality doesn't seem to be well-documented, but it is possible to implement UPnP communication functionality within a C# application. You will need to use UDP to query for the router (UPnP discover), and once you have found the device, query its functionality, and then query the number of packets sent and received for the Internet Gateway Device using a WebClient (TCP).
Code for a UPnP library:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;
namespace UPNPLib
{
public class RouterElement
{
public RouterElement()
{
}
public override string ToString()
{
return Name;
}
public List children = new List();
public RouterElement parent;
public string Name;
public string Value;
public RouterElement this[string name] {
get
{
foreach (RouterElement et in children)
{
if (et.Name.ToLower().Contains(name.ToLower()))
{
return et;
}
}
foreach (RouterElement et in children)
{
Console.WriteLine(et.Name);
}
throw new KeyNotFoundException("Unable to find the specified entry");
}
}
public RouterElement(XmlNode node, RouterElement _parent)
{
Name = node.Name;
if (node.ChildNodes.Count
/// Gets the root URL of the device
///
///
public static string GetRootUrl()
{
StringBuilder mbuilder = new StringBuilder();
mbuilder.Append("M-SEARCH * HTTP/1.1\r\n");
mbuilder.Append("HOST: 239.255.255.250:1900\r\n");
mbuilder.Append("ST:upnp:rootdevice\r\n");
mbuilder.Append("MAN:\"ssdp:discover\"\r\n");
mbuilder.Append("MX:3\r\n\r\n");
UdpClient mclient = new UdpClient();
byte[] dgram = Encoding.ASCII.GetBytes(mbuilder.ToString());
mclient.Send(dgram,dgram.Length,new IPEndPoint(IPAddress.Broadcast,1900));
IPEndPoint mpoint = new IPEndPoint(IPAddress.Any, 0);
rootsearch:
dgram = mclient.Receive(ref mpoint);
string mret = Encoding.ASCII.GetString(dgram);
string orig = mret;
mret = mret.ToLower();
string url = orig.Substring(mret.IndexOf("location:") + "location:".Length, mret.IndexOf("\r", mret.IndexOf("location:")) - (mret.IndexOf("location:") + "location:".Length));
WebClient wclient = new WebClient();
try
{
Console.WriteLine("POLL:" + url);
string reply = wclient.DownloadString(url);
if (!reply.ToLower().Contains("router"))
{
goto rootsearch;
}
}
catch (Exception)
{
goto rootsearch;
}
return url;
}
public static RouterElement enumRouterFunctions(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("serviceList");
RouterElement elem = new RouterElement();
foreach (XmlNode et in rootnodes)
{
RouterElement el = new RouterElement(et, null);
elem.children.Add(el);
}
return elem;
}
public static RouterElement getRouterInformation(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("device");
return new RouterElement(rootnodes[0], null);
}
}
public class RouterMethod
{
string url;
public string MethodName;
string parentname;
string MakeRequest(string URL, byte[] data, string[] headers)
{
Uri mri = new Uri(URL);
TcpClient mclient = new TcpClient();
mclient.Connect(mri.Host, mri.Port);
Stream mstream = mclient.GetStream();
StreamWriter textwriter = new StreamWriter(mstream);
textwriter.Write("POST "+mri.PathAndQuery+" HTTP/1.1\r\n");
textwriter.Write("Connection: Close\r\n");
textwriter.Write("Content-Type: text/xml; charset=\"utf-8\"\r\n");
foreach (string et in headers)
{
textwriter.Write(et + "\r\n");
}
textwriter.Write("Content-Length: " + (data.Length).ToString()+"\r\n");
textwriter.Write("Host: " + mri.Host+":"+mri.Port+"\r\n");
textwriter.Write("\r\n");
textwriter.Flush();
Stream reqstream = mstream;
reqstream.Write(data, 0, data.Length);
reqstream.Flush();
StreamReader reader = new StreamReader(mstream);
while (reader.ReadLine().Length > 2)
{
}
return reader.ReadToEnd();
}
public RouterElement Invoke(string[] args)
{
MemoryStream mstream = new MemoryStream();
StreamWriter mwriter = new StreamWriter(mstream);
//TODO: Implement argument list
string arglist = "";
mwriter.Write("" + "" + "");
mwriter.Write("");//" + arglist + "");
mwriter.Write("");
mwriter.Flush();
List headers = new List();
headers.Add("SOAPAction: \"" + parentschema + "#" + MethodName + "\"");
mstream.Position = 0;
byte[] dgram = new byte[mstream.Length];
mstream.Read(dgram, 0, dgram.Length);
XmlDocument mdoc = new XmlDocument();
string txt = MakeRequest(url, dgram, headers.ToArray());
mdoc.LoadXml(txt);
try
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[0], null);
return elem["Body"].children[0];
}
catch (Exception er)
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[1], null);
return elem["Body"].children[0];
}
}
public List parameters = new List();
string baseurl;
string parentschema;
public RouterMethod(string svcurl, RouterElement element,string pname, string baseURL, string svcpdsc)
{
parentschema = svcpdsc;
baseurl = baseURL;
parentname = pname;
url = svcurl;
MethodName = element["name"].Value;
try
{
foreach (RouterElement et in element["argumentList"].children)
{
parameters.Add(et.children[0].Value);
}
}
catch (KeyNotFoundException)
{
}
}
}
public class RouterService
{
string url;
public string ServiceName;
public List methods = new List();
public RouterMethod GetMethodByNonCaseSensitiveName(string name)
{
foreach (RouterMethod et in methods)
{
if (et.MethodName.ToLower() == name.ToLower())
{
return et;
}
}
throw new KeyNotFoundException();
}
public RouterService(RouterElement element, string baseurl)
{
ServiceName = element["serviceId"].Value;
url = element["controlURL"].Value;
WebClient mclient = new WebClient();
string turtle = element["SCPDURL"].Value;
if (!turtle.ToLower().Contains("http"))
{
turtle = baseurl + turtle;
}
Console.WriteLine("service URL " + turtle);
string axml = mclient.DownloadString(turtle);
XmlDocument mdoc = new XmlDocument();
if (!url.ToLower().Contains("http"))
{
url = baseurl + url;
}
mdoc.LoadXml(axml);
XmlNode mainnode = mdoc.GetElementsByTagName("actionList")[0];
RouterElement actions = new RouterElement(mainnode, null);
foreach (RouterElement et in actions.children)
{
RouterMethod method = new RouterMethod(url, et,ServiceName,baseurl,element["serviceType"].Value);
methods.Add(method);
}
}
}
}
Code for a bandwidth meter:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using UPNPLib;
using System.IO;
namespace bandwidthmeter
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BinaryReader mreader = new BinaryReader(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
if (mreader.BaseStream.Length > 0)
{
prevsent = mreader.ReadInt64();
prevrecv = mreader.ReadInt64();
}
mreader.Close();
List services = new List();
string fullurl = UPNP.GetRootUrl();
RouterElement router = UPNP.enumRouterFunctions(fullurl);
Console.WriteLine("Router feature enumeration complete");
foreach (RouterElement et in router.children)
{
services.Add(new RouterService(et.children[0], fullurl.Substring(0, fullurl.IndexOf("/", "http://".Length+1))));
}
getReceiveDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesReceived");
getSentDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesSent");
Console.WriteLine("Invoking " + getReceiveDelegate.MethodName);
//Console.WriteLine(services[1].GetMethodByNonCaseSensitiveName("GetTotalPacketsSent").Invoke(null));
Timer mymer = new Timer();
mymer.Tick += new EventHandler(mymer_Tick);
mymer.Interval = 1000;
mymer.Start();
FormClosed += new FormClosedEventHandler(Form1_FormClosed);
}
long prevsent = 0;
long prevrecv = 0;
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
BinaryWriter mwriter = new BinaryWriter(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
mwriter.Write(getsent());
mwriter.Write(getreceived());
mwriter.Flush();
mwriter.Close();
}
long getsent()
{
long retval = Convert.ToInt64(getSentDelegate.Invoke(null).children[0].Value);
if (prevsent > retval)
{
retval = prevsent + retval;
}
return retval;
}
long getreceived()
{
long retval = Convert.ToInt64(getReceiveDelegate.Invoke(null).children[0].Value);
if (prevrecv > retval)
{
retval = prevrecv + retval;
}
return retval;
}
void mymer_Tick(object sender, EventArgs e)
{
label1.Text = "Sent: "+(getsent()/1024/1024).ToString()+"MB\nReceived: "+(getreceived()/1024/1024).ToString()+"MB";
}
RouterMethod getSentDelegate;
RouterMethod getReceiveDelegate;
}
}
Have you considered using Background Intelligent Transfer Service (BITS). It's designed to do this job already:
Background Intelligent Transfer Service (BITS) transfers files (downloads or uploads) between a client and server and provides progress information related to the transfers. You can also download files from a peer.
and,
Preserve the responsiveness of other network applications.
I'm not sure if there's a managed interface to it (I can see reference to Powershell Cmdlets), so you might have to use COM interop to use it.
Making the assumption that you are targetting Windows PC's (as you said you were developing in C#), have you looked at BITS, the Background Intelligent Transfer Service?
There's examples of how to hook into it using C# on MSDN and elsewhere, e.g. http://msdn.microsoft.com/en-us/magazine/cc188766.aspx

Categories