diff --git a/app/models/blob.rb b/app/models/blob.rb index c6315a6789f..2b2425a926e 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -3,8 +3,10 @@ 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 + + RICH_VIEWERS = [ + ].freeze attr_reader :project @@ -43,82 +45,94 @@ class Blob < SimpleDelegator end def no_highlighting? - size && size > 1.megabyte + size && size > MAXIMUM_TEXT_HIGHLIGHT_SIZE end - def only_display_raw? + def too_large? size && truncated? end + def raw_size + if valid_lfs_pointer? + lfs_size + else + size + end + end + + def raw_binary? + if valid_lfs_pointer? + !rich_viewer&.text_based? + else + binary? + end + end + def extension - extname.downcase.delete('.') - end - - def svg? - text? && language && language.name == 'SVG' - end - - def pdf? - extension == 'pdf' - end - - def ipython_notebook? - text? && language&.name == 'Jupyter Notebook' - end - - def sketch? - binary? && extension == 'sketch' - end - - def stl? - extension == 'stl' - end - - def markup? - text? && Gitlab::MarkupHelper.markup?(name) - end - - def size_within_svg_limits? - size <= MAXIMUM_SVG_SIZE + @extension ||= extname.downcase.delete('.') end def video? - UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.')) + UploaderHelper::VIDEO_EXT.include?(extension) end - def to_partial_path(project) - if lfs_pointer? - if project.lfs_enabled? - 'download' - else - 'text' - end - elsif image? - 'image' - elsif svg? - 'svg' - elsif pdf? - 'pdf' - elsif ipython_notebook? - 'notebook' - elsif sketch? - 'sketch' - elsif stl? - 'stl' - elsif markup? - if only_display_raw? - 'too_large' - else - 'markup' - end - elsif text? - if only_display_raw? - 'too_large' - else - 'text' - end + def readable_text? + text? && !valid_lfs_pointer? && !too_large? + end + + def valid_lfs_pointer? + lfs_pointer? && project.lfs_enabled? + end + + def invalid_lfs_pointer? + lfs_pointer? && !project.lfs_enabled? + end + + def simple_viewer_class + if empty? + BlobViewer::Empty + elsif raw_binary? + BlobViewer::Download + else # text + BlobViewer::Text + end + end + + def rich_viewer_class + if invalid_lfs_pointer? || empty? + nil else - 'download' + rich_viewers_classes.find { |viewer_class| viewer_class.can_render?(self) } + end + 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 rendered_as_text?(override_max_size: false) + simple_viewer.is_a?(BlobViewer::Text) && !simple_viewer.render_error(override_max_size: override_max_size) + end + + def show_viewer_switcher? + simple_viewer.is_a?(BlobViewer::Text) && rich_viewer + end + + private + + def rich_viewers_classes + if valid_lfs_pointer? + RICH_VIEWERS + elsif binary? + RICH_VIEWERS.reject(&:text_based?) + else # text + RICH_VIEWERS.select(&:text_based?) end end end diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb new file mode 100644 index 00000000000..37acacc0019 --- /dev/null +++ b/app/models/blob_viewer/base.rb @@ -0,0 +1,81 @@ +module BlobViewer + class Base + class_attribute :partial_name, :type, :extensions, :client_side, :text_based, :switcher_icon, :switcher_title, :max_size, :absolute_max_size + + delegate :partial_path, :rich?, :simple?, :client_side?, :text_based?, to: :class + + attr_reader :blob + + def initialize(blob) + @blob = blob + end + + def self.partial_path + "projects/blob/viewers/#{partial_name}" + end + + def self.rich? + type == :rich + end + + def self.simple? + type == :simple + end + + def self.client_side? + client_side + end + + def server_side? + !client_side? + end + + def self.text_based? + text_based + end + + def self.can_render?(blob) + !extensions || extensions.include?(blob.extension) + end + + def can_override_max_size? + too_large? && !too_large?(override_max_size: true) + end + + def relevant_max_size + if too_large?(override_max_size: true) + absolute_max_size + elsif too_large? + max_size + end + end + + def render_error(override_max_size: false) + if too_large?(override_max_size: override_max_size) + :too_large + elsif server_side_but_stored_in_lfs? + :server_side_but_stored_in_lfs + end + end + + def prepare! + if server_side? && blob.project + blob.load_all_data!(blob.project.repository) + end + end + + private + + def too_large?(override_max_size: false) + if override_max_size + blob.raw_size > absolute_max_size + else + blob.raw_size > max_size + end + end + + def server_side_but_stored_in_lfs? + !client_side? && blob.valid_lfs_pointer? + end + end +end