What is the proper way to serve iCal Events? - c#

I am attempting to allow users to add events from an online calendar to the calendars on their device using DDay.iCal. This seems to work fine on iOS and on desktop platforms, but I am running into a snag with Android devices. I run into this message:
Is there a better way to serve this event that would keep that from happening?
public ActionResult ICS(int id)
{
// Get event from Database
var heEvent = HEEvent.GetEventDetails(id);
// Create iCal object
var iCal = new iCalendar();
iCal.Method = "PUBLISH";
// Create iCal Event
var icalEvent = iCal.Create<DDay.iCal.Event>();
icalEvent.Summary = heEvent.Name;
icalEvent.Start = new iCalDateTime(heEvent.TimeBegin.Year, heEvent.TimeBegin.Month, heEvent.TimeBegin.Day, heEvent.TimeBegin.Hour, heEvent.TimeBegin.Minute, 00);
TimeSpan calculatedEventDuration = heEvent.DateEnd.Subtract(heEvent.TimeBegin);
if (calculatedEventDuration.Hours > 1) { icalEvent.Duration = calculatedEventDuration; }
else { icalEvent.Duration = TimeSpan.FromHours(1); } // default to 1 hour if event time is less
icalEvent.Location = heEvent.Location;
// Create a serialization context and serializer factory.
// These will be used to build the serializer for our object.
ISerializationContext ctx = new SerializationContext();
ISerializerFactory factory = new DDay.iCal.Serialization.iCalendar.SerializerFactory();
// Get a serializer for our object
IStringSerializer serializer = factory.Build(iCal.GetType(), ctx) as IStringSerializer;
string output = serializer.SerializeToString(iCal);
var contentType = "text/calendar";
var bytes = Encoding.UTF8.GetBytes(output);
return File(bytes, contentType, String.Format(#"{0}.ics", heEvent.Name.Replace(" ", "_")));
}

Your error, you don't need me to tell you, has nothing to do with Ical. It's entirely between you, your phone, and your technology budget. (Buy an SD card !)
As regards serving Icalendars, you often hear them called feeds, but clients subscribed to an Icalendar poll the calendar url regularly, requesting the whole calendar each time. To avoid lots of needless processing, you will want to persist the calendar(s) somehow so that changes propagate without regenerating the same calendar thousands of times. For this, I suggest the file system and good use of HTTP caching headers. When your calendar is modified, write it as a static file in a web facing directory. Perhaps you're already doing this. Then set sensible caching headers on your webserver and away you go.

Related

How to use Google Cloud Speech (V1 API) for speech to text - need to be able to process over 3 hours audio files properly and efficiently

I am looking for documentation and stuff but could not find a solution yet
Installed NuGet package
Also generated API key
However can't find proper documentation how to use API key
Moreover, I want to be able to upload very long audio files
So what would be the proper way to upload up to 3 hours audio files and get their results?
I have 300$ budget so should be enough
Here my so far code
This code currently fails since I have not set the credentials correctly at the moment which I don't know how to
I also have service account file ready to use
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var speech = SpeechClient.Create();
var config = new RecognitionConfig
{
Encoding = RecognitionConfig.Types.AudioEncoding.Flac,
SampleRateHertz = 48000,
LanguageCode = LanguageCodes.English.UnitedStates
};
var audio = RecognitionAudio.FromStorageUri("1m.flac");
var response = speech.Recognize(config, audio);
foreach (var result in response.Results)
{
foreach (var alternative in result.Alternatives)
{
Debug.WriteLine(alternative.Transcript);
}
}
}
}
I don't want to set environment variable. I have both API key and Service Account json file. How can I manually set?
You need to use the SpeechClientBuilder to create a SpeechClient with custom credentials, if you don't want to use the environment variable. Assuming you've got a service account file somewhere, change this:
var speech = SpeechClient.Create();
to this:
var speech = new SpeechClientBuilder
{
CredentialsPath = "/path/to/your/file"
}.Build();
Note that to perform a long-running recognition operation, you should also use the LongRunningRecognize method - I strongly suspect your current RPC will fail, either explicitly because it's trying to run on a file that's too large, or it'll just time out.
You need to set the environment variable before create the instance of Speech:
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "text-tospeech.json");
Where the second param (text-tospeech.json) is your file with credentials generated by Google Api.

How to call google.apis.dialogflow.v2 in C#

I am new to Google APIs. I want to know how to call Google Dialogflow API in C# to get intent form the input text. But I can't find any example to call Dialogflow using C#.
Please provide some example to call Dialogflow from C#.
If I understand your question correctly you want to call the DialogFlow API from within a C# application (rather than writing fulfillment endpoint(s) that are called from DialogFlow. If that's the case here's a sample for making that call:
using Google.Cloud.Dialogflow.V2;
...
...
var query = new QueryInput
{
Text = new TextInput
{
Text = "Something you want to ask a DF agent",
LanguageCode = "en-us"
}
};
var sessionId = "SomeUniqueId";
var agent = "MyAgentName";
var creds = GoogleCredential.FromJson("{ json google credentials file)");
var channel = new Grpc.Core.Channel(SessionsClient.DefaultEndpoint.Host,
creds.ToChannelCredentials());
var client = SessionsClient.Create(channel);
var dialogFlow = client.DetectIntent(
new SessionName(agent, sessionId),
query
);
channel.ShutdownAsync();
In an earlier version of the DialogFlowAPI I was running into file locking issues when trying to re-deploy a web api project which the channel.ShutDownAsync() seemed to solve. I think this has been fixed in a recent release.
This is the simplest version of a DF request I've used. There is a more complicated version that passes in an input context in this post:
Making DialogFlow v2 DetectIntent Calls w/ C# (including input context)
(Nitpicking: I assume you know DialogFlow will call your code as specified/registered in the action at DialogFlow? So your code can only respond to DialogFlow, and not call it.)
Short answer/redirect:
Don't use Google.Apis.Dialogflow.v2 (with GoogleCloudDialogflowV2WebhookRequest and GoogleCloudDialogflowV2WebhookResponse) but use Google.Cloud.Dialogflow.v2 (with WebhookRequest and WebhookResponse) - see this eTag-error. I will also mention some other alternatives underneath.
Google.Cloud.Dialogflow.v2
Using Google.Cloud.Dialogflow.v2 NuGet (Edit: FWIW: this code was written for the beta-preview):
[HttpPost]
public dynamic PostWithCloudResponse([FromBody] WebhookRequest dialogflowRequest)
{
var intentName = dialogflowRequest.QueryResult.Intent.DisplayName;
var actualQuestion = dialogflowRequest.QueryResult.QueryText;
var testAnswer = $"Dialogflow Request for intent '{intentName}' and question '{actualQuestion}'";
var dialogflowResponse = new WebhookResponse
{
FulfillmentText = testAnswer,
FulfillmentMessages =
{ new Intent.Types.Message
{ SimpleResponses = new Intent.Types.Message.Types.SimpleResponses
{ SimpleResponses_ =
{ new Intent.Types.Message.Types.SimpleResponse
{
DisplayText = testAnswer,
TextToSpeech = testAnswer,
//Ssml = $"<speak>{testAnswer}</speak>"
}
}
}
}
}
};
var jsonResponse = dialogflowResponse.ToString();
return new ContentResult { Content = jsonResponse, ContentType = "application/json" }; ;
}
Edit: It turns out that the model binding may not bind all properties from the 'ProtoBuf-json' correctly (e.g. WebhookRequest.outputContexts[N].parameters),
so one should probably use the Google.Protobuf.JsonParser (e.g. see this documentation).
This parser may trip over unknown fields, so one probably also wants to ignore that. So now I use this code (I may one day make the generic method more generic and thus useful, by making HttpContext.Request.InputStream a parameter):
public ActionResult PostWithCloudResponse()
{
var dialogflowRequest = ParseProtobufRequest<WebhookRequest>();
...
var jsonResponse = dialogflowResponse.ToString();
return new ContentResult { Content = jsonResponse, ContentType = "application/json" }; ;
}
private T ParseProtobufRequest<T>() where T : Google.Protobuf.IMessage, new()
{
// parse ProtoBuf (not 'normal' json) with unknown fields, else it may not bind ProtoBuf correctly
// https://github.com/googleapis/google-cloud-dotnet/issues/2425 "ask the Protobuf code to parse the result"
string requestBody;
using (var reader = new StreamReader(HttpContext.Request.InputStream))
{
requestBody = reader.ReadToEnd();
}
var parser = new Google.Protobuf.JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true));
var typedRequest = parser.Parse<T>(requestBody);
return typedRequest;
}
BTW: This 'ProtoBuf-json' is also the reason to use WebhookResponse.ToString() which in turn uses Google.Protobuf.JsonFormatter.ToDiagnosticString.
Microsoft's BotBuilder
Microsoft's BotBuilder packages and Visual Studio template.
I havent't used it yet, but expect approximately the same code?
Hand written proprietary code
A simple example of incoming request code (called an NLU-Response by Google) is provided by Madoka Chiyoda (Chomado) at Github. The incoming call is simply parsed to her DialogFlowResponseModel:
public static async Task<HttpResponseMessage> Run([...]HttpRequestMessage req, [...]CloudBlockBlob mp3Out, TraceWriter log)
...
var data = await req.Content.ReadAsAsync<Models.DialogFlowResponseModel>();
Gactions
If you plan to work without DialogFlow later on, please note that the interface for Gactions differs significantly from the interface with DialogFlow.
The json-parameters and return-values have some overlap, but nothing gaining you any programming time (probably loosing some time by starting 'over').
However, starting with DialogFlow may gain you some quick dialog-experience (e.g. question & answer design/prototyping).
And the DialogFlow-API does have a NuGet package, where the Gactions-interface does not have a NuGet-package just yet.

Why aren't server-side changes being reflected in my app?

I have a simple C# Windows UAP project that uses a HttpClient to call a PHP script on a web server. The script returns an XML document that contains some GUIDs (*.xml files with the extension omitted, leaving a GUID). My app then uses that data. If I make a change on the server, coincidently causing the PHP script to return different data, my app still uses the old data (to be exact, it does this until the app is restarted). If a call the script using a browser, the data appears how I expect it to, but the app doesn't do what it should with the data. It almost seems like the first response is being cached.
Here's an example:
Say I start with one file in the folder where my PHP script finds all *.xml files (eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml in this case).
The script should and does return:
<?xml version="1.0"?>
<eventlist>
<id>eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
</eventlist>
When I run the app, its response is the same.
So far, all is working as it should.
However, say I add a new XML file in the folder (now eee8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml and eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml). The script returns just like I expect it to:
<?xml version="1.0"?>
<eventlist>
<id>eee8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
<id>eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
</eventlist>
The app's response this time is still the previous one (with only one id element).
This persists until the app restarts. After that, it works like it should--until I make another change in the folder.
Here's my PHP script:
<?php
header('Content-type: text/xml');
$handler = opendir('C:\path\to\folder\\');
$ids = '';
while (($file = readdir($handler)) !== FALSE) {
if (strpos($file, '.xml') !== FALSE) {
$ids .= '<id>'.str_replace('.xml', '', $file).'</id>';
}
}
closedir($handler);
exit('<eventlist>'.$ids.'</eventlist>');
?>
And my app's C# code:
public static async Task<string> ContactServer(ApiMethod m, IProgress<double[]> prog, params KeyValuePair<string, string>[] args) {
using (var client = new HttpClient()) {
var path = m.ToString().ToLower() + "/"; // in this case, is 'list/'.
//...
// other stuff, omitted for simplicity
//...
var fullUrl = "http://example.com/path/to/api/" + path; // in this case, is 'http://example.com/path/to/api/list/'.
var d = await client.GetAsync(new Uri(fullUrl));
var data = await d.Content.ReadAsStringAsync();
Debug.WriteLine(data);
return data;
}
}
Again, my PHP script works fine, but my app gets a different response than I do when I run the script in my browser manually.
Why is this happening?
Windows Runtime which provides the HTTPClient has a very aggressive webcaching strategy to save user's bandwidth. Unless your server explicitly sets a cache duration header, it will return all** requests with the same Uri directly from the cache without even contacting your server.
You can turn off this behaviour by:
Setting a cache duration header (cache-control: no-cache, etc.).
var request = (HttpWebRequest)WebRequest.Create(url.ToString());
if (request.Headers == null)
request.Headers = new WebHeaderCollection();
request.Headers.Add("Cache-Control", "no-cache");
Adding a random number to your requests query string.
string uri = "http://host.com/path?cache=" + Guid.NewGuid().ToString();
Or, as CodeCaster suggested, you could also avoid the caching by using the If-Modified-Since header
HttpWebRequest request = HttpWebRequest.CreateHttp(url);
if (request.Headers == null)
request.Headers = new WebHeaderCollection();
// Make sure that you format time string according RFC.
request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.UtcNow.ToString("r");
or you can add to every request the client makes with
httpClient.DefaultRequestHeaders.IfModifiedSince = DateTime.UtcNow.ToString("r");
Using Windows.Web.Http you could also use
var httpFilter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
httpFilter.CacheControl.ReadBehavior =
Windows.Web.Http.Filters.HttpCacheReadBehavior.MostRecent;
var httpClient = new Windows.Web.Http.HttpClient(httpFilter);
** I have said all requests, but I don't know if that is strictly correct, I will take a look and check and update here, though CodeCaster has suggested GET and HEAD only; I have certainly seen on GET, unsure about others off the top of my head

How do I retrieve some of videos details (duration, date, views) from a playlist with YouTube API V3 in one pass

Hello SO folks and more specifically Google folks monitoring this tag per your support page. I am working from .NET and PlaylistItems.List("snippet,contentDetails") does not do a whole lot compared to the old RSS Feed search. In fact adding part contentDetails adds little value in that only the VideoID is now returned but it is already part of Snippet.ResourceId.VideoId
"kind": "youtube#playlistItem",
bla,
bla,
"contentDetails": {
"videoId": "DLME0PsJRnk"
}
Why add a "part" which is only going to return one bit of information?
How about supporting something like "snippet,contentDetails(duration,PublishedAt,Views)"
I feel this is kind of basic metadata (snippet) most apps would want to list to the users.
While you are at it please please remove this non-sense of Java casing of parameters. Why would you leak-out your language of choice into an API, that's really sad. Yes it is frustrating to keep checking whether I case-spelled them correctly.
Well, it looks like you are forcing "us" to build a list of VideoIds than turn around and make more API calls when I was doing it previously with fewer.
It also means, I will have to manage the 50 items max paging twice, once for the playlist if it is over 50 videos and then manage manually my list of VideosIds paging when I turn around to make Videos.List calls.
Let me know if I missed an All-In-One call type of API, thank you.
Here is what I have now working, let me know if there is a better way
// 20150802
public async Task<List<YouTubeInfo>> PlaylistVideosInfo(String PlaylistID)
{
var YoutubeService = YouTubeService();
//
List<YouTubeInfo> VideoInfos = new List<YouTubeInfo>();
//
var NextPageToken = "";
while (NextPageToken != null)
{
//
var SearchListRequest = YoutubeService.PlaylistItems.List("snippet");
SearchListRequest.PlaylistId = PlaylistID;
SearchListRequest.MaxResults = 50;
SearchListRequest.PageToken = NextPageToken;
// Call the search.list method to retrieve results matching the specified query term.
var SearchListResponse = await SearchListRequest.ExecuteAsync();
// Collect Video IDs from this page
var VideoIDsBatch = new List<string>(); // batch Video detail search by 50 max
foreach (var searchResult in SearchListResponse.Items)
{
VideoIDsBatch.Add(searchResult.Snippet.ResourceId.VideoId);
}
// Make API call for this batch - expect a single page :(
var VideoListRequest = YoutubeService.Videos.List("snippet,contentDetails");
VideoListRequest.Id = String.Join(",", VideoIDsBatch);
VideoListRequest.MaxResults = 50;
var VideoListResponse = await VideoListRequest.ExecuteAsync();
// Collect each Video details
foreach (var VideoResult in VideoListResponse.Items)
{
YouTubeInfoAdd(VideoInfos, VideoResult);
}
// request next page
NextPageToken = SearchListResponse.NextPageToken;
}
// Return All Videos' detail
return VideoInfos;
}

iCal client support for multiple event requests

When a user signs up on our website, I'd like to send them an email that allows them to automatically update their calendar with the classes they have enrolled in. In most cases this will be multiple days/events.
As a test I'm using DDay.ical to create a multi-event request. However, it doesn't seem like either Outlook or the iPhone mail app notices the second event in the ical attachment.
I know that multiple events are supported in the iCal standard. How that doesn't mean that all clients support that scenarios. Do other clients support multi-event ical requests?
I don't think I'm doing anything wrong in code, but I'll post my code fragment to be sure:
// Create event part.
iCalendar iCal1 = new iCalendar();
iCal1.AddLocalTimeZone();
iCal1.Method = "REQUEST";
Event evt1 = iCal1.Create<Event>();
evt1.Start = new iCalDateTime(new DateTime(2014, 8, 4, 12, 30, 00, DateTimeKind.Local));
evt1.End = evt1.Start.AddMinutes(30);
evt1.IsAllDay = false;
evt1.Summary = string.Format("Lesson - {0}", evt1.Start.ToString("MM/dd"));
evt1.Location = "Anytown";
// Add recipients for appointment.
Attendee att1 = new Attendee("mailto:" + "me#MyDomain.com");
att1.RSVP = false;
att1.CommonName = "Me Jones";
evt1.Attendees.Add(att1);
Event evt2 = iCal1.Create<Event>();
evt2.Start = new iCalDateTime(new DateTime(2014, 8, 11, 12, 30, 00, DateTimeKind.Local));
evt2.End = evt1.Start.AddMinutes(30);
evt2.IsAllDay = false;
evt2.Summary = string.Format("Lesson - {0}", evt2.Start.ToString("MM/dd"));
evt2.Location = "AnyTown";
// Add recipients for appointment.
Attendee att2 = new Attendee("mailto:" + "me#MyDomain.com");
att2.RSVP = false;
att2.CommonName = "Me Jones";
evt2.Attendees.Add(att2);
iCalendarSerializer serializer1 = new iCalendarSerializer();
string t = serializer1.SerializeToString(iCal1);
Byte[] bytes = System.Text.Encoding.ASCII.GetBytes(t);
using (var ms = new System.IO.MemoryStream(bytes))
{
using (var a = new System.Net.Mail.Attachment(ms, "meeting.ics", "text/calendar")) //Either load from disk or use a MemoryStream bound to the bytes of a String
{
a.ContentDisposition.Inline = true; //Mark as inline
msg.Attachments.Add(a); //Add it to the message
Mailer.Send(msg);
}
}
Unfortunately, you're completely dependent on the implementation of Icalendar in the various email clients, and these are generally very protective of their users' calendars. They generally all support multi-event Icalendars, but invitations that "go straight in" to a users calendar have to be sent one event at a time. I'm not aware of any exceptions to this.
To process an Icalendar attachment containing more than one event, in Outlook for example, you need to save the attachment to disk, navigate to it and open it. It then opens as a separate calendar, and you need to drag the events one by one into your calendar. Nightmare. This will rarely be worth the trouble to develop.
Another option of course is to host the Icalendar on your website and get your users to subscribe by entering the calendar URL in their client. This has the advantage that changes propagate automatically, but email clients will still treat the events as external (no automatic reminders, in outlook the default is to display them in a separate pane, Gmail at least displays events from different calendars on the same grid.)

Categories