objectstore 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/atech/object_store.rb +15 -0
- data/lib/atech/object_store/file.rb +169 -0
- data/schema.sql +45 -0
- metadata +47 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Atech
|
2
|
+
module ObjectStore
|
3
|
+
VERSION = '0.0.1'
|
4
|
+
|
5
|
+
## Error class which all Object Store errors are inherited fro
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :backend
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'atech/object_store/file'
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Atech
|
2
|
+
module ObjectStore
|
3
|
+
class File
|
4
|
+
|
5
|
+
## Raised when a file cannot be found
|
6
|
+
class FileNotFound < Error; end
|
7
|
+
|
8
|
+
## Raised if a file cannot be added
|
9
|
+
class ValidationError < Error; end
|
10
|
+
|
11
|
+
## Raised if a frozen file is editted
|
12
|
+
class CannotEditFrozenFile < Error; end
|
13
|
+
|
14
|
+
## Returns a new file object for the given ID. If no file is found a FileNotFound exception will be raised
|
15
|
+
## otherwise the File object will be returned.
|
16
|
+
def self.find_by_id(id)
|
17
|
+
result = ObjectStore.backend.query("SELECT * FROM files WHERE id = #{id.to_i}").first || raise(FileNotFound, "File not found with id '#{id.to_i}'")
|
18
|
+
self.new(result)
|
19
|
+
end
|
20
|
+
|
21
|
+
## Imports a new file by passing a path and returning a new File object once it has been added to the database.
|
22
|
+
## If the file does not exist a ValidationError will be raised.
|
23
|
+
def self.add_local_file(path)
|
24
|
+
if ::File.exist?(path)
|
25
|
+
file_stat = ::File.stat(path)
|
26
|
+
file_data = ::File.read(path)
|
27
|
+
file_name = path.split('/').last
|
28
|
+
add_file(file_name, file_data, :created_at => file_stat.ctime, :updated_at => file_stat.mtime)
|
29
|
+
else
|
30
|
+
raise ValidationError, "File does not exist at '#{path}' to add"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
## Inserts a new File into the database. Returns a new object if successfully inserted or raises an error.
|
35
|
+
## Filename & data must be provided, options options will be added automatically unless specified.
|
36
|
+
def self.add_file(filename, data = '', options = {})
|
37
|
+
## Create a hash of properties to be for this class
|
38
|
+
options[:name] = filename
|
39
|
+
options[:size] ||= data.bytesize
|
40
|
+
options[:blob] = data
|
41
|
+
options[:created_at] ||= Time.now
|
42
|
+
options[:updated_at] ||= Time.now
|
43
|
+
|
44
|
+
## Ensure that new files have a filename & data
|
45
|
+
raise ValidationError, "A 'name' must be provided to add a new file" if options[:name].nil?
|
46
|
+
|
47
|
+
## Encode timestamps
|
48
|
+
options[:created_at] = options[:created_at].utc
|
49
|
+
options[:updated_at] = options[:updated_at].utc
|
50
|
+
|
51
|
+
##Create an insert query
|
52
|
+
columns = options.keys.join('`,`')
|
53
|
+
data = options.values.map { |data| escape_and_quote(data.to_s) }.join(',')
|
54
|
+
ObjectStore.backend.query("INSERT INTO files (`#{columns}`) VALUES (#{data})")
|
55
|
+
|
56
|
+
## Return a new File object
|
57
|
+
self.new(options.merge({:id => ObjectStore.backend.last_id}))
|
58
|
+
end
|
59
|
+
|
60
|
+
## Initialises a new File object with the hash of attributes from a MySQL query ensuring that
|
61
|
+
## all symbols are strings
|
62
|
+
def initialize(attributes)
|
63
|
+
@attributes = parsed_attributes(attributes)
|
64
|
+
end
|
65
|
+
|
66
|
+
## Returns details about the file
|
67
|
+
def inspect
|
68
|
+
"#<Atech::ObjectStore::File[#{id}] name=#{name}>"
|
69
|
+
end
|
70
|
+
|
71
|
+
## Returns the ID of the file
|
72
|
+
def id
|
73
|
+
@attributes['id']
|
74
|
+
end
|
75
|
+
|
76
|
+
## Returns the name of the file
|
77
|
+
def name
|
78
|
+
@attributes['name']
|
79
|
+
end
|
80
|
+
|
81
|
+
## Returns the size of the file as an integer
|
82
|
+
def size
|
83
|
+
@attributes['size'].to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
## Returns the date the file was created
|
87
|
+
def created_at
|
88
|
+
@attributes['created_at']
|
89
|
+
end
|
90
|
+
|
91
|
+
## Returns the date the file was last updated
|
92
|
+
def updated_at
|
93
|
+
@attributes['updated_at']
|
94
|
+
end
|
95
|
+
|
96
|
+
## Returns the blob data
|
97
|
+
def blob
|
98
|
+
@attributes['blob']
|
99
|
+
end
|
100
|
+
|
101
|
+
## Returns whether this objec tis frozen or not
|
102
|
+
def frozen?
|
103
|
+
!!@frozen
|
104
|
+
end
|
105
|
+
|
106
|
+
## Downloads the current file to a path on your local server. If a file already exists at
|
107
|
+
## the path entered, it will be overriden.
|
108
|
+
def copy(path)
|
109
|
+
::File.open(path, 'w') { |f| f.write(blob) }
|
110
|
+
end
|
111
|
+
|
112
|
+
## Appends data to the end of the current blob and updates the size and update time as appropriate.
|
113
|
+
def append(data)
|
114
|
+
raise CannotEditFrozenFile, "This file has been frozen and cannot be appended to" if frozen?
|
115
|
+
ObjectStore.backend.query("UPDATE files SET `blob` = CONCAT(`blob`, #{self.class.escape_and_quote(data)}), `size` = `size` + #{data.bytesize}, `updated_at` = '#{self.class.time_now}' WHERE id = #{@attributes['id']}")
|
116
|
+
reload(true)
|
117
|
+
end
|
118
|
+
|
119
|
+
## Overwrites any data which is stored in the file
|
120
|
+
def overwrite(data)
|
121
|
+
raise CannotEditFrozenFile, "This file has been frozen and cannot be overwriten" if frozen?
|
122
|
+
ObjectStore.backend.query("UPDATE files SET `blob` = #{self.class.escape_and_quote(data)}, `size` = #{data.bytesize}, `updated_at` = '#{self.class.time_now}' WHERE id = #{@attributes['id']}")
|
123
|
+
@attributes['blob'] = data
|
124
|
+
reload
|
125
|
+
end
|
126
|
+
|
127
|
+
## Changes the name for a file
|
128
|
+
def rename(name)
|
129
|
+
raise CannotEditFrozenFile, "This file has been frozen and cannot be renamed" if frozen?
|
130
|
+
ObjectStore.backend.query("UPDATE files SET `name` = #{self.class.escape_and_quote(name)}, `updated_at` = '#{self.class.time_now}' WHERE id = #{@attributes['id']}")
|
131
|
+
reload
|
132
|
+
end
|
133
|
+
|
134
|
+
## Removes the file from the database
|
135
|
+
def delete
|
136
|
+
raise CannotEditFrozenFile, "This file has been frozen and cannot be deleted" if frozen?
|
137
|
+
ObjectStore.backend.query("DELETE FROM files WHERE id = #{@attributes['id']}")
|
138
|
+
@frozen = true
|
139
|
+
end
|
140
|
+
|
141
|
+
## Reload properties from the database. Optionally, pass true to include the blob
|
142
|
+
## in the update
|
143
|
+
def reload(include_blob = false)
|
144
|
+
query = ObjectStore.backend.query("SELECT #{include_blob ? '*' : '`id`, `name`, `size`, `created_at`, `updated_at`'} FROM files WHERE id = #{@attributes['id']}").first
|
145
|
+
@attributes.merge!(parsed_attributes(query))
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def parsed_attributes(attributes)
|
151
|
+
attributes.inject(Hash.new) do |hash,(key,value)|
|
152
|
+
hash[key.to_s] = value
|
153
|
+
hash
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.escape_and_quote(string)
|
158
|
+
string = string.strftime('%Y-%m-%d %H:%M:%S') if string.is_a?(Time)
|
159
|
+
"'#{ObjectStore.backend.escape(string)}'"
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.time_now
|
163
|
+
Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/schema.sql
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
-- MySQL dump 10.13 Distrib 5.1.62, for debian-linux-gnu (x86_64)
|
2
|
+
--
|
3
|
+
-- Host: localhost Database: objectstore
|
4
|
+
-- ------------------------------------------------------
|
5
|
+
-- Server version 5.1.62-0ubuntu0.10.04.1
|
6
|
+
|
7
|
+
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
8
|
+
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
9
|
+
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
10
|
+
/*!40101 SET NAMES utf8 */;
|
11
|
+
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
12
|
+
/*!40103 SET TIME_ZONE='+00:00' */;
|
13
|
+
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
14
|
+
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
15
|
+
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
16
|
+
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
17
|
+
|
18
|
+
--
|
19
|
+
-- Table structure for table `files`
|
20
|
+
--
|
21
|
+
|
22
|
+
DROP TABLE IF EXISTS `files`;
|
23
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
24
|
+
/*!40101 SET character_set_client = utf8 */;
|
25
|
+
CREATE TABLE `files` (
|
26
|
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
27
|
+
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
28
|
+
`size` int(11) NOT NULL,
|
29
|
+
`blob` longblob NOT NULL,
|
30
|
+
`created_at` datetime NOT NULL,
|
31
|
+
`updated_at` datetime NOT NULL,
|
32
|
+
PRIMARY KEY (`id`)
|
33
|
+
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
34
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
35
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
36
|
+
|
37
|
+
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
38
|
+
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
39
|
+
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
40
|
+
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
41
|
+
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
42
|
+
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
43
|
+
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
44
|
+
|
45
|
+
-- Dump completed on 2012-06-09 11:19:03
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: objectstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Cooke
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-09 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: adam@atechmedia.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- schema.sql
|
21
|
+
- lib/atech/object_store/file.rb
|
22
|
+
- lib/atech/object_store.rb
|
23
|
+
homepage: http://www.atechmedia.com
|
24
|
+
licenses: []
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 1.8.17
|
44
|
+
signing_key:
|
45
|
+
specification_version: 3
|
46
|
+
summary: A SQL based object store library
|
47
|
+
test_files: []
|