objectstore 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []