livejournal 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|