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 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.1.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
 
@@ -50,6 +50,9 @@ module LiveJournal
50
50
  @usejournal = nil
51
51
  @server = server || LiveJournal::DEFAULT_SERVER
52
52
  end
53
+ def journal
54
+ @usejournal || @username
55
+ end
53
56
  def to_s
54
57
  "#{@username}: '#{@fullname}'"
55
58
  end
@@ -29,10 +29,13 @@ require 'livejournal/comment'
29
29
  require 'time' # parsing xmlschema times
30
30
 
31
31
  module LiveJournal
32
- HAVE_XML_PARSER = true
33
-
34
- require 'rexml/document'
35
- require 'xml/parser' if HAVE_XML_PARSER
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
@@ -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 = "1"
34
+ EXPECTED_DATABASE_VERSION = "2"
34
35
  SCHEMA = %q{
35
36
  CREATE TABLE meta (
36
- key STRING PRIMARY KEY,
37
- value STRING
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 STRING,
43
- event STRING,
44
- moodid INTEGER, mood STRING, music STRING, taglist STRING,
45
- pickeyword STRING, preformatted INTEGER, backdated INTEGER,
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 STRING, -- screened/deleted/active
57
- subject STRING,
58
- body STRING,
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 STRING
65
+ username TEXT
65
66
  );
66
67
  CREATE TABLE commentprop (
67
68
  commentid INTEGER, -- not primary key 'cause non-unique
68
- key STRING,
69
- value STRING
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
- def initialize(filename, create=false)
80
- exists = FileTest::exists? filename if create
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
- #trace!
95
- if create and not exists
96
- run_schema!
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 self.db_value(name, sym)
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
- def run_schema!
119
- transaction do
120
- @db.execute_batch(SCHEMA)
121
- end
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 entry.
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 entry.
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 ORDER BY itemid ASC'
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
- def _store_comments(comments, meta_only=true)
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
@@ -164,8 +164,16 @@ module LiveJournal
164
164
  (@itemid << 8) + @anum
165
165
  end
166
166
 
167
- # Render LJ markup to an HTML simulation.
168
- # (The server to use is for rendering links to other LJ users.)
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 an array of #Entry, depending on
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
 
@@ -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
@@ -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?
@@ -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
@@ -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']
@@ -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
- class ProgressBar
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 username
33
+ unless dbfile
70
34
  puts opts
71
- puts "ERROR: Must specify username."
35
+ puts "ERROR: Must specify database file."
72
36
  exit 1
73
37
  end
74
38
 
75
- unless dbfile
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 database file."
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
- print "Enter password (WARNING: echoed to screen): "
61
+ noecho = system('stty -echo')
62
+ print "Enter password"
63
+ print "(WARNING: echoed to screen)" unless noecho
64
+ print ": "
83
65
  $stdout.flush
84
- password = gets.strip
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 = LiveJournal::Database.new(dbfile, true)
90
- db.username = user.usejournal || user.username
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
@@ -206,4 +206,3 @@ end
206
206
  g.write $opts.outfile
207
207
 
208
208
  # vim: ts=2 sw=2 et :
209
-
@@ -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.1.0
7
- date: 2006-03-25 00:00:00 +09:00
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
- - lib
10
+ - lib
11
11
  email: martine@danga.com
12
12
  homepage: http://neugierig.org/software/livejournal/ruby/
13
13
  rubyforge_project:
14
- description: "LiveJournal module. Post to livejournal, retrieve friends lists, edit entries,
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
- - !ruby/object:Gem::Version
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
- - Evan Martin
29
+ - Evan Martin
32
30
  files:
33
- - Rakefile
34
- - README
35
- - LICENSE
36
- - setup.rb
37
- - lib/livejournal
38
- - lib/livejournal/database.rb
39
- - lib/livejournal/logjam.rb
40
- - lib/livejournal/sync.rb
41
- - lib/livejournal/request.rb
42
- - lib/livejournal/entry.rb
43
- - lib/livejournal/comments-xml.rb
44
- - lib/livejournal/login.rb
45
- - lib/livejournal/comment.rb
46
- - lib/livejournal/friends.rb
47
- - lib/livejournal/basic.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
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
- dependencies: []
67
+
68
+ dependencies: []
69
+