athenaeum 1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +3 -2
- data/bin/athenaeumd +254 -0
- data/lib/athenaeum.rb +2 -2
- data/lib/athenaeum/item.rb +17 -2
- metadata +4 -1
data/Rakefile
CHANGED
@@ -33,7 +33,8 @@ RUBYFORGE_USER = ENV['RUBYFORGE_USER']
|
|
33
33
|
|
34
34
|
TEXT_FILES = %w( Rakefile README LICENSE )
|
35
35
|
LIB_FILES = Dir.glob('lib/**/*').delete_if { |item| item.include?( "\.svn" ) }
|
36
|
-
|
36
|
+
BIN_FILES = Dir.glob('bin/*').delete_if { |item| item.include?( "\.svn" ) }
|
37
|
+
RELEASE_FILES = TEXT_FILES + LIB_FILES + BIN_FILES
|
37
38
|
|
38
39
|
task :default => [ :clean ]
|
39
40
|
|
@@ -116,7 +117,7 @@ spec = Gem::Specification.new do |s|
|
|
116
117
|
s.has_rdoc = true
|
117
118
|
|
118
119
|
s.files = RELEASE_FILES
|
119
|
-
s.executables = '
|
120
|
+
s.executables = BIN_FILES.map {|file| file.sub( /^bin\//, '' ) }
|
120
121
|
|
121
122
|
s.autorequire = 'athenaeum'
|
122
123
|
end
|
data/bin/athenaeumd
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'athenaeum'
|
7
|
+
require 'mongrel'
|
8
|
+
|
9
|
+
opts = {
|
10
|
+
:port => 3030,
|
11
|
+
:library => "#{ENV[ 'HOME' ]}/Library/Application\ Support/Delicious\ Library"
|
12
|
+
}
|
13
|
+
|
14
|
+
OptionParser.new do |options|
|
15
|
+
options.banner = "Usage: #{File.basename( $0 )} [options]"
|
16
|
+
|
17
|
+
options.on( "-p PORT", "--port PORT", "Specify port to bind to. Defaults to 3030." ) do |p|
|
18
|
+
opts[ :port ] = p
|
19
|
+
end
|
20
|
+
|
21
|
+
options.on( "-l PATH", "--library PATH", "Specify root directory of the Delicious Library support directory. Defaults to ~/Library/Application Support/Delicious Library" ) do |l|
|
22
|
+
opts[ :library ] = l
|
23
|
+
end
|
24
|
+
end.parse!
|
25
|
+
|
26
|
+
########################################################################
|
27
|
+
### M O N G R E L H A N D L E R S
|
28
|
+
########################################################################
|
29
|
+
|
30
|
+
class ItemListHandler < Mongrel::HttpHandler
|
31
|
+
def initialize( path )
|
32
|
+
@path = path
|
33
|
+
end
|
34
|
+
|
35
|
+
def process( req, resp )
|
36
|
+
rescan_library!
|
37
|
+
|
38
|
+
resp.start do |head,out|
|
39
|
+
out << header
|
40
|
+
|
41
|
+
Athenaeum::Item.types.sort_by {|k| k.type_name}.each do |type|
|
42
|
+
items = Athenaeum::Item.find_by_type( type )
|
43
|
+
next if items.empty?
|
44
|
+
|
45
|
+
type_name = type.type_name
|
46
|
+
type_name << 's' unless type_name == 'Music'
|
47
|
+
|
48
|
+
out << "<div class='item_section'>"
|
49
|
+
out << "<h1>#{type_name}</h1>"
|
50
|
+
|
51
|
+
# get the items of this type, sorted by title
|
52
|
+
items.sort_by {|item| item.title }.each do |item|
|
53
|
+
out << "<div class='item'>"
|
54
|
+
out << item.to_html
|
55
|
+
|
56
|
+
if loan = Athenaeum::Loan.find_by_item_uuid( item.uuid )
|
57
|
+
due_date = loan.due_date.strftime( '%B %d, %Y' )
|
58
|
+
|
59
|
+
out << "<div class='loan'>loaned to #{loan.borrower.to_html}, due on #{due_date}</div>"
|
60
|
+
end
|
61
|
+
|
62
|
+
out << "</div>"
|
63
|
+
end
|
64
|
+
|
65
|
+
out << "<div class='clear'></div>"
|
66
|
+
out << "</div>"
|
67
|
+
end
|
68
|
+
|
69
|
+
out << footer
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# checks to see if we have a current snapshot of the library and makes a new
|
74
|
+
# one if not
|
75
|
+
def rescan_library!
|
76
|
+
path = "#{@path}/Library\ Media\ Data.xml"
|
77
|
+
file = File.open( path )
|
78
|
+
|
79
|
+
@timestamp ||= nil
|
80
|
+
@file_size ||= nil
|
81
|
+
@md5_hash ||= nil
|
82
|
+
|
83
|
+
current_hash = `md5 '#{path}'`.split.last
|
84
|
+
|
85
|
+
file_changed = false
|
86
|
+
file_changed = true unless @timestamp == file.stat.ctime and
|
87
|
+
@file_size == file.stat.size and
|
88
|
+
@md5_hash == current_hash
|
89
|
+
|
90
|
+
if file_changed
|
91
|
+
log "file changed. Rescanning..."
|
92
|
+
|
93
|
+
Athenaeum::Item.registry.clear
|
94
|
+
Athenaeum::Loan.registry.clear
|
95
|
+
|
96
|
+
doc = Hpricot.XML( file.read )
|
97
|
+
|
98
|
+
# this query will get us all the top-level books, movies, music, and games
|
99
|
+
#from the item container. This prevents us from accidentally grabbing the
|
100
|
+
# recommendations and related items.
|
101
|
+
doc.at( "/library/items" ).search( "/book | /movie | /music | /game" ) do |item|
|
102
|
+
Athenaeum::Item.create( item )
|
103
|
+
end
|
104
|
+
|
105
|
+
# fetches all the borrowers from the xml, creating Athenaeum::Borrower objects
|
106
|
+
# for each. This must happen *after* the Items are found, because
|
107
|
+
# Athenaeum::Borrower uses Athenaeum::Item's registry to build its internal
|
108
|
+
# registry.
|
109
|
+
doc.search( "/library/borrowers/borrower" ).each do |b|
|
110
|
+
Athenaeum::Borrower.new( b )
|
111
|
+
end
|
112
|
+
|
113
|
+
@timestamp = file.stat.ctime
|
114
|
+
@file_size = file.stat.size
|
115
|
+
@md5_hash = current_hash
|
116
|
+
end
|
117
|
+
|
118
|
+
file.close
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class CSSHandler < Mongrel::HttpHandler
|
123
|
+
def process( req, resp )
|
124
|
+
resp.start do |head,out|
|
125
|
+
out << css
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
########################################################################
|
131
|
+
### T E M P L A T E S
|
132
|
+
########################################################################
|
133
|
+
|
134
|
+
def css
|
135
|
+
return <<-HEREDOC
|
136
|
+
body {
|
137
|
+
background: #3a3a2b;
|
138
|
+
margin: 1em;
|
139
|
+
|
140
|
+
font-family: sans-serif;
|
141
|
+
font-size: 0.74em;
|
142
|
+
}
|
143
|
+
|
144
|
+
a {
|
145
|
+
color: #980008;
|
146
|
+
text-decoration: none;
|
147
|
+
}
|
148
|
+
|
149
|
+
a:hover { text-decoration: underline; }
|
150
|
+
|
151
|
+
h1.title, h1.title a, div#footer a { color: #a6d854; }
|
152
|
+
|
153
|
+
div.item_section {
|
154
|
+
border: 1px solid #3a3a2b;
|
155
|
+
background: #faf8f1;
|
156
|
+
|
157
|
+
padding: 0.75em 1em;
|
158
|
+
|
159
|
+
margin-top: 2em;
|
160
|
+
margin-bottom: 1em;
|
161
|
+
}
|
162
|
+
|
163
|
+
div.item_section h1 { margin: 0; }
|
164
|
+
span.creator { font-style: italic; }
|
165
|
+
|
166
|
+
span.title {
|
167
|
+
font-size: 1.2em;
|
168
|
+
font-weight: bold;
|
169
|
+
}
|
170
|
+
|
171
|
+
div.publish_date {
|
172
|
+
padding-top: 2px;
|
173
|
+
font-size: 0.8em;
|
174
|
+
color: #616253;
|
175
|
+
}
|
176
|
+
|
177
|
+
div.item {
|
178
|
+
width: 45%;
|
179
|
+
min-height: 4em;
|
180
|
+
margin: 1em 2.5% auto 2.5%;
|
181
|
+
float:left;
|
182
|
+
}
|
183
|
+
|
184
|
+
div.loan {
|
185
|
+
margin-left: 1.5em;
|
186
|
+
margin-top: 4px;
|
187
|
+
}
|
188
|
+
|
189
|
+
div.clear { clear: both; }
|
190
|
+
|
191
|
+
div#footer {
|
192
|
+
color: #faf8f1;
|
193
|
+
font-size: 0.9em;
|
194
|
+
text-align: center;
|
195
|
+
line-height: 1.4em;
|
196
|
+
}
|
197
|
+
HEREDOC
|
198
|
+
end
|
199
|
+
|
200
|
+
def header
|
201
|
+
return <<-HEREDOC
|
202
|
+
<html>
|
203
|
+
<head>
|
204
|
+
<title>Delicious Library Contents</title>
|
205
|
+
<link rel="stylesheet" type="text/css" href="athenaeum.css">
|
206
|
+
</head>
|
207
|
+
<body>
|
208
|
+
<h1 class="title">Athenaeum — Your <a href="http://www.delicious-monster.com">Delicious Library</a> on the web!</h1>
|
209
|
+
HEREDOC
|
210
|
+
end
|
211
|
+
|
212
|
+
def footer
|
213
|
+
return <<-HEREDOC
|
214
|
+
<div id="footer">
|
215
|
+
<div id="timestamp">updated at #{@timestamp}</div>
|
216
|
+
<div id="copyright">Athenaeum is copyright © 2007, <a href="http://www.laika.com">LAIKA, Inc</a>.</div>
|
217
|
+
</div>
|
218
|
+
</body>
|
219
|
+
</html>
|
220
|
+
HEREDOC
|
221
|
+
end
|
222
|
+
|
223
|
+
########################################################################
|
224
|
+
### H E L P E R S
|
225
|
+
########################################################################
|
226
|
+
|
227
|
+
def log( str )
|
228
|
+
puts "[#{Time.now.strftime "%c"}] #{str}"
|
229
|
+
end
|
230
|
+
|
231
|
+
########################################################################
|
232
|
+
### M O N G R E L I T U P ! ! ! 1
|
233
|
+
########################################################################
|
234
|
+
|
235
|
+
config = Mongrel::Configurator.new :port => opts[ :port ] do
|
236
|
+
listener do
|
237
|
+
uri "/", :handler => ItemListHandler.new( opts[ :library ] )
|
238
|
+
uri "/athenaeum.css", :handler => CSSHandler.new
|
239
|
+
end
|
240
|
+
|
241
|
+
trap( 'INT' ) {stop}
|
242
|
+
|
243
|
+
run
|
244
|
+
end
|
245
|
+
|
246
|
+
log "Checking library..."
|
247
|
+
unless File.exists? "#{opts[ :library ]}/Library\ Media\ Data.xml"
|
248
|
+
log "Library could not be found in #{opts[ :library ]}... exiting."
|
249
|
+
config.stop ; exit
|
250
|
+
end
|
251
|
+
|
252
|
+
log "Athenaeum started. Listening on port #{opts[ :port ]}. Reading library in #{opts[ :library ]}."
|
253
|
+
|
254
|
+
config.join
|
data/lib/athenaeum.rb
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
#
|
18
18
|
# == Version
|
19
19
|
#
|
20
|
-
# $Id: athenaeum.rb
|
20
|
+
# $Id: athenaeum.rb 299 2007-08-03 22:58:19Z bbleything $
|
21
21
|
|
22
22
|
### standard library
|
23
23
|
require 'date'
|
@@ -33,7 +33,7 @@ module Athenaeum
|
|
33
33
|
NAME = "Athenaeum"
|
34
34
|
DESCRIPTION = "A small webapp to display the contents of your Delicious Library"
|
35
35
|
LONG_DESC = "Athenaeum is a collection of libraries to read and display the contents of your Delicious Library. It includes the ability to display what items are checked out, to whom, and when they are due."
|
36
|
-
VERSION = "1.0"
|
36
|
+
VERSION = "1.1.0"
|
37
37
|
end
|
38
38
|
|
39
39
|
### athenaeum
|
data/lib/athenaeum/item.rb
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
#
|
18
18
|
# == Version
|
19
19
|
#
|
20
|
-
# $Id: item.rb
|
20
|
+
# $Id: item.rb 298 2007-08-03 22:57:52Z bbleything $
|
21
21
|
|
22
22
|
class Athenaeum::Item
|
23
23
|
########################################################################
|
@@ -103,7 +103,22 @@ class Athenaeum::Item
|
|
103
103
|
|
104
104
|
# returns a decent HTML representation of the item
|
105
105
|
def to_html
|
106
|
-
|
106
|
+
out = []
|
107
|
+
out << "<span class='title'>#{self.title}</span>"
|
108
|
+
out << "<span class='creator'>by #{self.creator}</span>"
|
109
|
+
out << "<br />"
|
110
|
+
out << "<div class='publish_date'>Publish Date: #{self.publish_date}</div>"
|
111
|
+
return out.join( "\n" )
|
107
112
|
end
|
108
113
|
|
114
|
+
def publish_date
|
115
|
+
date = self.published rescue nil
|
116
|
+
|
117
|
+
if date
|
118
|
+
date = Date.strptime( date, '%d-%m-%Y' )
|
119
|
+
return date.strftime( '%B %d, %Y' )
|
120
|
+
else
|
121
|
+
return 'Unknown'
|
122
|
+
end
|
123
|
+
end
|
109
124
|
end
|
metadata
CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: athenaeum
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
6
|
+
version: 1.1.0
|
7
7
|
date: 2007-09-10 00:00:00 -07:00
|
8
8
|
summary: Athenaeum - A small webapp to display the contents of your Delicious Library
|
9
9
|
require_paths:
|
@@ -42,6 +42,8 @@ files:
|
|
42
42
|
- lib/athenaeum/itemtypes/music.rb
|
43
43
|
- lib/athenaeum/loan.rb
|
44
44
|
- lib/athenaeum.rb
|
45
|
+
- bin/athenaeum
|
46
|
+
- bin/athenaeumd
|
45
47
|
test_files: []
|
46
48
|
|
47
49
|
rdoc_options: []
|
@@ -50,6 +52,7 @@ extra_rdoc_files: []
|
|
50
52
|
|
51
53
|
executables:
|
52
54
|
- athenaeum
|
55
|
+
- athenaeumd
|
53
56
|
extensions: []
|
54
57
|
|
55
58
|
requirements: []
|