blob.rb 4.5 KB
Newer Older
1 2
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
class Blob < SimpleDelegator
J
Jacob Vosmaer 已提交
3 4 5
  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

6 7
  MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte

8 9 10 11 12 13 14 15 16 17 18 19 20 21
  # 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.
22
  RICH_VIEWERS = [
23 24 25 26
    BlobViewer::Markup,
    BlobViewer::Notebook,
    BlobViewer::SVG,

D
Douwe Maan 已提交
27 28
    BlobViewer::Image,
    BlobViewer::Sketch,
29

30 31
    BlobViewer::Video,
    
32 33
    BlobViewer::PDF,

D
Douwe Maan 已提交
34 35
    BlobViewer::BinarySTL,
    BlobViewer::TextSTL,
36
  ].freeze
37

38 39 40
  BINARY_VIEWERS = RICH_VIEWERS.select(&:binary?).freeze
  TEXT_VIEWERS = RICH_VIEWERS.select(&:text?).freeze

D
Douwe Maan 已提交
41 42
  attr_reader :project

43 44 45 46 47 48 49 50 51 52
  # Wrap a Gitlab::Git::Blob object, or return nil when given nil
  #
  # This method prevents the decorated object from evaluating to "truthy" when
  # given a nil value. For example:
  #
  #     blob = Blob.new(nil)
  #     puts "truthy" if blob # => "truthy"
  #
  #     blob = Blob.decorate(nil)
  #     puts "truthy" if blob # No output
D
Douwe Maan 已提交
53
  def self.decorate(blob, project = nil)
54 55
    return if blob.nil?

D
Douwe Maan 已提交
56 57 58
    new(blob, project)
  end

D
Douwe Maan 已提交
59
  def initialize(blob, project = nil)
D
Douwe Maan 已提交
60 61 62
    @project = project

    super(blob)
63 64
  end

65 66 67 68 69 70 71 72 73 74 75 76
  # Returns the data of the blob.
  #
  # If the blob is a text based blob the content is converted to UTF-8 and any
  # invalid byte sequences are replaced.
  def data
    if binary?
      super
    else
      @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
    end
  end

77
  def no_highlighting?
78
    size && size > MAXIMUM_TEXT_HIGHLIGHT_SIZE
79 80
  end

81
  def too_large?
82
    size && truncated?
83 84
  end

D
Douwe Maan 已提交
85 86 87
  # 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.
88 89 90 91 92 93
  def raw_size
    if valid_lfs_pointer?
      lfs_size
    else
      size
    end
94 95
  end

D
Douwe Maan 已提交
96 97 98 99
  # 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.
100 101
  def raw_binary?
    if valid_lfs_pointer?
D
Douwe Maan 已提交
102 103 104 105 106
      if rich_viewer
        rich_viewer.binary?
      else
        true
      end
107 108 109
    else
      binary?
    end
110 111
  end

112 113
  def extension
    @extension ||= extname.downcase.delete('.')
S
Sam Rose 已提交
114 115
  end

116 117
  def video?
    UploaderHelper::VIDEO_EXT.include?(extension)
P
Phil Hughes 已提交
118 119
  end

120 121
  def readable_text?
    text? && !valid_lfs_pointer? && !too_large?
J
Jacob Schatz 已提交
122 123
  end

124
  def valid_lfs_pointer?
D
Douwe Maan 已提交
125
    lfs_pointer? && project&.lfs_enabled?
126 127
  end

128
  def invalid_lfs_pointer?
D
Douwe Maan 已提交
129
    lfs_pointer? && !project&.lfs_enabled?
P
Phil Hughes 已提交
130 131
  end

132 133 134 135 136 137 138
  def simple_viewer
    @simple_viewer ||= simple_viewer_class.new(self)
  end

  def rich_viewer
    return @rich_viewer if defined?(@rich_viewer)

D
Douwe Maan 已提交
139
    @rich_viewer = rich_viewer_class&.new(self)
140 141
  end

D
Douwe Maan 已提交
142
  def rendered_as_text?(ignore_errors: true)
D
Douwe Maan 已提交
143
    simple_viewer.text? && (ignore_errors || simple_viewer.render_error.nil?)
144 145 146
  end

  def show_viewer_switcher?
D
Douwe Maan 已提交
147 148 149 150 151 152
    rendered_as_text? && rich_viewer
  end

  def override_max_size!
    simple_viewer&.override_max_size = true
    rich_viewer&.override_max_size = true
153 154 155 156
  end

  private

D
Douwe Maan 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169
  def simple_viewer_class
    if empty?
      BlobViewer::Empty
    elsif raw_binary?
      BlobViewer::Download
    else # text
      BlobViewer::Text
    end
  end

  def rich_viewer_class
    return if invalid_lfs_pointer? || empty?

D
Douwe Maan 已提交
170 171
    classes =
      if valid_lfs_pointer?
172
        BINARY_VIEWERS + TEXT_VIEWERS
D
Douwe Maan 已提交
173
      elsif binary?
174
        BINARY_VIEWERS
D
Douwe Maan 已提交
175
      else # text
176
        TEXT_VIEWERS
D
Douwe Maan 已提交
177 178 179
      end

    classes.find { |viewer_class| viewer_class.can_render?(self) }
D
Douwe Maan 已提交
180
  end
181
end