예제 #1
0
        public override Task OnConnected()
        {
            var userId = AccountUtils.GetUserId(Context.User.Identity);

            Groups.Add(Context.ConnectionId, KeyUtils.IntToKey(userId));
            return(base.OnConnected());
        }
예제 #2
0
 private async Task InternalPutTextIntoBlob(string text, string containerName)
 {
     if (text != null)
     {
         using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text)))
         {
             var blobName = KeyUtils.IntToKey(this.GetUserId());
             await AzureStorageUtils.UploadBlobAsync(stream, containerName, blobName, "text/plain; charset=utf-8");
         }
     }
 }
예제 #3
0
 private async Task InternalSaveTranscript(int resourceId, string transcript)
 {
     var entity = new GameCopycatEntity2
     {
         PartitionKey    = KeyUtils.IntToKey(resourceId),
         RowKey          = KeyUtils.DateTimeToDescKey(DateTime.UtcNow),
         Transcript      = transcript,
         UserId          = this.GetUserId(),
         UserDisplayName = this.GetUserDisplayName(),
     };
     await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.GameCopycat, entity);
 }
예제 #4
0
        public async Task <IHttpActionResult> GetTranscript(int id)
        {
            var partitionKey    = KeyUtils.IntToKey(id);
            var filterPartition = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey);
            // Since we do RowKey = KeyUtils.DateTimeToDescKey(now), the last written record is at the top.
            var query    = new TableQuery <GameCopycatEntity2>().Where(filterPartition).Take(1);
            var entities = await AzureStorageUtils.ExecuteQueryAsync(AzureStorageUtils.TableNames.GameCopycat, query);

            var result = entities.Any() ? entities.Single() : new object();

            return(Ok(result));
        }
예제 #5
0
        public async Task RegisterWatcher(int exerciseId)
        {
            var          userId       = AccountUtils.GetUserId(Context.User.Identity);
            const string sql          = @"select UserId from dbo.exeExercises where Id = @ExerciseId;";
            var          authorUserId = (await DapperHelper.QueryResilientlyAsync <int>(sql, new { ExerciseId = exerciseId, })).Single();

            if (userId == authorUserId)
            {
                var groupName = KeyUtils.IntToKey(exerciseId); // Corresponds to PieceTypes.PartitionKey
                await Groups.Add(Context.ConnectionId, groupName);
            }
            /* You should not manually remove the connection from the group when the user disconnects. This action is automatically performed by the SignalR framework. */
        }
예제 #6
0
        public async Task <IHttpActionResult> PostResourceView([FromUri] int id, [FromBody] JObject value)
        {
            // A suggestion may have resourceId == null(0?) when the resource is made on the client in code and points to Google

            // TODO. Idea: Get the unauthenicated user's session cookie and log views for the session in a separate datastore.
            var userId = this.GetUserId(); // GetUserId() returns 0 for an unauthenticated user. That's fine. We log every view.

            var logEnity = new DynamicTableEntity(KeyUtils.GetCurrentTimeKey(), KeyUtils.IntToKey(userId));

            logEnity["Json"] = new EntityProperty(value.ToString());
            var logTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryLog, logEnity);

            if (userId != 0 && id != 0)
            {
                var viewTask = DapperHelper.ExecuteResilientlyAsync("dbo.libPostResourceView",
                                                                    new
                {
                    UserId     = userId,
                    ResourceId = id,
                },
                                                                    CommandType.StoredProcedure);

                // We use KeyUtils.LocalTimeToInvertedKey() to keep the local time and to order last records first for retrieval.
                var localTime = (string)value["localTime"];
                var rowKey    = KeyUtils.LocalTimeToInvertedKey(localTime);

                if (String.IsNullOrEmpty(rowKey))
                {
                    throw new ArgumentOutOfRangeException(localTime);
                }

                var historyEntity = new LibraryHistoryEntity
                {
                    PartitionKey = KeyUtils.IntToKey(userId),
                    RowKey       = rowKey,
                    ResourceId   = id,
                };
                var historyTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryHistory, historyEntity);

                // Do all the tasks in parallel.
                await Task.WhenAll(viewTask, historyTask, logTask);
            }
            else
            {
                await logTask;
            }

            return(StatusCode(HttpStatusCode.NoContent));
        }
예제 #7
0
        public async Task <IHttpActionResult> GetFromFreeswitch(string uuid, int userId = 0)
        {
            RecordingDetails result = null;

            // Produce file paths.
            var workDirPath    = GeneralUtils.GetAppDataDir();
            var sourceFilePath = Path.Combine(workDirPath, uuid + ".wav");
            var outputFilePath = Path.ChangeExtension(sourceFilePath, "mp3");

            // The path structure in the Blob Storage is userIdKey/timeKey/filename.ext
            var userIdKey      = KeyUtils.IntToKey(userId);
            var timeKey        = KeyUtils.GetTimeAsBase32();
            var outputBlobName = String.Format("{0}/{1}/{2}.mp3", userIdKey, timeKey, uuid);
            var logBlobName    = Path.ChangeExtension(outputBlobName, "log");

            try
            {
                // Convert to MP3. Increase the audio volume by 10dB, convert to MP3 CBR 64kbit/s.
                var arguments = String.Format("-i \"{0}\" -af \"volume=10dB\" -b:a 64k \"{1}\"", sourceFilePath, outputFilePath);
                var logText   = RecordingUtils.RunFfmpeg(arguments);

                var containerName = AzureStorageUtils.ContainerNames.Artifacts;
                var taskMp3       = AzureStorageUtils.UploadFromFileAsync(outputFilePath, containerName, outputBlobName, "audio/mpeg");
                var taskLog       = AzureStorageUtils.UploadTextAsync(logText, containerName, logBlobName, "text/plain");
                // Upload the blobs simultaneously.
                await Task.WhenAll(taskMp3, taskLog);

                // Get the recording's duration.
                var duration = RecordingUtils.GetDurationFromFfmpegLogOrMp3File(logText, outputFilePath);

                // Delete the original WAV file on success.
                File.Delete(sourceFilePath);

                // The JSON encoder with default settings doesn't make upper-case -> lower-case letter conversion of property names. The receiving side is case-sensitive.
                result = new RecordingDetails
                {
                    BlobName      = outputBlobName,
                    TotalDuration = duration,
                };
            }
            finally
            {
                // Clean up the MP3 file anyway.
                File.Delete(outputFilePath);
            }

            return(Ok <RecordingDetails>(result));
        }
예제 #8
0
        public async Task <IHttpActionResult> PutPresentation([FromBody] JObject value)
        {
            var text = (string)value["presentation"];

            if (text != null)
            {
                using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text)))
                {
                    var blobName = KeyUtils.IntToKey(this.GetUserId());
                    await AzureStorageUtils.UploadBlobAsync(stream, AzureStorageUtils.ContainerNames.Presentations, blobName, "text/plain; charset=utf-8");

                    // CloudBlockBlob.UploadTextAsync() writes ContentType "application/octet-stream".
                }
            }
            return(StatusCode(text != null ? HttpStatusCode.NoContent : HttpStatusCode.BadRequest));
        }
예제 #9
0
        public async Task <IHttpActionResult> GetAllReviewPieces(int exerciseId)
        {
            var filterPartition = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, ReviewPiece.GetPartitionKey(exerciseId));
            var query           = new TableQuery <ReviewPiece>().Where(filterPartition);
            var allPieces       = await AzureStorageUtils.ExecuteQueryAsync(AzureStorageUtils.TableNames.ReviewPieces, query);

            // Enforce access rights. The exercise author cannot see review items in an unfinished review. An access entity is written when a review is finished. See ReviewsApiController.PostFinishReview
            var userAccessCode = ReviewPiece.PieceTypes.Viewer + KeyUtils.IntToKey(this.GetUserId());
            // Find the ReviewIds which are allowed to access.
            var reviewIds = allPieces
                            .Where(i => ReviewPiece.GetUserAccessCode(i.RowKey) == userAccessCode)
                            .Select(i => ReviewPiece.GetReviewId(i.RowKey))
                            .ToList();

            RemoveAccessEntries(allPieces);

            // Filter the record set.
            var accessablePieces = allPieces.Where(i => reviewIds.Contains(ReviewPiece.GetReviewId(i.RowKey)));
            var piecesArr        = accessablePieces.Select(i => i.Json).ToArray();

            return(Ok(piecesArr));
        }
예제 #10
0
        public async Task <ActionResult> Claim(string id)
        {
            var userIdKey    = KeyUtils.IntToKey(0);
            var longTimeKey  = id + this.GetExtId();
            var blobName     = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, "metadata", "json");
            var blob         = AzureStorageUtils.GetBlob(AzureStorageUtils.ContainerNames.Artifacts, blobName);
            var metadataJson = await blob.DownloadTextAsync();

            var metadata         = JObject.Parse(metadataJson);
            var serviceType      = (string)metadata["serviceType"];
            var cardId           = (Guid?)metadata["cardId"];
            var title            = (string)metadata["title"];
            var comment          = (string)metadata["comment"];
            var details          = metadata["recordingDetails"];
            var recordingDetails = details.ToObject <RecordingDetails>();

            var exerciseId = await ExerciseUtils.CreateExercise(recordingDetails.BlobName, this.GetUserId(),
                                                                serviceType, ArtifactType.Mp3, recordingDetails.TotalDuration, title, cardId, comment, details.ToString(Formatting.None));

            //~~ Redirect to the View exercise page.
            return(RedirectToAction("View", new { Id = exerciseId }));
        }
예제 #11
0
        private async Task <DapperHelper.PageItems <ResourceDto> > GetHistoryResources(int userId, string secondaryFilter, int offset = 0, int limit = 0)
        {
            var result = new DapperHelper.PageItems <ResourceDto>();

            var partitionKey    = KeyUtils.IntToKey(userId);
            var filterPartition = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey);
            var combinedFilter  = !String.IsNullOrEmpty(secondaryFilter) ? TableQuery.CombineFilters(filterPartition, TableOperators.And, secondaryFilter) : filterPartition;
            var query           = new TableQuery <LibraryHistoryEntity>().Where(combinedFilter);
            var entities        = await AzureStorageUtils.ExecuteQueryAsync(AzureStorageUtils.TableNames.LibraryHistory, query);

            result.TotalCount = entities.Count();

            if (entities.Any())
            {
                var historyItems = (limit != 0)
                        ? entities.Skip(offset).Take(limit)
                        : entities;

                result.Items = await HydrateHistoryItems(historyItems, userId);
            }

            return(result);
        }
예제 #12
0
        // GET: /history
        public async Task <ActionResult> History()
        {
            // Send all the days there are records for. We will enable/disable days in the calendar on the page accordingly. RowKeys in the table are "inverted" local time.
            var days = new List <string>();

            if (this.IsAuthenticated())
            {
                var userId          = this.GetUserId();
                var partitionKey    = KeyUtils.IntToKey(userId);
                var filterPartition = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey);
                var query           = new TableQuery <TableEntity>().Where(filterPartition);
                var table           = AzureStorageUtils.GetCloudTable(AzureStorageUtils.TableNames.LibraryHistory);

                TableQuerySegment <TableEntity> currentSegment = null;
                while (currentSegment == null || currentSegment.ContinuationToken != null)
                {
                    currentSegment = await table.ExecuteQuerySegmentedAsync <TableEntity>(
                        query,
                        currentSegment != null?currentSegment.ContinuationToken : null
                        );

                    // Format 2014-01-21 as "140121"
                    var localDays = currentSegment.Results
                                    .GroupBy(i => i.RowKey.Substring(0, 6))
                                    .Select(i => KeyUtils.InvertedKeyToLocalTime(i.Key, 3, "", "d2").Substring(2))
                    ;

                    days.AddRange(localDays);
                }
            }

            //var daysParam = days.Distinct();
            //ViewBag.DaysParamJson = JsonUtils.SerializeAsJson(daysParam);
            ViewBag.DaysParam = days.Distinct();

            return(View());
        }
예제 #13
0
        // TODO OBSOLETE. Do not rename the files parameter. The form inputs are bounded by this name.
        public async Task <ActionResult> SaveRecordings(IEnumerable <HttpPostedFileBase> files, string serviceType, Guid?cardId, string title, string comment)
        {
            /*
             * 1. Validate input.
             * 2. Save the original media files to blobs.
             * 3. Call the remote transcoding service. The service converts files to MP3, merges them into a single file, saves it to a blob and returns its name and the recording's duration.
             * 4. Create a database record.
             * 5. Redirect to the exercise page.
             */

            // 1. Validate input.

            // Find out the action to redirect to on error. We use the referrer string. RedirectToAction seems accept a case-insensitive parameter.
            var referrerAction = (Request.UrlReferrer.Segments.Skip(2).Take(1).SingleOrDefault() ?? "Index").Trim('/');

            //referrerAction = referrerAction.First().ToString().ToUpper() + referrerAction.Substring(1).ToLower();
            // var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));

            if (files == null || files.All(i => i == null))
            {
                return(RedirectToAction(referrerAction, new { error = "no_file" }));
            }

            // There may be empty form parts from input elements with no file selected.
            var originalFiles = files.Where(i => i != null);

            var contentType = originalFiles
                              .Select(i => i.ContentType)
                              .Distinct()
                              .Single()
            ;

            var acceptedContentTypes = (new[] { MediaType.Mpeg, MediaType.Mp3, MediaType.Amr, MediaType.Gpp, MediaType.QuickTime });

            if (!acceptedContentTypes.Contains(contentType))
            {
                return(RedirectToAction(referrerAction, new { error = "wrong_file_format" }));
            }

            // 2. Save the original media files to blobs.

            var userId    = this.GetUserId();
            var userIdKey = KeyUtils.IntToKey(userId);
            var timeKey   = KeyUtils.GetTimeAsBase32();
            var index     = 0;
            var extension = MediaType.GetExtension(contentType);

            //var blobNames = new List<string>();

            foreach (var file in originalFiles)
            {
                using (var inputStream = file.InputStream)
                {
                    // The directory structure in the Blob Storage is userIdKey/timeKey/index.ext. Runnymede.Helper.Controllers.RecordingsController.Get() relies on this structure.
                    var blobName = String.Format("{0}/{1}/{2}.{3}", userIdKey, timeKey, index, extension);
                    await AzureStorageUtils.UploadBlobAsync(inputStream, AzureStorageUtils.ContainerNames.Artifacts, blobName, file.ContentType);

                    index++;
                }
            }

            // 3. Call the remote transcoding service.

            var                 host      = ConfigurationManager.AppSettings["RecorderHost"];
            var                 urlFormat = "http://{0}/api/recordings/transcoded/?userIdKey={1}&timeKey={2}&extension={3}&count={4}";
            var                 url       = String.Format(urlFormat, host, userIdKey, timeKey, extension, originalFiles.Count());
            HttpClient          client    = new HttpClient();
            HttpResponseMessage response  = await client.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                return(RedirectToAction(referrerAction, new { error = "transcoding_error" }));
            }
            // Error is returned as HTML. Then we get error here: No MediaTypeFormatter is available to read an object of type 'JObject' from content with media type 'text/html'.
            var recordingDetails = await response.Content.ReadAsAsync <RecordingDetails>();

            // Make sure the duration is known. If the transcoder has failed to parse the ffmpeg logs, it returns DurationMsec = 0.
            if (recordingDetails.TotalDuration == 0)
            {
                // Read the blob and try to determine the duration directly.
                recordingDetails.TotalDuration =
                    await RecordingUtils.GetMp3Duration(AzureStorageUtils.ContainerNames.Artifacts, recordingDetails.BlobName);
            }

            // 4. Create a database record.
            var exerciseId = await ExerciseUtils.CreateExercise(recordingDetails.BlobName, userId,
                                                                serviceType, ArtifactType.Mp3, recordingDetails.TotalDuration, title, cardId, comment);

            // 5. Redirect to the exercise page.
            return(RedirectToAction("View", "Exercises", new { Id = exerciseId }));
        }
예제 #14
0
        public async Task <IHttpActionResult> PostTracks(string metadataName)
        {
            //~~ Validate.
            if (!Request.Content.IsMimeMultipartContent())
            {
                return(BadRequest());
            }

            // Parse the request body parts.
            var streamProvider = await Request.Content.ReadAsMultipartAsync();

            // Check mediaType of the files
            var mediaTypes = streamProvider.Contents
                             .Select(i => i.Headers)
                             .Where(i => i.ContentDisposition.Name.Trim('"') != metadataName)
                             .Select(i => i.ContentType)
                             //.Where(i => i != null) // ContentType is null in the 'metadata' part.
                             .Select(i => i.MediaType)
                             .Distinct()
            ;
            var mediaType          = mediaTypes.FirstOrDefault();
            var acceptedMediaTypes = new[] { MediaType.Mpeg, MediaType.Mp3, MediaType.Amr, MediaType.Gpp, MediaType.QuickTime };

            if ((mediaTypes.Count() != 1) || !acceptedMediaTypes.Contains(mediaType))
            {
                return(BadRequest(String.Join(", ", mediaTypes)));
            }

            //~~ Save the original media files to blobs.

            var userId    = this.GetUserId(); // May be 0 if the user is unauthenticated
            var userIdKey = KeyUtils.IntToKey(userId);
            var timeKey   = KeyUtils.GetTimeAsBase32();
            // The length of extId is 12 chars.
            var extId = (userId != 0)
                ? null
                : this.GetExtId();
            var longTimeKey = timeKey + extId; // If an operand of string concatenation is null, an empty string is substituted.
            var extension   = MediaType.GetExtension(mediaType);

            var tracks    = new List <KeyValuePair <string, MemoryStream> >();
            var fileNames = new List <string>();

            foreach (var content in streamProvider.Contents)
            {
                var name        = content.Headers.ContentDisposition.Name.Trim('"');
                var contentType = content.Headers.ContentType;
                // We have checked above, if ContentType has a value, it has the right MediaType.
                if ((name != metadataName) && (contentType != null))
                {
                    fileNames.Add(name);
                    var stream = await content.ReadAsStreamAsync();

                    using (stream)
                    {
                        var memStream = new MemoryStream();
                        await stream.CopyToAsync(memStream);

                        var pair = new KeyValuePair <string, MemoryStream>(name, memStream);
                        tracks.Add(pair);
                    }
                }
            }

            // Upload all blobs in parallel.
            var tasks = tracks.Select(i =>
            {
                var blobName = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, i.Key, extension);
                return(AzureStorageUtils.UploadBlobAsync(i.Value, AzureStorageUtils.ContainerNames.Artifacts, blobName, mediaType));
            });
            await Task.WhenAll(tasks);

            //~~ Call the transcoding service.

            ///* We send the file names as a comma separated list. There is also a binding in the Web API like this:
            //public IHttpActionResult GetFoo([FromUri] int[] ids); Call: /Foo?ids=1&ids=2&ids=3 or /Foo?ids[0]=1&ids[1]=2&ids[2]=3
            //public IHttpActionResult GetFoo([FromUri] List<string> ids); Call: /Foo?ids[]="a"&ids[]="b"&ids[]="c"
            //*/
            //var host = ConfigurationManager.AppSettings["RecorderHost"];
            //var urlFormat = "http://{0}/api/recordings/transcoded/?userIdKey={1}&timeKey={2}&extension={3}&fileNames={4}";
            //var url = String.Format(urlFormat, host, userIdKey, longTimeKey, extension, String.Join(",", fileNames));
            //HttpClient client = new HttpClient();
            //HttpResponseMessage response = await client.GetAsync(url);
            //if (!response.IsSuccessStatusCode)
            //{
            //    // return RedirectToAction(referrerAction, new { error = "transcoding_error" });
            //    return InternalServerError(new Exception("Transcoding error. " + response.StatusCode.ToString()));
            //}
            //// Error is returned as HTML. Then we get error here: No MediaTypeFormatter is available to read an object of type 'JObject' from content with media type 'text/html'.
            //var recordingDetails = await response.Content.ReadAsAsync<RecordingDetails>();

            //// Make sure the duration is known. If the transcoder has failed to parse the ffmpeg logs, it returns DurationMsec = 0.
            //if (recordingDetails.TotalDuration == 0)
            //{
            //    // Read the blob and try to determine the duration directly.
            //    recordingDetails.TotalDuration =
            //        await RecordingUtils.GetMp3Duration(AzureStorageUtils.ContainerNames.Artifacts, recordingDetails.BlobName);
            //}

            var transcoder       = new RecordingTranscoder();
            var recordingDetails = await transcoder.Transcode(tracks, userIdKey, longTimeKey, extension);

            // Release the memory streams.
            tracks.ForEach(i =>
            {
                i.Value.Dispose();
            });

            //~~ Read the metadata.
            // Chrome wraps the part name in double-quotes.
            var metadataContent = streamProvider.Contents
                                  .Single(i => i.Headers.ContentDisposition.Name.Trim('"') == metadataName)
            ;
            var metadataJson = await metadataContent.ReadAsStringAsync();

            var metadata    = JObject.Parse(metadataJson);
            var serviceType = (string)metadata["serviceType"];
            var cardId      = (Guid?)metadata["cardId"];
            var title       = (string)metadata["title"];
            var comment     = (string)metadata["comment"];

            var serializer = new JsonSerializer()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
            var details = JObject.FromObject(recordingDetails, serializer);

            if (userId != 0)
            {
                //~~ Create a database record.
                var exerciseId = await ExerciseUtils.CreateExercise(recordingDetails.BlobName, userId,
                                                                    serviceType, ArtifactType.Mp3, recordingDetails.TotalDuration, title, cardId, comment, details.ToString(Formatting.None));

                //~~ The client will redirect to the View exercise page.
                return(Ok(new { ExerciseId = exerciseId }));
            }
            else
            {
                //~~ Save the details for future use.
                metadata.Add(new JProperty("recordingDetails", details));
                var blobName = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, "metadata", "json");
                await AzureStorageUtils.UploadTextAsync(metadata.ToString(), AzureStorageUtils.ContainerNames.Artifacts, blobName, MediaType.Json);

                //~~ The client will redirect to the Signup page. longTimeKey will be built from timeKey on the Claim page.
                return(Ok(new { Key = timeKey }));
            }
        }
예제 #15
0
        public async Task <ActionResult> SaveRecordingMobile(HttpPostedFileBase fileInput, string recorderId, string recordingTitle)
        {
            /*
             * 1. Save the original media file to a blob
             * 2. Call the remote Transcoding service. Pass the blob name.
             * 3. The Transcoding service saves the MP3 file to a blob and returns its name and the recording's duration.
             * 4. Create a database record.
             */
            if (fileInput == null)
            {
                return(RedirectToAction("RecordSpeech", new { error = "no_file" }));
            }

            //  return RedirectToAction("RecordSpeech", new { error = "test01" });

            var contentType         = fileInput.ContentType;
            var acceptedContentType = (new[] { MediaType.Amr, MediaType.Gpp, MediaType.QuickTime }).Contains(contentType);

            if (!acceptedContentType)
            {
                return(RedirectToAction("RecordSpeech", new { error = contentType }));
            }

            var userId = this.GetUserId();

            // 1. Save the original file.
            // The directory structure in Blob Storage is userIdKey/timeKey/originalFileName.ext
            var timeKey  = KeyUtils.GetTimeAsBase32();
            var fileName = fileInput.FileName;

            // Sanitize the fileName. Reserved URL characters must be properly escaped.
            fileName = String.IsNullOrWhiteSpace(fileName)
                ? timeKey
                : Uri.EscapeUriString(fileName.Trim());
            var invalidChars = Path.GetInvalidFileNameChars();

            fileName = new String(fileName.Select(i => invalidChars.Contains(i) ? '_' : i).ToArray());
            if (!Path.HasExtension(fileName))
            {
                Path.ChangeExtension(fileName, MediaType.GetExtension(contentType));
            }

            var blobName = KeyUtils.IntToKey(userId) + AzureStorageUtils.DefaultDirectoryDelimiter + timeKey + AzureStorageUtils.DefaultDirectoryDelimiter + fileName;

            using (var stream = fileInput.InputStream)
            {
                await AzureStorageUtils.UploadBlobAsync(stream, AzureStorageUtils.ContainerNames.Recordings, blobName, contentType);
            }

            // 2. Call the transcoding service.
            var                 host     = ConfigurationManager.AppSettings["RecorderHost"];
            var                 url      = String.Format("http://{0}/api/recordings/transcoded/?inputBlobName={1}", host, blobName);
            HttpClient          client   = new HttpClient();
            HttpResponseMessage response = await client.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                return(RedirectToAction("RecordSpeech", new { error = "transcoding_error" }));
            }
            // 3. Get the results from the service.
            // Error is returned as HTML. Then we get error here: No MediaTypeFormatter is available to read an object of type 'JObject' from content with media type 'text/html'.
            var value = await response.Content.ReadAsAsync <JObject>();

            var outputBlobName = (string)value["outputBlobName"];
            var duration       = Convert.ToDecimal((int)value["durationMsec"] / 1000.0m);

            // The transcoder may return -1 if it has failed to parse the ffmpeg logs.
            if (duration < 0)
            {
                // Read the blob and try to determine the duration directly.
                duration = await RecordingUtils.GetMp3Duration(AzureStorageUtils.ContainerNames.Recordings, outputBlobName);
            }
            // 4. Create a database record.
            var exerciseId = await ExerciseUtils.CreateExercise(outputBlobName, userId, ServiceType.IeltsSpeaking, ArtifactType.Mp3,
                                                                duration, recordingTitle);

            // 5. Redirect to the exercise page.
            return(RedirectToAction("View", new { Id = exerciseId }));
        }
예제 #16
0
 public static string GetRowKey(int reviewId, string pieceType, int id)
 {
     // Corresponds to app.reviews.Editor.getRowKey()
     return KeyUtils.IntToKey(reviewId) + pieceType + KeyUtils.IntToKey(id);
 }
예제 #17
0
 public static string GetPartitionKey(int exerciseId)
 {
     // Corresponds to app.reviews.Editor.getPartitionKey()
     return KeyUtils.IntToKey(exerciseId);
 }
예제 #18
0
        public async Task <ActionResult> SavePhotos(IEnumerable <HttpPostedFileBase> files, IEnumerable <int> rotations, string serviceType, Guid?cardId, string title, string comment)
        {
            // Find out the action to redirect to on error. We use the referrer string. RedirectToAction seems accept a case-insensitive parameter.
            var referrerAction = (Request.UrlReferrer.Segments.Skip(2).Take(1).SingleOrDefault() ?? "Index").Trim('/');

            //referrerAction = referrerAction.First().ToString().ToUpper() + referrerAction.Substring(1).ToLower();
            // var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));

            if (files == null || files.All(i => i == null))
            {
                return(RedirectToAction(referrerAction, new { error = "no_file" }));
            }
            if (files.Count() != rotations.Count())
            {
                return(RedirectToAction(referrerAction, new { error = "wrong_rotations" }));
            }

            var items = files
                        .Select((i, idx) => new { File = i, Rotation = rotations.ElementAt(idx) })
                        // There may be empty form parts from input elements with no file selected.
                        .Where(i => i.File != null);

            if (!items.All(i => i.File.ContentType == MediaType.Jpeg))
            {
                return(RedirectToAction(referrerAction, new { error = "wrong_file_format" }));
            }

            var userId    = this.GetUserId();
            var userKey   = KeyUtils.IntToKey(userId);
            var timeKey   = KeyUtils.GetTimeAsBase32();
            var page      = 1; // Pages start counting from 1.
            var blobNames = new List <string>();

            foreach (var item in items)
            {
                using (var inputStream = item.File.InputStream)
                {
                    var blobName = String.Format("{0}/{1}/{2}.original.jpg", userKey, timeKey, page);
                    await AzureStorageUtils.UploadBlobAsync(inputStream, AzureStorageUtils.ContainerNames.Artifacts, blobName, MediaType.Jpeg);

                    using (var memoryStream = new MemoryStream())
                    {
                        /* I am not sure about using JpegBitmapDecoder. Because of the native code dependencies, the PresentationCore and WindowsBase assemblies need to be distributed as x86 and x64, so AnyCPU may be not possible? */
                        inputStream.Seek(0, SeekOrigin.Begin);
                        using (var image = Image.FromStream(inputStream))
                        {
                            RotateFlipType rotateFlipType = RotateFlipType.RotateNoneFlipNone;
                            switch (item.Rotation)
                            {
                            case -1:
                                rotateFlipType = RotateFlipType.Rotate270FlipNone;
                                break;

                            case 1:
                                rotateFlipType = RotateFlipType.Rotate90FlipNone;
                                break;

                            default:
                                break;
                            }
                            ;
                            if (rotateFlipType != RotateFlipType.RotateNoneFlipNone)
                            {
                                image.RotateFlip(rotateFlipType);
                            }
                            // We re-encode the image to decrease the size.
                            var codec         = ImageCodecInfo.GetImageEncoders().FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid);
                            var encoderParams = new EncoderParameters(1);
                            // Highest quality is 100. Quality affects the file size. Do not change it until you have exprimented.
                            int quality = 50; // Do not pass inline. This parameter is passed via pointer and it has to be strongly typed.
                            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
                            image.Save(memoryStream, codec, encoderParams);
                            //image.Save(memoryStream, ImageFormat.Jpeg);
                        }

                        memoryStream.Seek(0, SeekOrigin.Begin);
                        blobName = String.Format("{0}/{1}/{2}.jpg", userKey, timeKey, page);
                        await AzureStorageUtils.UploadBlobAsync(memoryStream, AzureStorageUtils.ContainerNames.Artifacts, blobName, MediaType.Jpeg);

                        blobNames.Add(blobName);
                    }

                    page++;
                }
            }

            var artifact = String.Join(",", blobNames);

            var exerciseId = await ExerciseUtils.CreateExercise(artifact, userId, serviceType, ArtifactType.Jpeg, 0, title, cardId, comment);

            return(RedirectToAction("View", "Exercises", new { Id = exerciseId }));
        }
예제 #19
0
        public async Task <IHttpActionResult> GetPresentation(int id)
        {
            // TODO. Enable CORS on the Azure blob and download presentation directly using AJAX. +http://odetocode.com/blogs/scott/archive/2014/03/31/http-clients-and-azure-blob-storage.aspx
            // +http://blog.cynapta.com/2013/12/cynapta-azure-cors-helper-free-tool-to-manage-cors-rules-for-windows-azure-blob-storage/
            var blob = AzureStorageUtils.GetBlob(AzureStorageUtils.ContainerNames.Presentations, KeyUtils.IntToKey(id));
            var text = await blob.DownloadTextAsync();

            //var text = await AzureStorageUtils.GetBlobAsText(AzureStorageUtils.ContainerNames.Presentations, KeyUtils.IntToKey(id));
            return(new RawStringResult(this, text, RawStringResult.TextMediaType.PlainText));
        }
예제 #20
0
        public async Task <IHttpActionResult> GetReviewPieces(int exerciseId, int reviewId)
        {
            var    filterPartition   = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, ReviewPiece.GetPartitionKey(exerciseId));
            var    filterRowFrom     = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, KeyUtils.IntToKey(reviewId));
            var    filterRowTo       = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, KeyUtils.IntToKey(reviewId) + "Z");
            string combinedRowFilter = TableQuery.CombineFilters(filterRowFrom, TableOperators.And, filterRowTo);
            string combinedFilter    = TableQuery.CombineFilters(filterPartition, TableOperators.And, combinedRowFilter);
            var    query             = new TableQuery <ReviewPiece>().Where(combinedFilter);
            var    pieces            = await AzureStorageUtils.ExecuteQueryAsync(AzureStorageUtils.TableNames.ReviewPieces, query);

            // The access entity was written at the review start. See ReviewsApiController.PostStartReview
            var userRowKey = ReviewPiece.GetRowKey(reviewId, ReviewPiece.PieceTypes.Viewer, this.GetUserId());

            if (pieces.Any(i => i.RowKey == userRowKey))
            {
                RemoveAccessEntries(pieces);
            }
            else
            {
                pieces.Clear();
            }

            var piecesArr = pieces.Select(i => i.Json).ToArray();

            return(Ok(piecesArr));
        }