1# frozen_string_literal: true
2
3class PersonalFileUploader < FileUploader
4  # Re-Override
5  def self.root
6    options.storage_path
7  end
8
9  def self.workhorse_local_upload_path
10    File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
11  end
12
13  def self.base_dir(model, _store = nil)
14    # base_dir is the path seen by the user when rendering Markdown, so
15    # it should be the same for both local and object storage. It is
16    # typically prefaced with uploads/-/system, but that prefix
17    # is omitted in the path stored on disk.
18    File.join(options.base_dir, model_path_segment(model))
19  end
20
21  def self.model_path_segment(model)
22    return 'temp/' unless model
23
24    File.join(model.class.underscore, model.id.to_s)
25  end
26
27  def object_store
28    return Store::LOCAL unless model
29
30    super
31  end
32
33  # model_path_segment does not require a model to be passed, so we can always
34  # generate a path, even when there's no model.
35  def model_valid?
36    true
37  end
38
39  # Revert-Override
40  def store_dir
41    store_dirs[object_store]
42  end
43
44  # A personal snippet path is stored using FileUploader#upload_path.
45  #
46  # The format for the path:
47  #
48  # Local storage: :random_hex/:filename.
49  # Object storage: personal_snippet/:id/:random_hex/:filename.
50  #
51  # upload_paths represent the possible paths for a given identifier,
52  # which will vary depending on whether the file is stored in local or
53  # object storage. upload_path should match an element in upload_paths.
54  #
55  # base_dir represents the path seen by the user in Markdown, and it
56  # should always be prefixed with uploads/-/system.
57  #
58  # store_dirs represent the paths that are actually used on disk. For
59  # object storage, this should omit the prefix /uploads/-/system.
60  #
61  # For example, consider the requested path /uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png.
62  #
63  # For local storage:
64  #
65  # File on disk: /opt/gitlab/embedded/service/gitlab-rails/public/uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png.
66  #
67  # base_dir: uploads/-/system/personal_snippet/172
68  # upload_path: ff4ad5c2e40b39ae57cda51577317d20/file.png
69  # upload_paths: ["ff4ad5c2e40b39ae57cda51577317d20/file.png", "personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png"].
70  # store_dirs:
71  # => {1=>"uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20", 2=>"personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20"}
72  #
73  # For object storage:
74  #
75  # upload_path: personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png
76  def upload_paths(identifier)
77    [
78      local_storage_path(identifier),
79      File.join(remote_storage_base_path, identifier)
80    ]
81  end
82
83  def store_dirs
84    {
85      Store::LOCAL => File.join(base_dir, dynamic_segment),
86      Store::REMOTE => remote_storage_base_path
87    }
88  end
89
90  private
91
92  # To avoid prefacing the remote storage path with `/uploads/-/system`,
93  # we just drop that part so that the destination path will be
94  # personal_snippet/:id/:random_hex/:filename.
95  def remote_storage_base_path
96    File.join(self.class.model_path_segment(model), dynamic_segment)
97  end
98
99  def secure_url
100    File.join('/', base_dir, secret, filename)
101  end
102end
103