activestorage_qinium 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a957256178e151a2f7cb7980ca1b5d14c2eb581f3aca737d08ef2b22fe3b9821
4
+ data.tar.gz: 67ed36ff67c18a64f3c7c990993cf289e84726a90b0eed2a9720a2efe4037fd7
5
+ SHA512:
6
+ metadata.gz: 992c598c417a4cb89131e57345f476eed8d461a2a02aa89fbc47cca29951297193ae1e035ef6b1f036209a764b1995076e826e98de2877ee3cf2a09fa3238f95
7
+ data.tar.gz: a208527fdd58e43fdeb7bf0304ac778a67aa8eec900ce5eca476b96e0f93c53028048b1a7964e2ac83f21eb3a7eb496b054036e2787f63740d7eac20a739217f
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-08-10
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in activestorage_qinium.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.21"
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ activestorage_qinium (0.1.0)
5
+ qinium (~> 0.1.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ diff-lcs (1.5.0)
12
+ parallel (1.22.1)
13
+ parser (3.1.1.0)
14
+ ast (~> 2.4.1)
15
+ qinium (0.1.1)
16
+ rainbow (3.1.1)
17
+ rake (13.0.6)
18
+ regexp_parser (2.5.0)
19
+ rexml (3.2.5)
20
+ rspec (3.11.0)
21
+ rspec-core (~> 3.11.0)
22
+ rspec-expectations (~> 3.11.0)
23
+ rspec-mocks (~> 3.11.0)
24
+ rspec-core (3.11.0)
25
+ rspec-support (~> 3.11.0)
26
+ rspec-expectations (3.11.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.11.0)
29
+ rspec-mocks (3.11.1)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.11.0)
32
+ rspec-support (3.11.0)
33
+ rubocop (1.27.0)
34
+ parallel (~> 1.10)
35
+ parser (>= 3.1.0.0)
36
+ rainbow (>= 2.2.2, < 4.0)
37
+ regexp_parser (>= 1.8, < 3.0)
38
+ rexml
39
+ rubocop-ast (>= 1.16.0, < 2.0)
40
+ ruby-progressbar (~> 1.7)
41
+ unicode-display_width (>= 1.4.0, < 3.0)
42
+ rubocop-ast (1.17.0)
43
+ parser (>= 3.1.1.0)
44
+ ruby-progressbar (1.11.0)
45
+ unicode-display_width (2.1.0)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ activestorage_qinium!
52
+ rake (~> 13.0)
53
+ rspec (~> 3.0)
54
+ rubocop (~> 1.21)
55
+
56
+ BUNDLED WITH
57
+ 2.3.9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 张小辉
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # ActivestorageQinium
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/activestorage_qinium`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add activestorage_qinium
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install activestorage_qinium
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activestorage_qinium.
30
+
31
+ ## License
32
+
33
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/active_storage_qinium/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "activestorage_qinium"
7
+ spec.version = ActiveStorageQinium::VERSION
8
+ spec.authors = ["xiaohui"]
9
+ spec.email = ["xiaohui@tanmer.com"]
10
+
11
+ spec.summary = "A muti-tenant SDK wrap the Qiniu Storage Service as an Active Storage service"
12
+ spec.description = "Wraps the Qiniu Storage Service as an Active Storage service, support muti-tenant settings. https://www.qiniu.com"
13
+ spec.homepage = "https://github.com/xiaohui-zhangxh/activestorage_qinium"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] =spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "qinium", "~> 0.1.1"
32
+ end
@@ -0,0 +1,73 @@
1
+ import { getMetaValue } from "./helpers"
2
+
3
+ export class BlobRecord {
4
+ constructor(file, checksum, url) {
5
+ this.file = file
6
+
7
+ this.attributes = {
8
+ filename: file.name,
9
+ content_type: file.type || "application/octet-stream",
10
+ byte_size: file.size,
11
+ checksum: checksum
12
+ }
13
+
14
+ this.xhr = new XMLHttpRequest
15
+ this.xhr.open("POST", url, true)
16
+ this.xhr.responseType = "json"
17
+ this.xhr.setRequestHeader("Content-Type", "application/json")
18
+ this.xhr.setRequestHeader("Accept", "application/json")
19
+ this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
20
+
21
+ const csrfToken = getMetaValue("csrf-token")
22
+ if (csrfToken != undefined) {
23
+ this.xhr.setRequestHeader("X-CSRF-Token", csrfToken)
24
+ }
25
+
26
+ this.xhr.addEventListener("load", event => this.requestDidLoad(event))
27
+ this.xhr.addEventListener("error", event => this.requestDidError(event))
28
+ }
29
+
30
+ get status() {
31
+ return this.xhr.status
32
+ }
33
+
34
+ get response() {
35
+ const { responseType, response } = this.xhr
36
+ if (responseType == "json") {
37
+ return response
38
+ } else {
39
+ // Shim for IE 11: https://connect.microsoft.com/IE/feedback/details/794808
40
+ return JSON.parse(response)
41
+ }
42
+ }
43
+
44
+ create(callback) {
45
+ this.callback = callback
46
+ this.xhr.send(JSON.stringify({ blob: this.attributes }))
47
+ }
48
+
49
+ requestDidLoad(event) {
50
+ if (this.status >= 200 && this.status < 300) {
51
+ const { response } = this
52
+ const { direct_upload } = response
53
+ delete response.direct_upload
54
+ this.attributes = response
55
+ this.directUploadData = direct_upload
56
+ this.callback(null, this.toJSON())
57
+ } else {
58
+ this.requestDidError(event)
59
+ }
60
+ }
61
+
62
+ requestDidError(event) {
63
+ this.callback(event, this)
64
+ }
65
+
66
+ toJSON() {
67
+ const result = {}
68
+ for (const key in this.attributes) {
69
+ result[key] = this.attributes[key]
70
+ }
71
+ return result
72
+ }
73
+ }
@@ -0,0 +1,45 @@
1
+ export class BlobUpload {
2
+ constructor(blob) {
3
+ this.blob = blob
4
+ this.file = blob.file
5
+ this.dataBuilder = blob.dataBuilder || ((ctx) => ctx.file.slice())
6
+ const { url, headers } = blob.directUploadData
7
+ this.xhr = new XMLHttpRequest
8
+ this.xhr.open("POST", url, true)
9
+ // this.xhr.responseType = "text"
10
+ for (const key in headers) {
11
+ this.xhr.setRequestHeader(key, headers[key])
12
+ }
13
+ this.xhr.addEventListener("load", event => this.requestDidLoad(event))
14
+ this.xhr.addEventListener("error", event => this.requestDidError(event))
15
+ }
16
+
17
+ create(callback) {
18
+ // debugger
19
+ this.callback = callback
20
+ if(this.blob.directUploadData.formData){
21
+ var formData
22
+ formData = new FormData()
23
+ for(const key in this.blob.directUploadData.formData){
24
+ formData.append(key, this.blob.directUploadData.formData[key])
25
+ }
26
+ formData.append('file', this.file)
27
+ this.xhr.send(formData)
28
+ }else{
29
+ this.xhr.send(this.dataBuilder(this.blob))
30
+ }
31
+ }
32
+
33
+ requestDidLoad(event) {
34
+ const { status, response } = this.xhr
35
+ if (status >= 200 && status < 300) {
36
+ this.callback(null, response)
37
+ } else {
38
+ this.requestDidError(event)
39
+ }
40
+ }
41
+
42
+ requestDidError(event) {
43
+ this.callback(event, this)
44
+ }
45
+ }
@@ -0,0 +1,48 @@
1
+ import { FileChecksum } from "./file_checksum"
2
+ import { BlobRecord } from "./blob_record"
3
+ import { BlobUpload } from "./blob_upload"
4
+
5
+ let id = 0
6
+
7
+ export class DirectUpload {
8
+ constructor(file, url, delegate) {
9
+ this.id = ++id
10
+ this.file = file
11
+ this.url = url
12
+ this.delegate = delegate
13
+ }
14
+
15
+ create(callback) {
16
+ FileChecksum.create(this.file, (error, checksum) => {
17
+ if (error) {
18
+ callback(error)
19
+ return
20
+ }
21
+
22
+ const blob = new BlobRecord(this.file, checksum, this.url)
23
+ notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
24
+
25
+ blob.create(error => {
26
+ if (error) {
27
+ callback(error)
28
+ } else {
29
+ const upload = new BlobUpload(blob)
30
+ notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr)
31
+ upload.create(error => {
32
+ if (error) {
33
+ callback(error)
34
+ } else {
35
+ callback(null, blob.toJSON())
36
+ }
37
+ })
38
+ }
39
+ })
40
+ })
41
+ }
42
+ }
43
+
44
+ function notify(object, methodName, ...messages) {
45
+ if (object && typeof object[methodName] == "function") {
46
+ return object[methodName](...messages)
47
+ }
48
+ }
@@ -0,0 +1,53 @@
1
+ import SparkMD5 from "spark-md5"
2
+
3
+ const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
4
+
5
+ export class FileChecksum {
6
+ static create(file, callback) {
7
+ const instance = new FileChecksum(file)
8
+ instance.create(callback)
9
+ }
10
+
11
+ constructor(file) {
12
+ this.file = file
13
+ this.chunkSize = 2097152 // 2MB
14
+ this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
15
+ this.chunkIndex = 0
16
+ }
17
+
18
+ create(callback) {
19
+ this.callback = callback
20
+ this.md5Buffer = new SparkMD5.ArrayBuffer
21
+ this.fileReader = new FileReader
22
+ this.fileReader.addEventListener("load", event => this.fileReaderDidLoad(event))
23
+ this.fileReader.addEventListener("error", event => this.fileReaderDidError(event))
24
+ this.readNextChunk()
25
+ }
26
+
27
+ fileReaderDidLoad(event) {
28
+ this.md5Buffer.append(event.target.result)
29
+
30
+ if (!this.readNextChunk()) {
31
+ const binaryDigest = this.md5Buffer.end(true)
32
+ const base64digest = btoa(binaryDigest)
33
+ this.callback(null, base64digest)
34
+ }
35
+ }
36
+
37
+ fileReaderDidError(event) {
38
+ this.callback(`Error reading ${this.file.name}`)
39
+ }
40
+
41
+ readNextChunk() {
42
+ if (this.chunkIndex < this.chunkCount || (this.chunkIndex == 0 && this.chunkCount == 0)) {
43
+ const start = this.chunkIndex * this.chunkSize
44
+ const end = Math.min(start + this.chunkSize, this.file.size)
45
+ const bytes = fileSlice.call(this.file, start, end)
46
+ this.fileReader.readAsArrayBuffer(bytes)
47
+ this.chunkIndex++
48
+ return true
49
+ } else {
50
+ return false
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,51 @@
1
+ export function getMetaValue(name) {
2
+ const element = findElement(document.head, `meta[name="${name}"]`)
3
+ if (element) {
4
+ return element.getAttribute("content")
5
+ }
6
+ }
7
+
8
+ export function findElements(root, selector) {
9
+ if (typeof root == "string") {
10
+ selector = root
11
+ root = document
12
+ }
13
+ const elements = root.querySelectorAll(selector)
14
+ return toArray(elements)
15
+ }
16
+
17
+ export function findElement(root, selector) {
18
+ if (typeof root == "string") {
19
+ selector = root
20
+ root = document
21
+ }
22
+ return root.querySelector(selector)
23
+ }
24
+
25
+ export function dispatchEvent(element, type, eventInit = {}) {
26
+ const { disabled } = element
27
+ const { bubbles, cancelable, detail } = eventInit
28
+ const event = document.createEvent("Event")
29
+
30
+ event.initEvent(type, bubbles || true, cancelable || true)
31
+ event.detail = detail || {}
32
+
33
+ try {
34
+ element.disabled = false
35
+ element.dispatchEvent(event)
36
+ } finally {
37
+ element.disabled = disabled
38
+ }
39
+
40
+ return event
41
+ }
42
+
43
+ export function toArray(value) {
44
+ if (Array.isArray(value)) {
45
+ return value
46
+ } else if (Array.from) {
47
+ return Array.from(value)
48
+ } else {
49
+ return [].slice.call(value)
50
+ }
51
+ }
@@ -0,0 +1,55 @@
1
+ import { Controller } from "stimulus";
2
+ import { DirectUpload } from "./direct_upload_controller/direct_upload";
3
+
4
+ export default class extends Controller {
5
+ static targets = ['file'];
6
+ static values = {
7
+ 'url': String
8
+ }
9
+
10
+ initialize(){
11
+ this.onFileChange = this.onFileChange.bind(this)
12
+ }
13
+
14
+ connect(){
15
+ this.hiddenInput = document.createElement("input")
16
+ this.hiddenInput.type = "hidden"
17
+ this.hiddenInput.name = this.fileTarget.name
18
+ this.fileTarget.removeAttribute('name')
19
+ this.fileTarget.insertAdjacentElement("beforebegin", this.hiddenInput)
20
+ this.fileTarget.addEventListener('change', this.onFileChange)
21
+ }
22
+
23
+ disconnect(){
24
+ this.fileTarget.removeEventListener('change', this.onFileChange)
25
+ }
26
+
27
+ onFileChange(event){
28
+ const { target } = event
29
+ const { files } = target
30
+ const directUpload = new DirectUpload(files[0], this.urlValue, this)
31
+
32
+ directUpload.create((error, attributes) => {
33
+ if(error){
34
+ this.hiddenInput.removeAttribute('value')
35
+ }else{
36
+ this.hiddenInput.setAttribute('value', attributes.signed_id)
37
+ }
38
+ })
39
+ }
40
+
41
+ // DirectUpload delegate
42
+
43
+ directUploadWillCreateBlobWithXHR(xhr) {
44
+ // this.dispatch("before-blob-request", { xhr })
45
+ }
46
+
47
+ directUploadWillStoreFileWithXHR(xhr) {
48
+ // this.dispatch("before-storage-request", { xhr })
49
+ xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event))
50
+ }
51
+
52
+ uploadRequestDidProgress(event){
53
+
54
+ }
55
+ }
@@ -0,0 +1,21 @@
1
+ module ActiveStorage
2
+ # Extracts width and height in pixels from an image blob.
3
+ #
4
+ # Example:
5
+ #
6
+ # ActiveStorage::Analyzer::QiniuImageAnalyzer.new(blob).metadata
7
+ # # => {:size=>39504, :format=>"gif", :width=>708, :height=>576, :colorModel=>"palette0", :frameNumber=>1}
8
+ #
9
+ class Analyzer::QiniumImageAnalyzer < Analyzer
10
+ def self.accept?(blob)
11
+ blob.image?
12
+ end
13
+
14
+ def metadata
15
+ _code, data, _headers = blob.service.qiniu.client.get(blob.service.url(blob.key, fop: 'imageInfo'))
16
+ data.symbolize_keys
17
+ rescue StandardError
18
+ {}
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,156 @@
1
+ module ActiveStorage
2
+ class Service::QiniumService < Service
3
+ attr_reader :qiniu
4
+
5
+ delegate :config, :client, to: :qiniu
6
+ delegate :settings, :bucket_private, :bucket, :access_key, :secret_key, :domain,
7
+ :protocol, :put_policy_options,
8
+ to: :config
9
+
10
+ def self.analyzers
11
+ [ActiveStorage::Analyzer::QiniumImageAnalyzer]
12
+ end
13
+
14
+ def initialize(options)
15
+ @qiniu = Qinium.new(options)
16
+ end
17
+
18
+ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
19
+ instrument :url, key: key do |payload|
20
+ url = config.up_host
21
+ payload[:url] = url
22
+ url
23
+ end
24
+ end
25
+
26
+ def form_data_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
27
+ put_policy = Qinium::PutPolicy.new(config, key: key, expires_in: expires_in)
28
+ put_policy.fsize_limit = content_length.to_i + 1000
29
+ put_policy.mime_limit = content_type
30
+ put_policy.detect_mime = 1
31
+ put_policy.insert_only = 1
32
+ {
33
+ key: key,
34
+ token: put_policy.to_token
35
+ }
36
+ end
37
+
38
+ def upload(key, io, checksum: nil, content_type: nil, **)
39
+ instrument :upload, key: key, checksum: checksum do
40
+ io = File.open(io) unless io.respond_to?(:read)
41
+
42
+ put_policy = Qinium::PutPolicy.new(config, key: key, expires_in: put_policy_options.expires_in)
43
+ up_token = put_policy.to_token
44
+ blocks = []
45
+ file_size = 0
46
+ host = nil
47
+ while (blk = io.read(config.block_size))
48
+ data = upload_blk(blk, token: up_token, host: host)
49
+ ctx = data.fetch('ctx')
50
+ host = data.fetch('host')
51
+ file_size += blk.size
52
+ blocks.push(ctx)
53
+ end
54
+
55
+ _code, data, _headers = qiniu.object.mkfile(token: up_token, file_size: file_size, key: key, mime_type: content_type, blocks: blocks)
56
+ data
57
+ end
58
+ end
59
+
60
+ def update_metadata(key, **metadata)
61
+ end
62
+
63
+ def download(key)
64
+ if block_given?
65
+ instrument :streaming_download, key: key do
66
+ URI.open(url(key, disposition: :attachment)) do |file|
67
+ while data = file.read(64.kilobytes)
68
+ yield data
69
+ end
70
+ end
71
+ end
72
+ else
73
+ instrument :download, key: key do
74
+ URI.open(url(key, disposition: :attachment)).read
75
+ end
76
+ end
77
+ end
78
+
79
+ def download_chunk(key, range)
80
+ instrument :download_chunk, key: key, range: range do
81
+ uri = URI(url(key, disposition: :attachment))
82
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
83
+ client.get(uri,
84
+ 'Range' => "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body
85
+ end
86
+ end
87
+ end
88
+
89
+ def delete(key)
90
+ instrument :delete, key: key do
91
+ qiniu.object.delete(key)
92
+ end
93
+ end
94
+
95
+ def delete_prefixed(prefix)
96
+ instrument :delete_prefixed, prefix: prefix do
97
+ items_for(prefix).each { |item| delete item['key'] }
98
+ end
99
+ end
100
+
101
+ def exist?(key)
102
+ instrument :exist, key: key do |payload|
103
+ answer = items_for(key).any?
104
+ payload[:exist] = answer
105
+ answer
106
+ end
107
+ end
108
+
109
+ def url(key, **options)
110
+ instrument :url, key: key do |payload|
111
+ fop = if options[:fop].present? # 内容预处理
112
+ options[:fop]
113
+ elsif options[:disposition].to_s == 'attachment' # 下载附件
114
+ attname = URI.encode_www_form_component "#{options[:filename] || key}"
115
+ "attname=#{attname}"
116
+ end
117
+
118
+ url = if bucket_private
119
+ expires_in = options[:expires_in] ||
120
+ Rails.application.config.active_storage.service_urls_expire_in ||
121
+ 3600
122
+ qiniu.auth.authorize_download_url(settings, domain, key,
123
+ schema: protocol, fop: fop, expires_in: expires_in)
124
+ else
125
+ url_encoded_key = key.split('/').map { |x| CGI.escape(x) }.join('/')
126
+ ["#{protocol}://#{domain}/#{url_encoded_key}", fop].compact.join('?')
127
+ end
128
+
129
+ payload[:url] = url
130
+ url
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def items_for(prefix = '')
137
+ _code, data, _headers = qiniu.object.list(prefix: prefix)
138
+ data['items']
139
+ end
140
+
141
+ def upload_blk(blk, token:, host: nil)
142
+ with_retries max: 3 do
143
+ _code, data, _headers = qiniu.object.mkblk(blk, token: token, host: host)
144
+ data
145
+ end
146
+ end
147
+
148
+ def with_retries(max: 3)
149
+ yield
150
+ rescue
151
+ raise if max.zero?
152
+ max -= 1
153
+ retry
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveStorageQinium
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageQinium
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_storage_qinium/version"
4
+
5
+ module ActiveStorageQinium
6
+
7
+ end
@@ -0,0 +1 @@
1
+ require 'active_storage_qinium'
@@ -0,0 +1,4 @@
1
+ module ActivestorageQinium
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activestorage_qinium
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - xiaohui
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: qinium
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
27
+ description: Wraps the Qiniu Storage Service as an Active Storage service, support
28
+ muti-tenant settings. https://www.qiniu.com
29
+ email:
30
+ - xiaohui@tanmer.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - CHANGELOG.md
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - activestorage_qinium.gemspec
44
+ - app/javascript/active_storage_qinium/direct_upload_controller.js
45
+ - app/javascript/active_storage_qinium/direct_upload_controller/blob_record.js
46
+ - app/javascript/active_storage_qinium/direct_upload_controller/blob_upload.js
47
+ - app/javascript/active_storage_qinium/direct_upload_controller/direct_upload.js
48
+ - app/javascript/active_storage_qinium/direct_upload_controller/file_checksum.js
49
+ - app/javascript/active_storage_qinium/direct_upload_controller/helpers.js
50
+ - lib/active_storage/analyzer/qinium_image_analyzer.rb
51
+ - lib/active_storage/service/qinium_service.rb
52
+ - lib/active_storage_qinium.rb
53
+ - lib/active_storage_qinium/engine.rb
54
+ - lib/active_storage_qinium/version.rb
55
+ - lib/activestorage_qinium.rb
56
+ - sig/activestorage_qinium.rbs
57
+ homepage: https://github.com/xiaohui-zhangxh/activestorage_qinium
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: https://github.com/xiaohui-zhangxh/activestorage_qinium
62
+ source_code_uri: https://github.com/xiaohui-zhangxh/activestorage_qinium
63
+ changelog_uri: https://github.com/xiaohui-zhangxh/activestorage_qinium/CHANGELOG.md
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.6.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.3.7
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: A muti-tenant SDK wrap the Qiniu Storage Service as an Active Storage service
83
+ test_files: []