zammad/zammad

View on GitHub
app/models/store/file.rb

Summary

Maintainability
A
55 mins
Test Coverage
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class Store < ApplicationModel
  class File < ApplicationModel
    include ApplicationLib
    after_destroy :destroy_provider

=begin

add new file to store

  store_file_id = Store::File.add(binary_data)

do also verify of written data

  store_file_id = Store::File.add(binary_data, true)

=end

    def self.add(data, verify = true)
      sha = checksum(data)

      file = Store::File.find_by(sha: sha)
      if file.nil?

        # load backend based on config
        adapter_name = Setting.get('storage_provider') || 'DB'
        if !adapter_name
          raise __("The setting 'storage_provider' was not configured.")
        end

        adapter = "Store::Provider::#{adapter_name}".constantize
        adapter.add(data, sha)
        file = Store::File.create(
          provider: adapter_name,
          sha:      sha,
        )

        # verify
        if verify
          read_data = adapter.get(sha)
          read_sha = checksum(read_data)
          if sha != read_sha
            raise "Content not written correctly (provider #{adapter_name})."
          end
        end
      end
      file
    end

=begin

read content of a file

  store = Store::File.find(123)

  store.content # returns binary

=end

    def content
      "Store::Provider::#{provider}".constantize.get(sha)
    end

=begin

file system check of store, check data and sha (in case fix it)

  Store::File.verify

read each file which should be in backend and verify against sha hash

in case of fixing sha hash use:

  Store::File.verify(true)

=end

    def self.verify(fix_it = nil)
      success = true
      Store::File.find_each(batch_size: 10) do |item|
        sha = checksum(item.content)
        logger.info "CHECK: Store::File.find(#{item.id})"
        next if sha == item.sha

        success = false
        logger.error "DIFF: sha diff of Store::File.find(#{item.id}) current:#{sha}/db:#{item.sha}/provider:#{item.provider}"
        store = Store.find_by(store_file_id: item.id)
        logger.error "STORE: #{store.inspect}"
        item.update_attribute(:sha, sha) if fix_it # rubocop:disable Rails/SkipsModelValidations
      end
      success
    end

=begin

move file from one to other provider

move files from file backend to db

  Store::File.move('File', 'DB')

move files from db backend to fs

  Store::File.move('DB', 'File')

nice move to keep system responsive

  Store::File.move('DB', 'File', delay_in_sec) # e. g. 1

=end

    def self.move(source, target, delay = nil)
      adapter_source = "Store::Provider::#{source}".constantize
      adapter_target = "Store::Provider::#{target}".constantize

      Store::File.where(provider: source).find_each(batch_size: 10) do |item|
        adapter_target.add(item.content, item.sha)
        item.update_attribute(:provider, target) # rubocop:disable Rails/SkipsModelValidations
        adapter_source.delete(item.sha)

        logger.info "Moved file #{item.sha} from #{source} to #{target}"

        sleep delay if delay
      end

      true
    end

=begin

generate a checksum for the given content

  Store::File.checksum(binary_data)

=end

    def self.checksum(content)
      Digest::SHA256.hexdigest(content)
    end

    private

    def destroy_provider
      "Store::Provider::#{provider}".constantize.delete(sha)
    end
  end
end