private async Task <LogEntry[]> GetLogEntries(MuxOptions options) { switch (options.Strategy) { case StrategyType.AutoDetect: { var logFilePath = Path.Combine(options.InputPath, HierarchicalLogFileName); if (File.Exists(logFilePath)) { options.Strategy = StrategyType.Hierarchical; } else { options.Strategy = StrategyType.Flat; } return(await GetLogEntries(options).ConfigureAwait(false)); } case StrategyType.Hierarchical: { var logFilePath = Path.Combine(options.InputPath, HierarchicalLogFileName); if (!File.Exists(logFilePath)) { return(null); } return(await LogUtility.GetEntries(logFilePath, _Logger).ConfigureAwait(false)); } case StrategyType.Flat: { var logEntries = new List <LogEntry>(); IEnumerable <string> filePaths; if (Options.InputFilePaths.Count() > 0) { filePaths = Options.InputFilePaths; } else if (Options.InputFileNames.Count() > 0) { filePaths = Options.InputFileNames.Select(inputFileName => Path.Combine(Options.InputPath, inputFileName)); } else { filePaths = Directory.EnumerateFiles(Options.InputPath, "*.*", SearchOption.TopDirectoryOnly); } foreach (var filePath in filePaths) { // filter the input files if a filter is provided. if (Options.InputFilter != null && !Regex.Match(Path.GetFileName(filePath), Options.InputFilter).Success) { continue; } if (filePath.EndsWith(".json") || filePath.EndsWith(".json.rec")) { try { logEntries.AddRange(await LogUtility.GetEntries(filePath, _Logger).ConfigureAwait(false)); } catch (FileNotFoundException) { _Logger.LogWarning("Could not read from {FilePath} as it no longer exists. Is another process running that could have removed it?", filePath); } catch (IOException ex) when(ex.Message.Contains("Stale file handle")) // for Linux { _Logger.LogWarning("Could not read from {FilePath} as the file handle is stale. Is another process running that could have removed it?", filePath); } } } return(logEntries.ToArray()); } default: throw new InvalidOperationException($"Unexpected strategy type '{options.Strategy}'."); } }
private async Task <bool> JsonIntegrityCheck() { var tempFiles = new List <Tuple <string, string> >(); var noErrors = true; _Logger.LogDebug($"Starting integrity check of the directory {_InputDirectory}"); foreach (var jsonFile in Directory.EnumerateFiles(_InputDirectory, "*.json", SearchOption.TopDirectoryOnly)) { _Logger.LogDebug($"JsonIntegrityCheck starting processing file {jsonFile}"); try { if (File.Exists(jsonFile + ".fail")) { _Logger.LogDebug($"File {jsonFile} is already marked as fail-checked. Skipping..."); continue; } var logEntries = await LogUtility.GetEntries(jsonFile, _Logger).ConfigureAwait(false); if (logEntries == null) { continue; } var errorList = new List <string>(); var isValid = true; void AddToErrorList(string message) { isValid = false; _Logger.LogDebug($"JsonIntegrityCheck adds new line to error list for file {jsonFile}: {message}"); errorList.Add(message); } try { var startEntry = GetEvent(logEntries, LogEntry.TypeStartRecording); var stopEntry = GetEvent(logEntries, LogEntry.TypeStopRecording); string audioFilePath = null; string videoFilePath = null; DateTime?firstAudioTimestamp = null; DateTime?firstVideoTimestamp = null; DateTime?lastAudioTimestamp = null; DateTime?lastVideoTimestamp = null; var addDataBlock = false; var addRecordStopTS = false; var addFirstAudioTS = false; var addFirstVideoTS = false; var calcLastAudioTS = false; var calcLastVideoTS = false; var addAudioPath = false; var addVideoPath = false; if (startEntry == null) { AddToErrorList($"Event \"{LogEntry.TypeStartRecording}\" is missing"); } if (stopEntry == null) { AddToErrorList($"Event \"{LogEntry.TypeStopRecording}\" is missing"); } if (isValid) { bool ReportIfMissing <T>(T var, string nodeName, string sectionName) { if (var?.Equals(default(T)) == null) { AddToErrorList($"JSON node \"{nodeName}\" is missing in section \"{sectionName}\"."); return(true); } return(false); } void ReportIfDifferent <T>(T var1, T var2, string nodeName) where T : class, IComparable <T> { if (var1 != null && var2 != null && !var1.Equals(var2)) { AddToErrorList($"Value of JSON node \"{nodeName}\" is different between section \"{LogEntry.TypeStartRecording}\" and \"{LogEntry.TypeStopRecording}\"."); } } ReportIfMissing((startEntry.Timestamp.Ticks == 0) ? (DateTime?)null : startEntry.Timestamp, nameof(startEntry.Timestamp), LogEntry.TypeStartRecording); ReportIfMissing(startEntry.ApplicationId, nameof(startEntry.ApplicationId), LogEntry.TypeStartRecording); ReportIfMissing(startEntry.ChannelId, nameof(startEntry.ChannelId), LogEntry.TypeStartRecording); ReportIfMissing(startEntry.ConnectionId, nameof(startEntry.ConnectionId), LogEntry.TypeStartRecording); ReportIfMissing(stopEntry.ApplicationId, nameof(stopEntry.ApplicationId), LogEntry.TypeStopRecording); ReportIfMissing(stopEntry.ChannelId, nameof(stopEntry.ChannelId), LogEntry.TypeStopRecording); ReportIfMissing(stopEntry.ConnectionId, nameof(stopEntry.ConnectionId), LogEntry.TypeStopRecording); ReportIfDifferent(startEntry.ExternalId, stopEntry.ExternalId, nameof(stopEntry.ExternalId)); ReportIfDifferent(startEntry.ApplicationId, stopEntry.ApplicationId, nameof(stopEntry.ApplicationId)); ReportIfDifferent(startEntry.ChannelId, stopEntry.ChannelId, nameof(stopEntry.ChannelId)); ReportIfDifferent(startEntry.ConnectionId, stopEntry.ConnectionId, nameof(stopEntry.ConnectionId)); var data = stopEntry.Data; if (data != null) { audioFilePath = data.AudioFile; videoFilePath = data.VideoFile; firstAudioTimestamp = data.AudioFirstFrameTimestamp; firstVideoTimestamp = data.VideoFirstFrameTimestamp; lastAudioTimestamp = data.AudioFirstFrameTimestamp; lastVideoTimestamp = data.VideoLastFrameTimestamp; } if (stopEntry.Timestamp.Ticks > 0 && startEntry.Timestamp > stopEntry.Timestamp) { AddToErrorList("Start recording timestamp must not be greater than stop recording timestamp."); } addRecordStopTS = (stopEntry.Timestamp.Ticks == 0); if (audioFilePath != null && !File.Exists(audioFilePath)) { AddToErrorList($"Audio file {audioFilePath} is missing."); } if (videoFilePath != null && !File.Exists(videoFilePath)) { AddToErrorList($"Video file {videoFilePath} is missing."); } if (isValid) { var baseName = Path.GetFileNameWithoutExtension(jsonFile); var audioPath = Path.Combine(_InputDirectory, baseName + ".mka"); var videoPath = Path.Combine(_InputDirectory, baseName + ".mkv"); if (data == null) { addDataBlock = true; } if (File.Exists(audioPath)) { if (firstAudioTimestamp == null) { addFirstAudioTS = true; firstAudioTimestamp = (startEntry.Timestamp.Ticks == 0) ? (DateTime?)null : startEntry.Timestamp; if (firstAudioTimestamp == null) { AddToErrorList($"JSON file has audio file but neither \"timestamp\" nor \"audioFirstFrameTimestamp\" defined in section \"{LogEntry.TypeStopRecording}\". File cannot be recovered"); } } if (audioFilePath == null) { addAudioPath = true; audioFilePath = audioPath; } } if (File.Exists(videoPath)) { if (firstVideoTimestamp == null) { addFirstVideoTS = true; firstVideoTimestamp = (startEntry.Timestamp.Ticks == 0) ? (DateTime?)null : startEntry.Timestamp; if (firstVideoTimestamp == null) { AddToErrorList($"JSON file has audio file but neither \"timestamp\" nor \"videoFirstFrameTimestamp\" defined in section \"{LogEntry.TypeStopRecording}\". File cannot be recovered"); } } if (videoFilePath == null) { addVideoPath = true; videoFilePath = videoPath; } } if (audioFilePath == null && videoFilePath == null) { AddToErrorList($"JSON file does not have any media file referred in \"data\" block of the section \"{LogEntry.TypeStopRecording}\" and it cannot be reconstructed (no auido or video file found)."); } else { if (audioFilePath != null && lastAudioTimestamp == null) { calcLastAudioTS = true; } if (videoFilePath != null && lastVideoTimestamp == null) { calcLastVideoTS = true; } } } } if (isValid && (addDataBlock || addAudioPath || addVideoPath || addRecordStopTS || addFirstAudioTS || addFirstVideoTS || calcLastAudioTS || calcLastVideoTS)) { _Logger.LogDebug($"Calculating last frame durations for file {jsonFile}"); var tempFile = jsonFile + ".tmp.$$$"; var pair = Tuple.Create(jsonFile, tempFile); if (calcLastAudioTS) { var duration = await GetDuration(audioFilePath, true).ConfigureAwait(false); if (duration != null) { lastAudioTimestamp = firstAudioTimestamp + duration; _Logger.LogDebug($"Calculated last audio frame timestamp for file {jsonFile}: {lastAudioTimestamp} (duration: {duration})."); } else { lastAudioTimestamp = firstAudioTimestamp; _Logger.LogDebug($"Calculated last audio frame timestamp for file {jsonFile}: {lastAudioTimestamp} (duration: cannot be calculated)."); } } if (calcLastVideoTS) { var duration = await GetDuration(videoFilePath, false).ConfigureAwait(false); if (duration != null) { lastVideoTimestamp = firstVideoTimestamp + duration; _Logger.LogDebug($"Calculated last video frame timestamp for file {jsonFile}: {lastVideoTimestamp} (duration: {duration})."); } else { lastVideoTimestamp = firstVideoTimestamp; _Logger.LogDebug($"Calculated last audio frame timestamp for file {jsonFile}: {lastVideoTimestamp} (duration: cannot be calculated)."); } } if (addDataBlock) { stopEntry.Data = new LogEntryData(); } if (addFirstAudioTS) { stopEntry.Data.AudioFirstFrameTimestamp = firstAudioTimestamp; } if (addFirstVideoTS) { stopEntry.Data.VideoFirstFrameTimestamp = firstVideoTimestamp; } if (addAudioPath) { stopEntry.Data.AudioFile = audioFilePath; } if (addVideoPath) { stopEntry.Data.VideoFile = videoFilePath; } if (addRecordStopTS) { DateTime?stopRecTimestamp; if (lastAudioTimestamp != null && lastVideoTimestamp != null) { stopRecTimestamp = ((lastAudioTimestamp > lastVideoTimestamp) ? lastAudioTimestamp : lastVideoTimestamp); } else { stopRecTimestamp = lastAudioTimestamp ?? lastVideoTimestamp; } if (stopRecTimestamp != null) { stopEntry.Timestamp = (DateTime)stopRecTimestamp; } } if (calcLastAudioTS && lastAudioTimestamp != null) { stopEntry.Data.AudioLastFrameTimestamp = lastAudioTimestamp; } if (calcLastVideoTS && lastVideoTimestamp != null) { stopEntry.Data.VideoLastFrameTimestamp = lastVideoTimestamp; } File.WriteAllText(tempFile, JsonConvert.SerializeObject(logEntries, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() })); tempFiles.Add(pair); } }