Start support for reading data back from the database.
[quanlib.git] / store.rb
1
2 require 'fileutils'
3 require 'pg'
4
5 class Store
6   def initialize
7     @basepath = '/home/chris/prog/quanlib/efs'  # TODO: FIXME: configure this in a sane way
8     @conn = nil
9
10     #@dburl = 'dbi:Pg:quanlib:localhost'
11     @dbhost = "localhost"
12     @dbport = 5432
13     @dbname = 'quanlib'
14     @dbuser = 'quanlib'
15     @dbpass = 'quanlib'
16   end
17
18   def connect
19     # @conn = PGconn.connect('localhost', 5432, '', '', 'quanlib', 'quanlib', 'quanlib')
20     @conn = PG.connect('localhost', 5432, '', '', 'quanlib', 'quanlib', 'quanlib')
21     return @conn
22   end
23
24   def disconnect
25     @conn.close()
26   end
27
28   def construct_efs_path(efs_id)
29     id_str = sprintf('%010d', efs_id)
30     path = sprintf('%s/%s/%s/%s', id_str[0,2], id_str[2,2], id_str[4,2], id_str[6,2])
31     name = id_str + '.dat'
32     return path, name
33   end
34
35   def create_schema
36     create_authors = 
37 <<EOS
38       CREATE TABLE Authors (
39         id          SERIAL PRIMARY KEY,
40         grouping    VARCHAR(64),
41         reading     VARCHAR(128),
42         sort        VARCHAR(128)
43       );
44 EOS
45
46     create_books = 
47 <<EOS
48       CREATE TABLE Books (
49         id          SERIAL PRIMARY KEY,
50         author      INTEGER REFERENCES Authors(id),
51         cover       INTEGER,
52         description TEXT,
53         path        VARCHAR(256),
54         series      VARCHAR(128),
55         title       VARCHAR(196),
56         volume      VARCHAR(16)
57       );
58 EOS
59
60     create_efs = 
61 <<EOS
62       CREATE TABLE EFS (
63         id          INTEGER,
64         mimetype    VARCHAR(64)
65       );
66 EOS
67
68     stmts = [
69       create_authors,
70       create_books,
71       create_efs,
72       'CREATE SEQUENCE efs_id;'
73     ]
74
75     for stmt in stmts
76       @conn.exec(stmt)
77     end
78   end
79
80   def dropSchema
81     stmts = [
82       'DROP TABLE Books;',
83       'DROP TABLE Authors;',
84       'DROP TABLE EFS;',
85       'DROP SEQUENCE efs_id;'
86     ]
87
88     for stmt in stmts do
89       @conn.exec(stmt)
90     end
91   end
92
93   def init_db
94     sql = "SELECT 1 FROM pg_tables WHERE tableowner='quanlib' AND tablename='books'"
95     found = false
96     @conn.exec(sql).each do |row|
97       found = true
98     end
99
100     if ! found
101       create_schema()
102     end
103   end
104
105   def find_author(author)
106     sqlSelect = "SELECT id FROM Authors WHERE grouping=$1 AND reading=$2 AND sort=$3;"
107     args = [author.grouping, author.reading_order, author.sort_order]
108     @conn.exec_params(sqlSelect, args) do |rs|
109       if rs.ntuples > 0
110         return rs[0]['id']
111       end
112     end
113     return nil
114   end
115
116   def load_author(id)
117     sqlSelect = "SELECT grouping, reading, sort FROM Authors WHERE id=$1"
118     args = [id]
119     @conn.exec_params(sqlSelect, args) do |rs|
120       if rs.ntuples != 1
121         raise "Expected 1 row for " + id + " but got " + rs.ntuples + ":  " + sqlSelect
122       end
123       row = rs[0]
124       return Author.new(row['grouping'], row['reading'], row['sort'])
125     end
126   end
127
128   def store_author(author)
129     id = find_author(author)
130     if nil == id
131       sqlInsert = "INSERT INTO Authors(grouping, reading, sort) VALUES ($1, $2, $3);"
132       args = [author.grouping, author.reading_order, author.sort_order]
133       begin 
134         rs = @conn.exec_params(sqlInsert, args)
135       rescue Exception => e
136         puts sqlInsert + ":  " + args.inspect()
137         puts e.message
138         puts $@
139       ensure
140         rs.clear if rs
141       end
142     end
143     return find_author(author)
144   end
145
146   def load_book(id)
147     sql = "SELECT author, cover, description, path, series, title, volume FROM Books WHERE id=$1;"
148     book = nil
149
150     begin
151       @conn.exec_params(sql, [id]) do |rs|
152         if 1 != rs.ntuples
153           raise 'Expected one row in Books for id ' + id + ', but found ' + rs.length + '.'
154           return nil
155         end
156         row = rs[0]
157
158         book = Book.new()
159         book.author = load_author(row['author'])
160         book.cover = load_cover(row['cover'])
161         book.description = row['description']
162         book.path = row['path']
163         book.series = row['series']
164         book.title = row['title']
165         book.volume = row['volume']
166       end    
167     rescue Exception => e
168       puts sql + ": " + id
169       puts e.message
170     end
171
172     return book
173   end
174
175   def store_book(book)
176     sql = "INSERT INTO Books (author, cover, description, path, series, title, volume) VALUES ($1, $2, $3, $4, $5, $6, $7);"
177
178     author_id = store_author(book.author)
179     (efs_id, mime_type) = store_cover(book)
180
181     args = [author_id, efs_id, book.description(), book.path(), book.series(), book.title(), book.volume()]
182
183     begin
184       rs = @conn.exec_params(sql, args)
185     rescue Exception => e
186       puts sql + ": " + args.inspect()
187       puts e.message 
188       puts $@
189     ensure
190       rs.clear if rs
191     end
192   end
193
194   def load_cover(id)
195     (efspath, efsname) = construct_efs_path(id)
196     efspath = @basepath + '/' + efspath
197     cover = Cover.new()
198     cover.load_image(efspath + '/' + efsname)
199     return cover
200   end
201
202   def store_cover(book)
203     efs_id = nil
204     cover = book.cover()
205
206     if nil == cover
207       return nil
208     end
209
210     @conn.exec("SELECT nextval('efs_id')") do |rs|
211       efs_id = rs[0]['nextval']
212     end
213
214     if nil == efs_id
215       return nil
216     end
217
218     (efspath, efsname) = construct_efs_path(efs_id)
219
220     efspath = @basepath + '/' + efspath
221
222     FileUtils.mkdir_p(efspath)
223
224     (filepath, mimetype) = cover.write_image(efspath, efsname)
225
226     sql = "INSERT INTO efs VALUES ($1, $2)"
227     begin
228       rs = @conn.exec_params(sql, [efs_id, mimetype])
229     rescue Exception => e
230       puts e.message
231       puts $@
232     ensure
233       rs.clear if rs
234     end
235     
236     return efs_id, mimetype
237   end
238 end
239