diff options
Diffstat (limited to 'app/models/blob.rb')
-rw-r--r-- | app/models/blob.rb | 198 |
1 files changed, 171 insertions, 27 deletions
diff --git a/app/models/blob.rb b/app/models/blob.rb index 95d2111a992..e75926241ba 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -3,8 +3,62 @@ class Blob < SimpleDelegator CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour - # The maximum size of an SVG that can be displayed. - MAXIMUM_SVG_SIZE = 2.megabytes + MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte + + # Finding a viewer for a blob happens based only on extension and whether the + # blob is binary or text, which means 1 blob should only be matched by 1 viewer, + # and the order of these viewers doesn't really matter. + # + # However, when the blob is an LFS pointer, we cannot know for sure whether the + # file being pointed to is binary or text. In this case, we match only on + # extension, preferring binary viewers over text ones if both exist, since the + # large files referred to in "Large File Storage" are much more likely to be + # binary than text. + # + # `.stl` files, for example, exist in both binary and text forms, and are + # handled by different viewers (`BinarySTL` and `TextSTL`) depending on blob + # type. LFS pointers to `.stl` files are assumed to always be the binary kind, + # and use the `BinarySTL` viewer. + RICH_VIEWERS = [ + BlobViewer::Markup, + BlobViewer::Notebook, + BlobViewer::SVG, + + BlobViewer::Image, + BlobViewer::Sketch, + BlobViewer::Balsamiq, + + BlobViewer::Video, + + BlobViewer::PDF, + + BlobViewer::BinarySTL, + BlobViewer::TextSTL + ].sort_by { |v| v.binary? ? 0 : 1 }.freeze + + AUXILIARY_VIEWERS = [ + BlobViewer::GitlabCiYml, + BlobViewer::RouteMap, + + BlobViewer::Readme, + BlobViewer::License, + BlobViewer::Contributing, + BlobViewer::Changelog, + + BlobViewer::Cartfile, + BlobViewer::ComposerJson, + BlobViewer::Gemfile, + BlobViewer::Gemspec, + BlobViewer::GodepsJson, + BlobViewer::PackageJson, + BlobViewer::Podfile, + BlobViewer::Podspec, + BlobViewer::PodspecJson, + BlobViewer::RequirementsTxt, + BlobViewer::YarnLock + ].freeze + + attr_reader :project # Wrap a Gitlab::Git::Blob object, or return nil when given nil # @@ -16,10 +70,16 @@ class Blob < SimpleDelegator # # blob = Blob.decorate(nil) # puts "truthy" if blob # No output - def self.decorate(blob) + def self.decorate(blob, project = nil) return if blob.nil? - new(blob) + new(blob, project) + end + + def initialize(blob, project = nil) + @project = project + + super(blob) end # Returns the data of the blob. @@ -35,44 +95,128 @@ class Blob < SimpleDelegator end def no_highlighting? - size && size > 1.megabyte + raw_size && raw_size > MAXIMUM_TEXT_HIGHLIGHT_SIZE end - def only_display_raw? - size && truncated? + def empty? + raw_size == 0 end - def svg? - text? && language && language.name == 'SVG' + def too_large? + size && truncated? end - def ipython_notebook? - text? && language&.name == 'Jupyter Notebook' + def external_storage_error? + if external_storage == :lfs + !project&.lfs_enabled? + else + false + end end - def size_within_svg_limits? - size <= MAXIMUM_SVG_SIZE + def stored_externally? + return @stored_externally if defined?(@stored_externally) + + @stored_externally = external_storage && !external_storage_error? end - def video? - UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.')) + # Returns the size of the file that this blob represents. If this blob is an + # LFS pointer, this is the size of the file stored in LFS. Otherwise, this is + # the size of the blob itself. + def raw_size + if stored_externally? + external_size + else + size + end end - def to_partial_path(project) - if lfs_pointer? - if project.lfs_enabled? - 'download' + # Returns whether the file that this blob represents is binary. If this blob is + # an LFS pointer, we assume the file stored in LFS is binary, unless a + # text-based rich blob viewer matched on the file's extension. Otherwise, this + # depends on the type of the blob itself. + def raw_binary? + if stored_externally? + if rich_viewer + rich_viewer.binary? + elsif Linguist::Language.find_by_filename(name).any? + false + elsif _mime_type + _mime_type.binary? else - 'text' + true end - elsif image? || svg? - 'image' - elsif ipython_notebook? - 'notebook' - elsif text? - 'text' else - 'download' + binary? end end + + def extension + @extension ||= extname.downcase.delete('.') + end + + def video? + UploaderHelper::VIDEO_EXT.include?(extension) + end + + def readable_text? + text? && !stored_externally? && !too_large? + end + + def simple_viewer + @simple_viewer ||= simple_viewer_class.new(self) + end + + def rich_viewer + return @rich_viewer if defined?(@rich_viewer) + + @rich_viewer = rich_viewer_class&.new(self) + end + + def auxiliary_viewer + return @auxiliary_viewer if defined?(@auxiliary_viewer) + + @auxiliary_viewer = auxiliary_viewer_class&.new(self) + end + + def rendered_as_text?(ignore_errors: true) + simple_viewer.text? && (ignore_errors || simple_viewer.render_error.nil?) + end + + def show_viewer_switcher? + rendered_as_text? && rich_viewer + end + + def override_max_size! + simple_viewer&.override_max_size = true + rich_viewer&.override_max_size = true + end + + private + + def simple_viewer_class + if empty? + BlobViewer::Empty + elsif raw_binary? + BlobViewer::Download + else # text + BlobViewer::Text + end + end + + def rich_viewer_class + viewer_class_from(RICH_VIEWERS) + end + + def auxiliary_viewer_class + viewer_class_from(AUXILIARY_VIEWERS) + end + + def viewer_class_from(classes) + return if empty? || external_storage_error? + + verify_binary = !stored_externally? + + classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) } + end end |