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 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
+