<commit_before>﻿using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Input;
using NuGetPackageExplorer.Types;

namespace PackageExplorerViewModel {
    internal class ViewContentCommand : CommandBase, ICommand {

        public ViewContentCommand(PackageViewModel packageViewModel)
            : base(packageViewModel) {
        }

        public event EventHandler CanExecuteChanged = delegate { };

        public bool CanExecute(object parameter) {
            return ViewModel.SelectedItem is PackageFile;
        }

        public void RaiseCanExecuteChanged() {
            CanExecuteChanged(this, EventArgs.Empty);
        }

        public void Execute(object parameter) {
            if ("Hide".Equals(parameter)) {
                ViewModel.ShowContentViewer = false;
            }
            else {
                var file = (parameter ?? ViewModel.SelectedItem) as PackageFile;
                if (file != null) {
                    ShowFile(file);
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design", 
            "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification="We don't want plugin to crash the app.")]
        private void ShowFile(PackageFile file) {
            long size = 0;
            object content = null;
            bool isBinary = false;

            // find a plugin which can handles this file's extension
            var contentViewer = FindContentViewer(file);
            if (contentViewer != null) {
                isBinary = true;
                try {
                    using (Stream stream = file.GetStream()) {
                        size = stream.Length;
                        content = contentViewer.GetView(Path.GetExtension(file.Name), stream);
                    }

                    if (content == null) {
                        content = Resources.PluginFailToReadContent;
                    }
                }
                catch (Exception) {
                    // don't let plugin crash the app
                    content = Resources.PluginFailToReadContent;
                }

                if (content is string) {
                    isBinary = false;
                }
            }
            else {
                isBinary = IsBinaryFile(file.Name);
                if (isBinary) {
                    using (Stream stream = file.GetStream()) {
                        size = stream.Length;
                    }
                    content = Resources.UnsupportedFormatMessage;
                }
                else {
                    content = ReadFileContent(file, out size);
                }
            }

            var fileInfo = new FileContentInfo(
                file,
                file.Path,
                content,
                !isBinary,
                size,
                DetermineLanguage(file.Name));

            ViewModel.ShowFile(fileInfo);
        }

        private IPackageContentViewer FindContentViewer(PackageFile file) {
            string extension = Path.GetExtension(file.Name);
            return (from p in ViewModel.ContentViewerMetadata
                    where p.Metadata.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
                    orderby p.Metadata.Priority
                    select p.Value).FirstOrDefault();
        }

        private static string ReadFileContent(PackageFile file, out long size) {
            const int MaxLengthToOpen = 10 * 1024;      // limit to 10K 
            const int BufferSize = 2 * 1024;
            char[] buffer = new char[BufferSize];       // read 2K at a time

            StringBuilder sb = new StringBuilder();
            Stream stream = file.GetStream();
            size = stream.Length;
            using (StreamReader reader = new StreamReader(stream)) {
                while (sb.Length < MaxLengthToOpen) {
                    int bytesRead = reader.Read(buffer, 0, BufferSize);
                    if (bytesRead == 0) {
                        break;
                    }
                    else {
                        sb.Append(new string(buffer, 0, bytesRead));
                    }
                }

                // if not reaching the end of the stream yet, append the text "Truncating..."
                if (reader.Peek() > -1) {
                    // continue reading the rest of the current line to avoid dangling line
                    sb.AppendLine(reader.ReadLine());

                    if (reader.Peek() > -1) {
                        sb.AppendLine().AppendLine("*** The rest of the content is truncated. ***");
                    }
                }
            }<commit_msg>[Package Explorer] When a plugin fails to read content, try the next plugin instead of showing error.<commit_after>﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Input;
using NuGetPackageExplorer.Types;

namespace PackageExplorerViewModel {
    internal class ViewContentCommand : CommandBase, ICommand {

        public ViewContentCommand(PackageViewModel packageViewModel)
            : base(packageViewModel) {
        }

        public event EventHandler CanExecuteChanged = delegate { };

        public bool CanExecute(object parameter) {
            return ViewModel.SelectedItem is PackageFile;
        }

        public void RaiseCanExecuteChanged() {
            CanExecuteChanged(this, EventArgs.Empty);
        }

        public void Execute(object parameter) {
            if ("Hide".Equals(parameter)) {
                ViewModel.ShowContentViewer = false;
            }
            else {
                var file = (parameter ?? ViewModel.SelectedItem) as PackageFile;
                if (file != null) {
                    ShowFile(file);
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design",
            "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "We don't want plugin to crash the app.")]
        private void ShowFile(PackageFile file) {
            long size = -1;
            object content = null;
            bool isBinary = false;

            // find all plugins which can handle this file's extension
            var contentViewers = FindContentViewer(file);
            if (contentViewers != null) {
                isBinary = true;
                try {
                    // iterate over all plugins, looking for the first one that return non-null content
                    foreach (var viewer in contentViewers) {
                        using (Stream stream = file.GetStream()) {
                            if (size == -1) {
                                size = stream.Length;
                            }
                            content = viewer.GetView(Path.GetExtension(file.Name), stream);
                            if (content != null) {
                                // found a plugin that can read this file, stop
                                break;
                            }
                        }
                    }
                }
                catch (Exception) {
                    // don't let plugin crash the app
                    content = Resources.PluginFailToReadContent;
                }

                if (content is string) {
                    isBinary = false;
                }
            }

            // if plugins fail to read this file, fall back to the default viewer
            if (content == null) {
                isBinary = IsBinaryFile(file.Name);
                if (isBinary) {
                    // don't calculate the size again if we already have it
                    if (size == -1) {
                        using (Stream stream = file.GetStream()) {
                            size = stream.Length;
                        }
                    }
                    content = Resources.UnsupportedFormatMessage;
                }
                else {
                    content = ReadFileContent(file, out size);
                }
            }

            var fileInfo = new FileContentInfo(
                file,
                file.Path,
                content,
                !isBinary,
                size,
                DetermineLanguage(file.Name));

            ViewModel.ShowFile(fileInfo);
        }

        private IEnumerable<IPackageContentViewer> FindContentViewer(PackageFile file) {
            string extension = Path.GetExtension(file.Name);
            return from p in ViewModel.ContentViewerMetadata
                   where p.Metadata.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
                   orderby p.Metadata.Priority
                   select p.Value;
        }

        private static string ReadFileContent(PackageFile file, out long size) {
            const int MaxLengthToOpen = 10 * 1024;      // limit to 10K 
            const int BufferSize = 2 * 1024;
            char[] buffer = new char[BufferSize];       // read 2K at a time

            StringBuilder sb = new StringBuilder();
            Stream stream = file.GetStream();
            size = stream.Length;
            using (StreamReader reader = new StreamReader(stream)) {
                while (sb.Length < MaxLengthToOpen) {
                    int bytesRead = reader.Read(buffer, 0, BufferSize);
                    if (bytesRead == 0) {
                        break;
                    }
                    else {
                        sb.Append(new string(buffer, 0, bytesRead));
                    }
                }

                // if not reaching the end of the stream yet, append the text "Truncating..."
                if (reader.Peek() > -1) {
                    // continue reading the rest of the current line to avoid dangling line
                    sb.AppendLine(reader.ReadLine());

                    if (reader.Peek() > -1) {
                        sb.AppendLine().AppendLine("*** The rest of the content is truncated. ***");
                    }
                }
            }
{'fraction_non_alphanumeric': 0.07612187287063175, 'fraction_numerical': 0.004867127421395892, 'mean_word_length': 1.2836185819070904, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6037824', 'n_tokens_mistral': 2437, 'n_tokens_neox': 2216, 'n_words': 642}