summaryrefslogtreecommitdiff
path: root/app/models/blob.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/blob.rb')
-rw-r--r--app/models/blob.rb198
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