public async Task <Stream> ConvertToUtf(ScoreMeta scoreMeta)
        {
            var memStream = new MemoryStream();
            var option    = new JsonSerializerOptions()
            {
            };
            await JsonSerializer.SerializeAsync(memStream, scoreMeta, option);

            memStream.Position = 0;
            return(memStream);
        }
        public async Task <IActionResult> CreateScoreAsync(
            [FromRoute(Name = "score_name")] string scoreName,
            [FromBody] PatchScore patchScore)
        {
            var convertor        = new ScoreMetaConvertor();
            var metaFileOperator = new MetaFileOperator(S3Client, BucketName);

            var key = $"{scoreName}/{ScoreMeta.FileName}";


            ScoreMeta scoreMeta;

            try
            {
                scoreMeta = await metaFileOperator.GetScoreMetaAsync(scoreName);
            }
            catch (AmazonS3Exception e)
            {
                Logger.LogError(e, e.Message);
                throw;
            }

            var newCurrentScoreMeta = convertor.ConvertToContent(scoreMeta.GetLastScoreContent(), patchScore);

            var newScoreMetaKey = ScoreMeta.CreateKey();

            scoreMeta[newScoreMetaKey] = newCurrentScoreMeta;

            try
            {
                await metaFileOperator.SaveScoreMetaAsync(scoreName, scoreMeta, newScoreMetaKey);
            }
            catch (Exception e)
            {
                Logger.LogError(e, e.Message);
                throw;
            }

            return(Ok());
        }
        public async Task <IActionResult> CreateVersion(
            [FromRoute(Name = "score_name")] string scoreName,
            [FromForm] NewScoreVersion newScoreVersion)
        {
            var metaFileOperator = new MetaFileOperator(S3Client, BucketName);

            var images  = newScoreVersion.Images;
            var nosText = newScoreVersion.Nos;

            var keyCount = images.GroupBy(x => x.FileName, x => x)
                           .Select(x => (filename: x.Key, count: x.Count()))
                           .Where(x => 2 <= x.count)
                           .ToImmutableArray();

            if (keyCount.Any())
            {
                var errorMessage = string.Join(Environment.NewLine, new string[]
                {
                    "次のファイルが重複しています"
                }.Concat(keyCount.Select(x => $"'{x.filename}'")));
                throw new InvalidOperationException(errorMessage);
            }

            var nos = JsonSerializer.Deserialize <Dictionary <string, double> >(nosText);

            var notContainsNos = images.Select(x => x.FileName)
                                 .Where(x => !nos.ContainsKey(x))
                                 .ToImmutableArray();

            if (notContainsNos.Any())
            {
                var errorMessage = string.Join(Environment.NewLine, new string[]
                {
                    "次のファイルの No が指定されていません"
                }.Concat(notContainsNos.Select(x => $"'{x}'")));
                throw new InvalidOperationException(errorMessage);
            }

            var now = DateTimeOffset.UtcNow;

            var nextVersion = await metaFileOperator.NewVersionNumber(scoreName);

            var versionFileKey = metaFileOperator.CreateVersionFileKey(scoreName, nextVersion, now);

            var versionMeta = new ScoreVersionMeta()
            {
                Version = int.Parse(nextVersion),
            };

            foreach (var formFile in images)
            {
                var no = nos[formFile.FileName].ToString("F").TrimEnd('0').TrimEnd('.');

                var commentPrefix = metaFileOperator.CreatePageCommentPrefix(scoreName, nextVersion, now, no);
                try
                {
                    var key = await metaFileOperator.SaveImage(scoreName, formFile);

                    versionMeta.Pages[no] = new ScoreVersionPageMeta()
                    {
                        No            = no,
                        ImageFileKey  = key,
                        CommentPrefix = commentPrefix,
                    };
                }
                catch (AmazonS3Exception e)
                {
                    Logger.LogError(e, e.Message);
                    throw;
                }
            }

            await metaFileOperator.SaveVersionMetaAsync(versionFileKey, versionMeta);

            var scoreMeta = await metaFileOperator.GetScoreMetaAsync(scoreName);

            var scoreContentMeta = await scoreMeta.GetLastScoreContent().DeepCopyAsync();

            scoreContentMeta.VersionFileKeys[nextVersion] = versionFileKey;

            var scoreMetaKey = ScoreMeta.CreateKey();

            scoreMeta[scoreMetaKey] = scoreContentMeta;

            await metaFileOperator.SaveScoreMetaAsync(scoreName, scoreMeta, scoreMetaKey);

            return(Ok());
        }
        public async Task SaveScoreMetaAsync(string scoreName, ScoreMeta scoreMeta, string metaKey)
        {
            var convertor = new ScoreMetaConvertor();
            var key       = $"{scoreName}/{ScoreMeta.FileName}";

            int retryCount = 5;

            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    var stream = await convertor.ConvertToUtf(scoreMeta);

                    var putRequest = new PutObjectRequest
                    {
                        BucketName  = BucketName,
                        Key         = key,
                        InputStream = stream,
                        CannedACL   = S3CannedACL.PublicRead,
                    };
                    await this.S3Client.PutObjectAsync(putRequest);
                }
                catch (AmazonS3Exception)
                {
                    // Error
                    throw;
                }


                try
                {
                    var request = new GetObjectRequest
                    {
                        BucketName = BucketName,
                        Key        = key,
                    };

                    var response = await this.S3Client.GetObjectAsync(request);

                    var stream = response.ResponseStream;

                    var checkedMeta = await convertor.ConvertToScoreMeta(stream);

                    // 同一のキーが登録される可能性があるがキーには少数3桁の時間を指定してるので
                    // ほぼキーが重なる自体はないと考える
                    // もしキーの重複が問題になる用であれば内容の比較も行う
                    if (checkedMeta.ContainsKey(metaKey))
                    {
                        return;
                    }
                }
                catch (AmazonS3Exception)
                {
                    // skip
                }

                if (i < (retryCount - 1))
                {
                    Thread.Sleep(2000);
                }
            }

            throw new InvalidOperationException("追加更新に失敗しました");
        }