livejournal 0.1.0 → 0.2.0
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/README +2 -2
- data/Rakefile +2 -2
- data/lib/livejournal/basic.rb +3 -0
- data/lib/livejournal/comments-xml.rb +7 -4
- data/lib/livejournal/database.rb +76 -60
- data/lib/livejournal/entry.rb +24 -4
- data/lib/livejournal/friends.rb +14 -1
- data/lib/livejournal/login.rb +2 -0
- data/lib/livejournal/logjam.rb +2 -0
- data/lib/livejournal/request.rb +3 -0
- data/lib/livejournal/sync.rb +3 -0
- data/sample/export +38 -48
- data/sample/fuse +198 -0
- data/sample/graph +0 -1
- data/sample/progressbar.rb +41 -0
- metadata +41 -34
data/README
CHANGED
@@ -26,12 +26,12 @@ Example usage:
|
|
26
26
|
==Implemented Requests
|
27
27
|
===Login Requests
|
28
28
|
* LiveJournal::Request::Login
|
29
|
-
* LiveJournal::Request::SessionGenerate (only useful for syncing)
|
30
29
|
===Friend Requests
|
31
30
|
* LiveJournal::Request::Friends
|
32
31
|
* LiveJournal::Request::FriendOfs
|
33
32
|
* LiveJournal::Request::CheckFriends
|
34
33
|
===Entry Requests
|
34
|
+
* LiveJournal::Request::PostEvent
|
35
35
|
* LiveJournal::Request::GetEvents
|
36
36
|
* LiveJournal::Request::EditEvent
|
37
37
|
|
@@ -46,5 +46,5 @@ Integrates well with syncing. See samples/export.
|
|
46
46
|
|
47
47
|
==Other Features
|
48
48
|
* LiveJournal::LogJam -- interface with LogJam (http://logjam.danga.com) 's
|
49
|
-
journal exports
|
49
|
+
journal exports. (XXX currently broken)
|
50
50
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/rdoctask'
|
|
5
5
|
require 'rake/testtask'
|
6
6
|
|
7
7
|
PKG_NAME = 'livejournal'
|
8
|
-
PKG_VERSION = '0.
|
8
|
+
PKG_VERSION = '0.2.0'
|
9
9
|
|
10
10
|
FILES = FileList[
|
11
11
|
'Rakefile', 'README', 'LICENSE', 'setup.rb',
|
@@ -37,7 +37,7 @@ desc 'Generate RDoc'
|
|
37
37
|
Rake::RDocTask.new :rdoc do |rd|
|
38
38
|
rd.title = "ljrb (LiveJournal Ruby module) Documentation"
|
39
39
|
rd.rdoc_dir = 'doc'
|
40
|
-
rd.rdoc_files.add 'lib', 'README', 'LICENSE'
|
40
|
+
rd.rdoc_files.add 'lib/livejournal/*.rb', 'README', 'LICENSE'
|
41
41
|
rd.main = 'README'
|
42
42
|
end
|
43
43
|
|
data/lib/livejournal/basic.rb
CHANGED
@@ -29,10 +29,13 @@ require 'livejournal/comment'
|
|
29
29
|
require 'time' # parsing xmlschema times
|
30
30
|
|
31
31
|
module LiveJournal
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
begin
|
33
|
+
require 'xml/parser'
|
34
|
+
HAVE_XML_PARSER = true
|
35
|
+
rescue Exception
|
36
|
+
require 'rexml/document'
|
37
|
+
HAVE_XML_PARSER = false
|
38
|
+
end
|
36
39
|
|
37
40
|
module Sync
|
38
41
|
module CommentsXML
|
data/lib/livejournal/database.rb
CHANGED
@@ -27,22 +27,23 @@
|
|
27
27
|
require 'sqlite3'
|
28
28
|
|
29
29
|
module LiveJournal
|
30
|
-
# A SQLite database dump.
|
31
30
|
class DatabaseError < RuntimeError; end
|
31
|
+
|
32
|
+
# An interface for an SQLite database dump.
|
32
33
|
class Database
|
33
|
-
EXPECTED_DATABASE_VERSION = "
|
34
|
+
EXPECTED_DATABASE_VERSION = "2"
|
34
35
|
SCHEMA = %q{
|
35
36
|
CREATE TABLE meta (
|
36
|
-
key
|
37
|
-
value
|
37
|
+
key TEXT PRIMARY KEY,
|
38
|
+
value TEXT
|
38
39
|
);
|
39
40
|
CREATE TABLE entry (
|
40
41
|
itemid INTEGER PRIMARY KEY,
|
41
42
|
anum INTEGER,
|
42
|
-
subject
|
43
|
-
event
|
44
|
-
moodid INTEGER, mood
|
45
|
-
pickeyword
|
43
|
+
subject TEXT,
|
44
|
+
event TEXT,
|
45
|
+
moodid INTEGER, mood TEXT, music TEXT, taglist TEXT,
|
46
|
+
pickeyword TEXT, preformatted INTEGER, backdated INTEGER,
|
46
47
|
comments INTEGER, year INTEGER, month INTEGER, day INTEGER,
|
47
48
|
timestamp INTEGER, security INTEGER
|
48
49
|
);
|
@@ -53,31 +54,34 @@ module LiveJournal
|
|
53
54
|
posterid INTEGER,
|
54
55
|
itemid INTEGER,
|
55
56
|
parentid INTEGER,
|
56
|
-
state
|
57
|
-
subject
|
58
|
-
body
|
57
|
+
state TEXT, -- screened/deleted/active
|
58
|
+
subject TEXT,
|
59
|
+
body TEXT,
|
59
60
|
timestamp INTEGER -- unix timestamp
|
60
61
|
);
|
61
62
|
CREATE INDEX commententry ON comment (itemid);
|
62
63
|
CREATE TABLE users (
|
63
64
|
userid INTEGER PRIMARY KEY,
|
64
|
-
username
|
65
|
+
username TEXT
|
65
66
|
);
|
66
67
|
CREATE TABLE commentprop (
|
67
68
|
commentid INTEGER, -- not primary key 'cause non-unique
|
68
|
-
key
|
69
|
-
value
|
69
|
+
key TEXT,
|
70
|
+
value TEXT
|
70
71
|
);
|
71
72
|
}.gsub(/^ /, '')
|
72
73
|
|
73
|
-
def self.optional_to_i(x)
|
74
|
+
def self.optional_to_i(x) # :nodoc:
|
74
75
|
return nil if x.nil?
|
75
76
|
return x.to_i
|
76
77
|
end
|
77
78
|
|
79
|
+
# The underlying SQLite3 database.
|
78
80
|
attr_reader :db
|
79
|
-
|
80
|
-
|
81
|
+
|
82
|
+
def initialize(filename, create_if_necessary=false)
|
83
|
+
exists = FileTest::exists? filename
|
84
|
+
raise Errno::ENOENT if not create_if_necessary and not exists
|
81
85
|
@db = SQLite3::Database.new(filename)
|
82
86
|
|
83
87
|
# We'd like to use type translation, but it unfortunately fails on MAX()
|
@@ -85,43 +89,56 @@ module LiveJournal
|
|
85
89
|
# @db.type_translation = true
|
86
90
|
|
87
91
|
if exists
|
92
|
+
# Existing database!
|
88
93
|
version = self.version
|
89
94
|
unless version == EXPECTED_DATABASE_VERSION
|
90
95
|
raise DatabaseError, "Database version mismatch -- db has #{version.inspect}, expected #{EXPECTED_DATABASE_VERSION.inspect}"
|
91
96
|
end
|
92
97
|
end
|
93
98
|
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
if create_if_necessary and not exists
|
100
|
+
# New database! Initialize it.
|
101
|
+
transaction do
|
102
|
+
@db.execute_batch(SCHEMA)
|
103
|
+
end
|
97
104
|
self.version = EXPECTED_DATABASE_VERSION
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
108
|
+
# Run a block within a single database transaction.
|
109
|
+
# Useful for bulk inserts.
|
101
110
|
def transaction
|
102
111
|
@db.transaction { yield }
|
103
112
|
end
|
104
113
|
|
114
|
+
# Close the underlying database. (Is this necessary? Not sure.)
|
105
115
|
def close
|
106
116
|
@db.close
|
107
117
|
end
|
108
118
|
|
109
|
-
def
|
119
|
+
def get_meta key # :nodoc:
|
120
|
+
return @db.get_first_value('SELECT value FROM meta WHERE key=?', key)
|
121
|
+
end
|
122
|
+
def set_meta key, value # :nodoc:
|
123
|
+
@db.execute('INSERT OR REPLACE INTO meta VALUES (?, ?)', key, value)
|
124
|
+
end
|
125
|
+
def self.db_value(name, sym) # :nodoc:
|
110
126
|
class_eval %{def #{sym}; get_meta(#{name.inspect}); end}
|
111
127
|
class_eval %{def #{sym}=(v); set_meta(#{name.inspect}, v); end}
|
112
128
|
end
|
113
129
|
|
114
130
|
db_value 'username', :username
|
131
|
+
db_value 'usejournal', :usejournal
|
115
132
|
db_value 'lastsync', :lastsync
|
116
133
|
db_value 'version', :version
|
117
134
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
135
|
+
# The the actual journal stored by this Database.
|
136
|
+
# (This is different than simply the username when usejournal is specified.)
|
137
|
+
def journal
|
138
|
+
usejournal || username
|
122
139
|
end
|
123
|
-
|
124
|
-
# Turn tracing on.
|
140
|
+
|
141
|
+
# Turn tracing on. Mostly useful for debugging.
|
125
142
|
def trace!
|
126
143
|
@db.trace() do |data, sql|
|
127
144
|
puts "SQL> #{sql.inspect}"
|
@@ -133,23 +150,24 @@ module LiveJournal
|
|
133
150
|
query_entry("select * from entry where itemid=?", itemid)
|
134
151
|
end
|
135
152
|
|
136
|
-
# Given SQL that selects an entry, return that
|
153
|
+
# Given SQL that selects an entry, return that Entry.
|
137
154
|
def query_entry(sql, *sqlargs)
|
138
155
|
row = @db.get_first_row(sql, *sqlargs)
|
139
156
|
return Entry.new.load_from_database_row(row)
|
140
157
|
end
|
141
158
|
|
142
|
-
# Given SQL that selects some entries, yield each
|
143
|
-
def query_entries(sql, *sqlargs)
|
159
|
+
# Given SQL that selects some entries, yield each Entry.
|
160
|
+
def query_entries(sql, *sqlargs) # :yields: entry
|
144
161
|
@db.execute(sql, *sqlargs) do |row|
|
145
162
|
yield Entry.new.load_from_database_row(row)
|
146
163
|
end
|
147
164
|
end
|
148
165
|
|
149
|
-
# Yield a set of entries.
|
166
|
+
# Yield a set of entries, ordered by ascending itemid (first to last).
|
150
167
|
def each_entry(where=nil, &block)
|
151
|
-
sql = 'SELECT * FROM entry
|
168
|
+
sql = 'SELECT * FROM entry'
|
152
169
|
sql += " WHERE #{where}" if where
|
170
|
+
sql += ' ORDER BY itemid ASC'
|
153
171
|
query_entries sql, &block
|
154
172
|
end
|
155
173
|
|
@@ -158,22 +176,47 @@ module LiveJournal
|
|
158
176
|
@db.get_first_value('SELECT COUNT(*) FROM entry').to_i
|
159
177
|
end
|
160
178
|
|
179
|
+
# Store an Entry.
|
161
180
|
def store_entry entry
|
162
181
|
sql = 'INSERT OR REPLACE INTO entry VALUES (' + ("?, " * 16) + '?)'
|
163
182
|
@db.execute(sql, *entry.to_database_row)
|
164
183
|
end
|
165
184
|
|
185
|
+
# Used for Sync::Comments.
|
166
186
|
def last_comment_meta
|
167
187
|
Database::optional_to_i(
|
168
188
|
@db.get_first_value('SELECT MAX(commentid) FROM comment'))
|
169
189
|
end
|
190
|
+
# Used for Sync::Comments.
|
170
191
|
def last_comment_full
|
171
192
|
Database::optional_to_i(
|
172
193
|
@db.get_first_value('SELECT MAX(commentid) FROM comment ' +
|
173
194
|
'WHERE body IS NOT NULL'))
|
174
195
|
end
|
175
196
|
|
176
|
-
|
197
|
+
# Used for Sync::Comments.
|
198
|
+
def store_comments_meta(comments)
|
199
|
+
store_comments(comments, true)
|
200
|
+
end
|
201
|
+
# Used for Sync::Comments.
|
202
|
+
def store_comments_full(comments)
|
203
|
+
store_comments(comments, false)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Used for Sync::Comments.
|
207
|
+
def store_usermap(usermap)
|
208
|
+
transaction do
|
209
|
+
sql = "INSERT OR REPLACE INTO users VALUES (?, ?)"
|
210
|
+
@db.prepare(sql) do |stmt|
|
211
|
+
usermap.each do |id, user|
|
212
|
+
stmt.execute(id, user)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
def store_comments(comments, meta_only=true)
|
177
220
|
transaction do
|
178
221
|
sql = "INSERT OR REPLACE INTO comment "
|
179
222
|
if meta_only
|
@@ -193,33 +236,6 @@ module LiveJournal
|
|
193
236
|
end
|
194
237
|
end
|
195
238
|
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
239
|
end
|
224
240
|
|
225
241
|
class Entry
|
data/lib/livejournal/entry.rb
CHANGED
@@ -164,8 +164,16 @@ module LiveJournal
|
|
164
164
|
(@itemid << 8) + @anum
|
165
165
|
end
|
166
166
|
|
167
|
-
|
168
|
-
|
167
|
+
def url(user)
|
168
|
+
raise UnimplementedError, "only works for lj.com" unless user.server == LiveJournal::DEFAULT_SERVER
|
169
|
+
journal = user.journal.gsub(/_/, '-')
|
170
|
+
"http://#{journal}.livejournal.com/#{display_itemid}.html"
|
171
|
+
end
|
172
|
+
|
173
|
+
# Render LJ markup to an HTML simulation of what is displayed on LJ
|
174
|
+
# itself. (XXX this needs some work: polls, better preformatting, etc.)
|
175
|
+
#
|
176
|
+
# (The server to use is necessary for rendering links to other LJ users.)
|
169
177
|
def event_as_html server=LiveJournal::DEFAULT_SERVER
|
170
178
|
# I'd like to use REXML but the content isn't XML, so REs it is!
|
171
179
|
html = @event.dup
|
@@ -233,6 +241,9 @@ module LiveJournal
|
|
233
241
|
@entry = entry
|
234
242
|
end
|
235
243
|
|
244
|
+
# Post an #Entry as a new post. Fills in the <tt>itemid</tt> and
|
245
|
+
# <tt>anum</tt> fields on the #Entry, which are necessary for
|
246
|
+
# Entry#display_itemid and Entry#url.
|
236
247
|
def run
|
237
248
|
super
|
238
249
|
@entry.itemid = @result['itemid'].to_i
|
@@ -263,8 +274,8 @@ module LiveJournal
|
|
263
274
|
end
|
264
275
|
end
|
265
276
|
|
266
|
-
# Returns either a single #Entry or
|
267
|
-
# the mode this was constructed with.
|
277
|
+
# Returns either a single #Entry or a hash of itemid => #Entry, depending
|
278
|
+
# on the mode this was constructed with.
|
268
279
|
def run
|
269
280
|
super
|
270
281
|
|
@@ -288,6 +299,15 @@ module LiveJournal
|
|
288
299
|
end
|
289
300
|
|
290
301
|
class EditEvent < Req
|
302
|
+
# To edit an entry, pass in a #User and an #Entry to this and run it.
|
303
|
+
# To delete an entry, pass in <tt>:delete => true</tt> as the third
|
304
|
+
# parameter. (In this case, the Entry object only needs its
|
305
|
+
# <tt>itemid</tt> filled in.)
|
306
|
+
#
|
307
|
+
# The LiveJournal API for deletion is to "edit" an entry to have an
|
308
|
+
# empty event. To prevent accidentally deleting entries, if you pass
|
309
|
+
# in an entry with an empty event without passing the delete flag, this
|
310
|
+
# will raise the AccidentalDeleteError exception.
|
291
311
|
def initialize(user, entry, opts={})
|
292
312
|
super(user, 'editevent')
|
293
313
|
|
data/lib/livejournal/friends.rb
CHANGED
@@ -80,8 +80,21 @@ module LiveJournal
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
# An example of polling for friends list updates.
|
84
|
+
# req = LiveJournal::Request::CheckFriends.new(user)
|
85
|
+
# req.run # always will return false on the first run.
|
86
|
+
# loop do
|
87
|
+
# puts "Waiting for new entries..."
|
88
|
+
# sleep req.interval # uses the server-recommended sleep time.
|
89
|
+
# break if req.run == true
|
90
|
+
# end
|
91
|
+
# puts "#{user.username}'s friends list has been updated!"
|
83
92
|
class CheckFriends < Req
|
93
|
+
# The server-recommended number of seconds to wait between running this.
|
84
94
|
attr_reader :interval
|
95
|
+
# If you want to keep your CheckFriends state without saving the object,
|
96
|
+
# save the #lastupdate field and pass it to a new object.
|
97
|
+
attr_reader :lastupdate
|
85
98
|
def initialize(user, lastupdate=nil)
|
86
99
|
super(user, 'checkfriends')
|
87
100
|
@lastupdate = lastupdate
|
@@ -92,7 +105,7 @@ module LiveJournal
|
|
92
105
|
@request['lastupdate'] = @lastupdate if @lastupdate
|
93
106
|
super
|
94
107
|
@lastupdate = @result['lastupdate']
|
95
|
-
@interval = @result['interval']
|
108
|
+
@interval = @result['interval'].to_i
|
96
109
|
@result['new'] == '1'
|
97
110
|
end
|
98
111
|
end
|
data/lib/livejournal/login.rb
CHANGED
@@ -30,6 +30,8 @@ module LiveJournal
|
|
30
30
|
def initialize(user)
|
31
31
|
super(user, 'login')
|
32
32
|
end
|
33
|
+
# Fills in the <tt>fullname</tt> of the #User this was created with.
|
34
|
+
# (XXX this sould be updated to also get the list of communities, etc.)
|
33
35
|
def run
|
34
36
|
super
|
35
37
|
u = @user # should we clone here?
|
data/lib/livejournal/logjam.rb
CHANGED
@@ -23,10 +23,12 @@
|
|
23
23
|
#++
|
24
24
|
#
|
25
25
|
# This module extends the LiveJournal module to work with LogJam's data.
|
26
|
+
# XXX this is currently not working due to database schema divergence
|
26
27
|
|
27
28
|
require 'rexml/document' # parsing logjam conf
|
28
29
|
|
29
30
|
module LiveJournal
|
31
|
+
# XXX this is currently not working due to database schema divergence
|
30
32
|
module LogJam
|
31
33
|
# Path to LogJam data.
|
32
34
|
def self.logjam_path
|
data/lib/livejournal/request.rb
CHANGED
@@ -121,10 +121,13 @@ module LiveJournal
|
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
124
|
+
# Used for LiveJournal's challenge-response based authentication,
|
125
|
+
# and used by ljrb for all requests.
|
124
126
|
class GetChallenge < Req
|
125
127
|
def initialize
|
126
128
|
super(nil, 'getchallenge')
|
127
129
|
end
|
130
|
+
# Returns the challenge.
|
128
131
|
def run
|
129
132
|
super
|
130
133
|
return @result['challenge']
|
data/lib/livejournal/sync.rb
CHANGED
@@ -68,10 +68,13 @@ module LiveJournal
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
# This is only used for generating sessions used for syncing comments.
|
72
|
+
# It is used by ljrb internally.
|
71
73
|
class SessionGenerate < Req
|
72
74
|
def initialize(user)
|
73
75
|
super(user, 'sessiongenerate')
|
74
76
|
end
|
77
|
+
# Returns the LJ session.
|
75
78
|
def run
|
76
79
|
super
|
77
80
|
@result['ljsession']
|
data/sample/export
CHANGED
@@ -3,47 +3,9 @@
|
|
3
3
|
require 'livejournal/sync'
|
4
4
|
require 'livejournal/database'
|
5
5
|
require 'optparse'
|
6
|
+
require 'progressbar'
|
6
7
|
|
7
|
-
|
8
|
-
def initialize(caption)
|
9
|
-
@cur = 0
|
10
|
-
@width = 40
|
11
|
-
print caption
|
12
|
-
print("[" + " "*40 + "]" + "\x08"*41)
|
13
|
-
$stdout.flush
|
14
|
-
end
|
15
|
-
|
16
|
-
def fill_to(pos)
|
17
|
-
dots = pos - @cur
|
18
|
-
print "."*dots
|
19
|
-
$stdout.flush
|
20
|
-
@cur = pos
|
21
|
-
end
|
22
|
-
|
23
|
-
def update(cur, max)
|
24
|
-
fill_to(@width*cur/max)
|
25
|
-
end
|
26
|
-
|
27
|
-
def finish(error=false)
|
28
|
-
return unless @cur >= 0
|
29
|
-
fill_to(@width) unless error
|
30
|
-
puts
|
31
|
-
@cur = -1
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.with_progress(caption)
|
35
|
-
bar = ProgressBar.new(caption)
|
36
|
-
begin
|
37
|
-
yield bar
|
38
|
-
rescue => e
|
39
|
-
bar.finish(true)
|
40
|
-
raise e
|
41
|
-
else
|
42
|
-
bar.finish(false)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
8
|
+
create = false
|
47
9
|
username = nil
|
48
10
|
password = nil
|
49
11
|
usejournal = nil
|
@@ -51,6 +13,8 @@ dbfile = nil
|
|
51
13
|
get_entries = true
|
52
14
|
get_comments = true
|
53
15
|
opts = OptionParser.new do |opts|
|
16
|
+
opts.on('-c', '--create',
|
17
|
+
'Create a new database if necessary') { |create| }
|
54
18
|
opts.on('-u', '--user USERNAME',
|
55
19
|
'Login username') { |username| }
|
56
20
|
opts.on('-p', '--password PASSWORD',
|
@@ -66,28 +30,54 @@ opts = OptionParser.new do |opts|
|
|
66
30
|
end
|
67
31
|
opts.parse!(ARGV)
|
68
32
|
|
69
|
-
unless
|
33
|
+
unless dbfile
|
70
34
|
puts opts
|
71
|
-
puts "ERROR: Must specify
|
35
|
+
puts "ERROR: Must specify database file."
|
72
36
|
exit 1
|
73
37
|
end
|
74
38
|
|
75
|
-
|
39
|
+
begin
|
40
|
+
db = LiveJournal::Database.new(dbfile, create)
|
41
|
+
rescue Errno::ENOENT
|
42
|
+
puts "Use the --create flag to create a new database."
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
username ||= db.username
|
46
|
+
usejournal ||= db.usejournal
|
47
|
+
|
48
|
+
unless username
|
76
49
|
puts opts
|
77
|
-
puts "ERROR: Must specify
|
50
|
+
puts "ERROR: Must specify username."
|
78
51
|
exit 1
|
79
52
|
end
|
80
53
|
|
54
|
+
if usejournal
|
55
|
+
puts "Journal: #{usejournal} (syncing as #{username})."
|
56
|
+
else
|
57
|
+
puts "Journal: #{username}."
|
58
|
+
end
|
59
|
+
|
81
60
|
unless password
|
82
|
-
|
61
|
+
noecho = system('stty -echo')
|
62
|
+
print "Enter password"
|
63
|
+
print "(WARNING: echoed to screen)" unless noecho
|
64
|
+
print ": "
|
83
65
|
$stdout.flush
|
84
|
-
|
66
|
+
|
67
|
+
begin
|
68
|
+
password = gets.strip
|
69
|
+
ensure
|
70
|
+
if noecho
|
71
|
+
system('stty sane')
|
72
|
+
puts # since the user input didn't add the newline for us...
|
73
|
+
end
|
74
|
+
end
|
85
75
|
end
|
86
76
|
|
87
77
|
user = LiveJournal::User.new(username, password)
|
88
78
|
user.usejournal = usejournal
|
89
|
-
db =
|
90
|
-
db.
|
79
|
+
db.username = user.username
|
80
|
+
db.usejournal = usejournal if usejournal
|
91
81
|
|
92
82
|
if get_entries
|
93
83
|
puts "Fetching entries..."
|
data/sample/fuse
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
#!/usr/bin/ruby -I../lib
|
2
|
+
|
3
|
+
require 'fusefs'
|
4
|
+
require 'livejournal/database'
|
5
|
+
require 'livejournal/entry'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
class DBQuery
|
9
|
+
def initialize
|
10
|
+
@db = $db
|
11
|
+
end
|
12
|
+
def has_any?(where, *args)
|
13
|
+
exists = @db.db.get_first_value("SELECT itemid #{where} LIMIT 1", *args)
|
14
|
+
exists != nil
|
15
|
+
end
|
16
|
+
def has_year? year
|
17
|
+
has_any?("FROM entry WHERE year=?", year)
|
18
|
+
end
|
19
|
+
def has_month? year, month
|
20
|
+
has_any?("FROM entry WHERE year=? AND month=?", year, month)
|
21
|
+
end
|
22
|
+
def has_day? year, month, day
|
23
|
+
has_any?("FROM entry WHERE year=? AND month=? AND day=?", year, month, day)
|
24
|
+
end
|
25
|
+
def has_entry? itemid
|
26
|
+
has_any?("FROM entry WHERE itemid=?", itemid)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_array(sql, *args)
|
30
|
+
array = []
|
31
|
+
@db.db.execute("SELECT DISTINCT #{sql}", *args) do |row|
|
32
|
+
array << row[0]
|
33
|
+
end
|
34
|
+
array
|
35
|
+
end
|
36
|
+
|
37
|
+
def years
|
38
|
+
get_array('year FROM entry')
|
39
|
+
end
|
40
|
+
def months year
|
41
|
+
get_array('month FROM entry WHERE year=?', year).map { |m| "%02d" % m }
|
42
|
+
end
|
43
|
+
def days year, month
|
44
|
+
get_array('day FROM entry WHERE year=? AND month=?', year, month).map { |d| "%02d" % d }
|
45
|
+
end
|
46
|
+
|
47
|
+
def day_entries year, month, day
|
48
|
+
get_array('itemid FROM entry WHERE year=? AND month=? AND day=?',
|
49
|
+
year, month, day)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Dispatcher < FuseFS::FuseDir
|
54
|
+
def initialize
|
55
|
+
@matches = []
|
56
|
+
end
|
57
|
+
def register(match, target)
|
58
|
+
@matches << [match, target]
|
59
|
+
end
|
60
|
+
|
61
|
+
def dispatch(sym, path, *args)
|
62
|
+
path, rest = split_path path if path
|
63
|
+
if path
|
64
|
+
@matches.each do |match, target|
|
65
|
+
if path =~ match
|
66
|
+
return target.dispatch(sym, rest, *(args+[path]))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# otherwise, dispatch it to the current object
|
72
|
+
begin
|
73
|
+
s = self.send(sym, path, *args)
|
74
|
+
rescue
|
75
|
+
p $!
|
76
|
+
end
|
77
|
+
return s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class DispatchDir < FuseFS::FuseDir
|
82
|
+
def initialize dispatcher
|
83
|
+
@dispatcher = dispatcher
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.add_dispatch(*args)
|
87
|
+
args.each do |sym|
|
88
|
+
class_eval %{def #{sym}(path, *args)
|
89
|
+
@dispatcher.dispatch(#{sym.inspect}, path, *args)
|
90
|
+
end}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
add_dispatch :directory?, :file?
|
95
|
+
add_dispatch :contents, :read_file
|
96
|
+
end
|
97
|
+
|
98
|
+
class LJFS < Dispatcher
|
99
|
+
class Day < Dispatcher
|
100
|
+
def contents path, year, mon, day
|
101
|
+
entries = $dbq.day_entries year, mon, day
|
102
|
+
entries.map do |itemid|
|
103
|
+
"#{itemid}.txt"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
def directory? path, year, mon, day
|
107
|
+
path == nil and $dbq.has_day? year, mon, day
|
108
|
+
end
|
109
|
+
def file? path, year, mon, day
|
110
|
+
return false unless $dbq.has_day? year, mon, day
|
111
|
+
return false unless path =~ /^(\d+)\.txt$/
|
112
|
+
itemid = $1
|
113
|
+
return false unless $dbq.has_entry? itemid
|
114
|
+
true
|
115
|
+
end
|
116
|
+
def read_file path, year, mon, day
|
117
|
+
return false unless $dbq.has_day? year, mon, day
|
118
|
+
return false unless path =~ /^(\d+)\.txt$/
|
119
|
+
itemid = $1
|
120
|
+
return false unless $dbq.has_entry? itemid
|
121
|
+
entry = $db.get_entry itemid
|
122
|
+
out = StringIO.new
|
123
|
+
if entry.subject
|
124
|
+
out.puts entry.subject
|
125
|
+
out.puts("=" * entry.subject.length)
|
126
|
+
out.puts
|
127
|
+
end
|
128
|
+
out.puts entry.event
|
129
|
+
out.rewind
|
130
|
+
return out.read
|
131
|
+
end
|
132
|
+
end
|
133
|
+
class Month < Dispatcher
|
134
|
+
def initialize
|
135
|
+
super
|
136
|
+
register(/^\d{2}/, Day.new)
|
137
|
+
end
|
138
|
+
def directory? path, year, mon
|
139
|
+
path == nil and $dbq.has_month? year, mon
|
140
|
+
end
|
141
|
+
def file? path, year, mon
|
142
|
+
false
|
143
|
+
end
|
144
|
+
def contents path, year, mon
|
145
|
+
$dbq.days year, mon
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Year < Dispatcher
|
150
|
+
def initialize
|
151
|
+
super
|
152
|
+
register(/^\d{2}/, Month.new)
|
153
|
+
end
|
154
|
+
def directory? path, year
|
155
|
+
path == nil and $dbq.has_year? year
|
156
|
+
end
|
157
|
+
def file? path, year
|
158
|
+
false
|
159
|
+
end
|
160
|
+
def contents path, year
|
161
|
+
$dbq.months year
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize
|
166
|
+
super
|
167
|
+
register(/^\d{4}$/, Year.new)
|
168
|
+
end
|
169
|
+
|
170
|
+
def directory? path
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
def file? path
|
174
|
+
return path == 'username'
|
175
|
+
end
|
176
|
+
def read_file path
|
177
|
+
return $db.username
|
178
|
+
end
|
179
|
+
def contents *args
|
180
|
+
return ['username'] + $dbq.years
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
unless ARGV.length == 2
|
185
|
+
puts "usage: #{$0} dbfile mountpoint"
|
186
|
+
exit 1
|
187
|
+
end
|
188
|
+
dbfile, mountpoint = ARGV
|
189
|
+
|
190
|
+
$db = LiveJournal::Database.new dbfile
|
191
|
+
$dbq = DBQuery.new # temp hack
|
192
|
+
root = DispatchDir.new LJFS.new
|
193
|
+
|
194
|
+
FuseFS.set_root root
|
195
|
+
FuseFS.mount_under mountpoint
|
196
|
+
FuseFS.run
|
197
|
+
|
198
|
+
# vim: ts=2 sw=2 et :
|
data/sample/graph
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
class ProgressBar
|
2
|
+
def initialize(caption)
|
3
|
+
@cur = 0
|
4
|
+
@width = 40
|
5
|
+
print caption
|
6
|
+
print("[" + " "*40 + "]" + "\x08"*41)
|
7
|
+
$stdout.flush
|
8
|
+
end
|
9
|
+
|
10
|
+
def fill_to(pos)
|
11
|
+
dots = pos - @cur
|
12
|
+
print "."*dots
|
13
|
+
$stdout.flush
|
14
|
+
@cur = pos
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(cur, max)
|
18
|
+
fill_to(@width*cur/max)
|
19
|
+
end
|
20
|
+
|
21
|
+
def finish(error=false)
|
22
|
+
return unless @cur >= 0
|
23
|
+
fill_to(@width) unless error
|
24
|
+
puts
|
25
|
+
@cur = -1
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.with_progress(caption)
|
29
|
+
bar = ProgressBar.new(caption)
|
30
|
+
begin
|
31
|
+
yield bar
|
32
|
+
rescue => e
|
33
|
+
bar.finish(true)
|
34
|
+
raise e
|
35
|
+
else
|
36
|
+
bar.finish(false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# vim: ts=2 sw=2 et :
|
metadata
CHANGED
@@ -3,60 +3,67 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: livejournal
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2006-05-13 00:00:00 +09:00
|
8
8
|
summary: module for interacting with livejournal
|
9
9
|
require_paths:
|
10
|
-
|
10
|
+
- lib
|
11
11
|
email: martine@danga.com
|
12
12
|
homepage: http://neugierig.org/software/livejournal/ruby/
|
13
13
|
rubyforge_project:
|
14
|
-
description:
|
15
|
-
sync journal to an offline database."
|
14
|
+
description: LiveJournal module. Post to livejournal, retrieve friends lists, edit entries, sync journal to an offline database.
|
16
15
|
autorequire:
|
17
16
|
default_executable:
|
18
17
|
bindir: bin
|
19
18
|
has_rdoc: true
|
20
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
20
|
requirements:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
version: 0.0.0
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
26
24
|
version:
|
27
25
|
platform: ruby
|
28
26
|
signing_key:
|
29
27
|
cert_chain:
|
30
28
|
authors:
|
31
|
-
|
29
|
+
- Evan Martin
|
32
30
|
files:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
31
|
+
- Rakefile
|
32
|
+
- README
|
33
|
+
- LICENSE
|
34
|
+
- setup.rb
|
35
|
+
- lib/livejournal
|
36
|
+
- lib/livejournal/database.rb
|
37
|
+
- lib/livejournal/logjam.rb
|
38
|
+
- lib/livejournal/sync.rb
|
39
|
+
- lib/livejournal/request.rb
|
40
|
+
- lib/livejournal/entry.rb
|
41
|
+
- lib/livejournal/comments-xml.rb
|
42
|
+
- lib/livejournal/login.rb
|
43
|
+
- lib/livejournal/comment.rb
|
44
|
+
- lib/livejournal/friends.rb
|
45
|
+
- lib/livejournal/basic.rb
|
46
|
+
- sample/fuse
|
47
|
+
- sample/progressbar.rb
|
48
|
+
- sample/graph
|
49
|
+
- sample/export
|
50
|
+
- test/database.rb
|
51
|
+
- test/time.rb
|
52
|
+
- test/checkfriends.rb
|
53
|
+
- test/roundtrip.rb
|
54
|
+
- test/comments-xml.rb
|
55
|
+
- test/login.rb
|
56
56
|
test_files: []
|
57
|
+
|
57
58
|
rdoc_options: []
|
59
|
+
|
58
60
|
extra_rdoc_files: []
|
61
|
+
|
59
62
|
executables: []
|
63
|
+
|
60
64
|
extensions: []
|
65
|
+
|
61
66
|
requirements: []
|
62
|
-
|
67
|
+
|
68
|
+
dependencies: []
|
69
|
+
|