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.
@@ -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: []