livejournal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to deal
3
+ in the Software without restriction, including without limitation the rights
4
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5
+ copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in
9
+ all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
data/README ADDED
@@ -0,0 +1,50 @@
1
+ =ljrb: LiveJournal Ruby module
2
+
3
+ Copyright:: Copyright (C) 2005 Evan Martin <martine@danga.com>
4
+ Website:: http://neugierig.org/software/livejournal/ruby
5
+
6
+ Example usage:
7
+ require 'livejournal/login'
8
+
9
+ puts "Logging in..."
10
+ user = LiveJournal::User.new('test', 'test')
11
+ login = LiveJournal::Request::Login.new(user)
12
+ login.run
13
+
14
+ puts "Login response:"
15
+ login.dumpresponse
16
+
17
+ puts "User's full name: #{user.fullname}"
18
+
19
+ ==LiveJournal Datatypes
20
+ * LiveJournal::Server
21
+ * LiveJournal::User
22
+ * LiveJournal::Entry
23
+ * LiveJournal::Comment
24
+ * LiveJournal::Friend
25
+
26
+ ==Implemented Requests
27
+ ===Login Requests
28
+ * LiveJournal::Request::Login
29
+ * LiveJournal::Request::SessionGenerate (only useful for syncing)
30
+ ===Friend Requests
31
+ * LiveJournal::Request::Friends
32
+ * LiveJournal::Request::FriendOfs
33
+ * LiveJournal::Request::CheckFriends
34
+ ===Entry Requests
35
+ * LiveJournal::Request::GetEvents
36
+ * LiveJournal::Request::EditEvent
37
+
38
+ ==Journal Offline Synchronization
39
+ * LiveJournal::Sync::Entries
40
+ * LiveJournal::Sync::Comments
41
+ See samples/export for an example of how to use these.
42
+
43
+ ==SQLite3 Support
44
+ * LiveJournal::Database -- storing/loading entries+comments with SQLite3
45
+ Integrates well with syncing. See samples/export.
46
+
47
+ ==Other Features
48
+ * LiveJournal::LogJam -- interface with LogJam (http://logjam.danga.com) 's
49
+ journal exports
50
+
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/packagetask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+
7
+ PKG_NAME = 'livejournal'
8
+ PKG_VERSION = '0.0.1'
9
+
10
+ FILES = FileList[
11
+ 'Rakefile', 'README', 'LICENSE', 'setup.rb',
12
+ 'lib/**/*', 'sample/*', 'test/*'
13
+ ]
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = PKG_NAME
17
+ s.version = PKG_VERSION
18
+ s.summary = 'module for interacting with livejournal'
19
+ s.description = %q{LiveJournal module. Post to livejournal, retrieve friends
20
+ lists, edit entries, sync journal to an offline database.}
21
+ s.author = 'Evan Martin'
22
+ s.email = 'martine@danga.com'
23
+ s.homepage = 'http://neugierig.org/software/livejournal/ruby/'
24
+
25
+ s.has_rdoc = true
26
+ s.files = FILES.to_a
27
+ end
28
+
29
+ desc 'Build Package'
30
+ Rake::GemPackageTask.new(spec) do |p|
31
+ p.need_tar = true
32
+ p.need_zip = true
33
+ end
34
+
35
+
36
+ desc 'Generate RDoc'
37
+ Rake::RDocTask.new :rdoc do |rd|
38
+ rd.title = "ljrb (LiveJournal Ruby module) Documentation"
39
+ rd.rdoc_dir = 'doc'
40
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
41
+ rd.main = 'README'
42
+ end
43
+
44
+ desc 'Run Tests'
45
+ Rake::TestTask.new :test do |t|
46
+ t.test_files = FileList['test/*.rb']
47
+ end
48
+
49
+ desc 'Push data to my webspace'
50
+ task :pushweb => [:rdoc, :package] do
51
+ pkg = "pkg/#{PKG_NAME}-#{PKG_VERSION}"
52
+ target = 'neugierig.org:/home/martine/www/neugierig/htdocs/software/livejournal/ruby'
53
+ sh %{rsync -av --delete doc/* #{target}/doc/}
54
+ sh %{rsync -av #{pkg}.* #{target}/download/}
55
+ end
56
+
57
+ desc 'Push everything'
58
+ task :push => [:pushweb] # XXX push to rubyforge
59
+
60
+ # vim: set ts=2 sw=2 et :
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+ #--
3
+ # ljrb -- LiveJournal Ruby module
4
+ # Copyright (c) 2005 Evan Martin <martine@danga.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #++
24
+
25
+ module LiveJournal
26
+ # A LiveJournal server. name is currently unused.
27
+ class Server
28
+ attr_accessor :name, :url
29
+
30
+ def initialize(name, url)
31
+ @name = name
32
+ @url = url
33
+ end
34
+ end
35
+ DEFAULT_SERVER = Server.new("LiveJournal.com", "http://www.livejournal.com")
36
+
37
+ # A LiveJournal user. Given a username, password, and server, running a
38
+ # LiveJournal::Request::Login will fill in the other fields.
39
+ class User
40
+ # parameter when creating a User
41
+ attr_accessor :username, :password, :server
42
+ # Set usejournal to log in as user username but act as user usejournal.
43
+ # For example, to work with a community you own.
44
+ attr_accessor :usejournal
45
+ # User's self-reported name, as retrieved by LiveJournal::Request::Login
46
+ attr_accessor :fullname
47
+ def initialize(username=nil, password=nil, server=nil)
48
+ @username = username
49
+ @password = password
50
+ @usejournal = nil
51
+ @server = server || LiveJournal::DEFAULT_SERVER
52
+ end
53
+ def to_s
54
+ "#{@username}: '#{@fullname}'"
55
+ end
56
+ end
57
+ end
58
+
59
+ # vim: ts=2 sw=2 et :
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/ruby
2
+ #--
3
+ # ljrb -- LiveJournal Ruby module
4
+ # Copyright (c) 2005 Evan Martin <martine@danga.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #++
24
+ #
25
+ # LiveJournal comments.
26
+
27
+ # http://www.livejournal.com/developer/exporting.bml
28
+
29
+ module LiveJournal
30
+ class Comment
31
+ attr_accessor :commentid, :posterid, :itemid, :parentid
32
+ # State of the comment. Possible values: {+:active+, +:screened+, +:deleted+}
33
+ attr_accessor :state
34
+ attr_accessor :subject, :body
35
+ # a Ruby Time object
36
+ attr_reader :time
37
+
38
+ def initialize
39
+ @commentid = @posterid = @itemid = @parentid = nil
40
+ @subject = @body = nil
41
+ @time = nil
42
+ @state = :active
43
+ end
44
+
45
+ # Convert a state to the string representation used by LiveJournal.
46
+ def self.state_from_string(str)
47
+ case str
48
+ when nil; :active
49
+ when 'A'; :active
50
+ when 'D'; :deleted
51
+ when 'S'; :screened
52
+ else raise ArgumentError, "Invalid comment state: #{str.inspect}"
53
+ end
54
+ end
55
+
56
+ # Convert a state from the string representation used by LiveJournal.
57
+ def self.state_to_string state
58
+ case state
59
+ when nil; nil
60
+ when :active; nil
61
+ when :deleted; 'D'
62
+ when :screened; 'S'
63
+ else raise ArgumentError, "Invalid comment state: #{state.inspect}"
64
+ end
65
+ end
66
+
67
+ def time=(time)
68
+ raise RuntimeError, "Must use GMT times everywhere to reduce confusion. See LiveJournal::coerce_gmt for details." unless time.gmt?
69
+ @time = time
70
+ end
71
+
72
+ def ==(other)
73
+ [:commentid, :posterid, :state, :itemid, :parentid,
74
+ :subject, :body, :time].each do |attr|
75
+ return false if send(attr) != other.send(attr)
76
+ end
77
+ return true
78
+ end
79
+ end
80
+ end
81
+
82
+ # vim: ts=2 sw=2 et :
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/ruby
2
+ #--
3
+ # ljrb -- LiveJournal Ruby module
4
+ # Copyright (c) 2005 Evan Martin <martine@danga.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #++
24
+ #
25
+ # REXML is pleasant to use but hella slow, so we allow using the expat-based
26
+ # parser as well.
27
+
28
+ require 'livejournal/comment'
29
+ require 'time' # parsing xmlschema times
30
+
31
+ module LiveJournal
32
+ HAVE_XML_PARSER = true
33
+
34
+ require 'rexml/document'
35
+ require 'xml/parser' if HAVE_XML_PARSER
36
+
37
+ module Sync
38
+ module CommentsXML
39
+ def self.optional_int_string(x)
40
+ return nil unless x
41
+ x.to_i
42
+ end
43
+
44
+ def self.load_comment_from_attrs(comment, attrs)
45
+ comment.commentid = attrs['id'].to_i
46
+ comment.posterid = CommentsXML::optional_int_string attrs['posterid']
47
+ comment.itemid = CommentsXML::optional_int_string attrs['jitemid']
48
+ comment.parentid = CommentsXML::optional_int_string attrs['parentid']
49
+ statestr = attrs['state']
50
+ comment.state = LiveJournal::Comment::state_from_string(statestr) if statestr
51
+ end
52
+
53
+ class Base
54
+ attr_reader :maxid, :comments, :usermap
55
+ def initialize(data=nil)
56
+ @maxid = nil
57
+ @comments = {}
58
+ @usermap = {}
59
+ parse data if data
60
+ end
61
+ end
62
+
63
+ class WithREXML < Base
64
+ def parse(data)
65
+ doc = REXML::Document.new(data)
66
+ root = doc.root
67
+
68
+ root.elements.each('maxid') { |e| @maxid = e.text.to_i }
69
+
70
+ root.elements.each('comments/comment') do |e|
71
+ id = e.attributes['id'].to_i
72
+ comment = @comments[id] || Comment.new
73
+ CommentsXML::load_comment_from_attrs(comment, e.attributes)
74
+ e.elements.each('subject') { |s| comment.subject = s.text }
75
+ e.elements.each('body') { |s| comment.body = s.text }
76
+ e.elements.each('date') { |s| comment.time = Time::xmlschema s.text }
77
+ @comments[id] = comment
78
+ end
79
+
80
+ root.elements.each('usermaps/usermap') do |e|
81
+ id = e.attributes['id'].to_i
82
+ user = e.attributes['user']
83
+ @usermap[id] = user
84
+ end
85
+ end
86
+ end
87
+
88
+ if HAVE_XML_PARSER
89
+ class WithExpat < Base
90
+ class Parser < XMLParser
91
+ attr_reader :maxid, :comments, :usermap
92
+ def initialize
93
+ super
94
+ @maxid = nil
95
+ @cur_comment = nil
96
+ @comments = {}
97
+ @usermap = {}
98
+ @content = nil
99
+ end
100
+ def startElement(name, attrs)
101
+ case name
102
+ when 'maxid'
103
+ @content = ''
104
+ when 'comment'
105
+ id = attrs['id'].to_i
106
+ @cur_comment = @comments[id] || Comment.new
107
+ @comments[id] = @cur_comment
108
+ CommentsXML::load_comment_from_attrs(@cur_comment, attrs)
109
+ when 'usermap'
110
+ id = attrs['id'].to_i
111
+ user = attrs['user']
112
+ @usermap[id] = user
113
+ when 'date'
114
+ @content = ''
115
+ when 'subject'
116
+ @content = ''
117
+ when 'body'
118
+ @content = ''
119
+ end
120
+ end
121
+ def character(data)
122
+ @content << data if @content
123
+ end
124
+ def endElement(name)
125
+ return unless @content
126
+ case name
127
+ when 'maxid'
128
+ @maxid = @content.to_i
129
+ when 'date'
130
+ @cur_comment.time = Time::xmlschema(@content)
131
+ when 'subject'
132
+ @cur_comment.subject = @content
133
+ when 'body'
134
+ @cur_comment.body = @content
135
+ end
136
+ @content = nil
137
+ end
138
+ end
139
+ def parse(data)
140
+ parser = Parser.new
141
+ parser.parse(data)
142
+ @maxid = parser.maxid
143
+ @comments = parser.comments
144
+ @usermap = parser.usermap
145
+ end
146
+ end # class WithExpat
147
+ end # if HAVE_XML_PARSER
148
+
149
+ if HAVE_XML_PARSER
150
+ Parser = WithExpat
151
+ else
152
+ Parser = WithREXML
153
+ end
154
+ end # module CommentsXML
155
+ end # module Sync
156
+ end # module LiveJournal
157
+
158
+ # vim: ts=2 sw=2 et :
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/ruby
2
+ #--
3
+ # ljrb -- LiveJournal Ruby module
4
+ # Copyright (c) 2005 Evan Martin <martine@danga.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #++
24
+ #
25
+ # This module interacts with the sqlite export from LogJam.
26
+
27
+ require 'sqlite3'
28
+
29
+ module LiveJournal
30
+ # A SQLite database dump.
31
+ class DatabaseError < RuntimeError; end
32
+ class Database
33
+ EXPECTED_DATABASE_VERSION = "1"
34
+ SCHEMA = %q{
35
+ CREATE TABLE meta (
36
+ key STRING PRIMARY KEY,
37
+ value STRING
38
+ );
39
+ CREATE TABLE entry (
40
+ itemid INTEGER PRIMARY KEY,
41
+ anum INTEGER,
42
+ subject STRING,
43
+ event STRING,
44
+ moodid INTEGER, mood STRING, music STRING, taglist STRING,
45
+ pickeyword STRING, preformatted INTEGER, backdated INTEGER,
46
+ comments INTEGER, year INTEGER, month INTEGER, day INTEGER,
47
+ timestamp INTEGER, security INTEGER
48
+ );
49
+ CREATE INDEX dateindex ON entry (year, month, day);
50
+ CREATE INDEX timeindex ON entry (timestamp);
51
+ CREATE TABLE comment (
52
+ commentid INTEGER PRIMARY KEY,
53
+ posterid INTEGER,
54
+ itemid INTEGER,
55
+ parentid INTEGER,
56
+ state STRING, -- screened/deleted/active
57
+ subject STRING,
58
+ body STRING,
59
+ timestamp INTEGER -- unix timestamp
60
+ );
61
+ CREATE INDEX commententry ON comment (itemid);
62
+ CREATE TABLE users (
63
+ userid INTEGER PRIMARY KEY,
64
+ username STRING
65
+ );
66
+ CREATE TABLE commentprop (
67
+ commentid INTEGER, -- not primary key 'cause non-unique
68
+ key STRING,
69
+ value STRING
70
+ );
71
+ }.gsub(/^ /, '')
72
+
73
+ def self.optional_to_i(x)
74
+ return nil if x.nil?
75
+ return x.to_i
76
+ end
77
+
78
+ attr_reader :db
79
+ def initialize(filename, create=false)
80
+ exists = FileTest::exists? filename if create
81
+ @db = SQLite3::Database.new(filename)
82
+
83
+ # We'd like to use type translation, but it unfortunately fails on MAX()
84
+ # queries.
85
+ # @db.type_translation = true
86
+
87
+ if exists
88
+ version = self.version
89
+ unless version == EXPECTED_DATABASE_VERSION
90
+ raise DatabaseError, "Database version mismatch -- db has #{version.inspect}, expected #{EXPECTED_DATABASE_VERSION.inspect}"
91
+ end
92
+ end
93
+
94
+ #trace!
95
+ if create and not exists
96
+ run_schema!
97
+ self.version = EXPECTED_DATABASE_VERSION
98
+ end
99
+ end
100
+
101
+ def transaction
102
+ @db.transaction { yield }
103
+ end
104
+
105
+ def close
106
+ @db.close
107
+ end
108
+
109
+ def self.db_value(name, sym)
110
+ class_eval %{def #{sym}; get_meta(#{name.inspect}); end}
111
+ class_eval %{def #{sym}=(v); set_meta(#{name.inspect}, v); end}
112
+ end
113
+
114
+ db_value 'username', :username
115
+ db_value 'lastsync', :lastsync
116
+ db_value 'version', :version
117
+
118
+ def run_schema!
119
+ transaction do
120
+ @db.execute_batch(SCHEMA)
121
+ end
122
+ end
123
+
124
+ # Turn tracing on.
125
+ def trace!
126
+ @db.trace() do |data, sql|
127
+ puts "SQL> #{sql.inspect}"
128
+ end
129
+ end
130
+
131
+ # Fetch a specific itemid.
132
+ def get_entry(itemid)
133
+ query_entry("select * from entry where itemid=?", itemid)
134
+ end
135
+
136
+ # Given SQL that selects an entry, return that entry.
137
+ def query_entry(sql, *sqlargs)
138
+ row = @db.get_first_row(sql, *sqlargs)
139
+ return Entry.new.load_from_database_row(row)
140
+ end
141
+
142
+ # Given SQL that selects some entries, yield each entry.
143
+ def query_entries(sql, *sqlargs)
144
+ @db.execute(sql, *sqlargs) do |row|
145
+ yield Entry.new.load_from_database_row(row)
146
+ end
147
+ end
148
+
149
+ # Yield most recent limit entries.
150
+ def each_entry(limit=nil, &block)
151
+ sql = 'SELECT * FROM entry ORDER BY itemid DESC'
152
+ sql += " LIMIT #{limit}" if limit
153
+ query_entries sql, &block
154
+ end
155
+
156
+ # Return the total number of entries.
157
+ def total_entry_count
158
+ @db.get_first_value('SELECT COUNT(*) FROM entry').to_i
159
+ end
160
+
161
+ def store_entry entry
162
+ sql = 'INSERT OR REPLACE INTO entry VALUES (' + ("?, " * 16) + '?)'
163
+ @db.execute(sql, *entry.to_database_row)
164
+ end
165
+
166
+ def last_comment_meta
167
+ Database::optional_to_i(
168
+ @db.get_first_value('SELECT MAX(commentid) FROM comment'))
169
+ end
170
+ def last_comment_full
171
+ Database::optional_to_i(
172
+ @db.get_first_value('SELECT MAX(commentid) FROM comment ' +
173
+ 'WHERE body IS NOT NULL'))
174
+ end
175
+
176
+ def _store_comments(comments, meta_only=true)
177
+ transaction do
178
+ sql = "INSERT OR REPLACE INTO comment "
179
+ if meta_only
180
+ sql += "(commentid, posterid, state) VALUES (?, ?, ?)"
181
+ else
182
+ sql += "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
183
+ end
184
+ @db.prepare(sql) do |stmt|
185
+ comments.each do |id, comment|
186
+ if meta_only
187
+ stmt.execute(comment.commentid, comment.posterid,
188
+ LiveJournal::Comment::state_to_string(comment.state))
189
+ else
190
+ stmt.execute(*comment.to_database_row)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ def store_comments_meta(comments)
198
+ _store_comments(comments, true)
199
+ end
200
+ def store_comments_full(comments)
201
+ _store_comments(comments, false)
202
+ end
203
+
204
+ def store_usermap(usermap)
205
+ transaction do
206
+ sql = "INSERT OR REPLACE INTO users VALUES (?, ?)"
207
+ @db.prepare(sql) do |stmt|
208
+ usermap.each do |id, user|
209
+ stmt.execute(id, user)
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ def get_meta key
216
+ return @db.get_first_value('SELECT value FROM meta WHERE key=?', key)
217
+ end
218
+ def set_meta key, value
219
+ db.transaction do
220
+ @db.execute('INSERT OR REPLACE INTO meta VALUES (?, ?)', key, value)
221
+ end
222
+ end
223
+ end
224
+
225
+ class Entry
226
+ # Parse an entry from a row from the database.
227
+ def load_from_database_row row
228
+ @itemid, @anum = row[0].to_i, row[1].to_i
229
+ @subject, @event = row[2], row[3]
230
+ @moodid, @mood = row[4].nil? ? nil : row[4].to_i, row[5]
231
+ @music, @taglist, @pickeyword = row[6], row[7], row[8]
232
+ @taglist = if @taglist then @taglist.split(/, /) else [] end
233
+ @preformatted, @backdated = !row[9].nil?, !row[10].nil?
234
+ @comments = case Database::optional_to_i(row[11])
235
+ when nil; :normal
236
+ when 1; :none
237
+ when 2; :noemail
238
+ else raise DatabaseError, "Bad comments value: #{row[11].inspect}"
239
+ end
240
+
241
+ @time = Time.at(row[15].to_i).utc
242
+
243
+ case Database::optional_to_i(row[16])
244
+ when nil
245
+ @security = :public
246
+ when 0
247
+ @security = :private
248
+ when 1
249
+ @security = :friends
250
+ else
251
+ @security = :custom
252
+ @allowmask = row[16]
253
+ end
254
+
255
+ self
256
+ end
257
+ def to_database_row
258
+ comments = case @comments
259
+ when :normal; nil
260
+ when :none; 1
261
+ when :noemail; 2
262
+ end
263
+ security = case @security
264
+ when :public; nil
265
+ when :private; 0
266
+ when :friends; 1
267
+ when :custom; @allowmask
268
+ end
269
+ [@itemid, @anum, @subject, @event,
270
+ @moodid, @mood, @music, @taglist.join(', '), @pickeyword,
271
+ @preformatted ? 1 : nil, @backdated ? 1 : nil, comments,
272
+ @time.year, @time.mon, @time.day, @time.to_i, security]
273
+ end
274
+ end
275
+ class Comment
276
+ def load_from_database_row row
277
+ @commentid, @posterid = row[0].to_i, row[1].to_i
278
+ @itemid, @parentid = row[2].to_i, row[3].to_i
279
+ @state = Comment::state_from_string row[4]
280
+ @subject, @body = row[5], row[6]
281
+ @time = Time.at(row[7]).utc
282
+ self
283
+ end
284
+ def to_database_row
285
+ state = Comment::state_to_string @state
286
+ [@commentid, @posterid, @itemid, @parentid,
287
+ state, @subject, @body, @time.to_i]
288
+ end
289
+ end
290
+ end
291
+
292
+ # vim: ts=2 sw=2 et :