murlsh 1.0.0 → 1.1.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/.htaccess +5 -0
- data/COPYING +27 -0
- data/README.textile +2 -2
- data/Rakefile +133 -66
- data/VERSION +1 -1
- data/config.ru +9 -8
- data/config.yaml +3 -2
- data/lib/murlsh/auth.rb +6 -8
- data/lib/murlsh/config_server.rb +4 -6
- data/lib/murlsh/dispatch.rb +5 -7
- data/lib/murlsh/doc.rb +1 -1
- data/lib/murlsh/etag_add_encoding.rb +1 -3
- data/lib/murlsh/failproof.rb +0 -1
- data/lib/murlsh/far_future_expires.rb +2 -4
- data/lib/murlsh/head_from_get.rb +2 -2
- data/lib/murlsh/image_list.rb +32 -0
- data/lib/murlsh/img_store.rb +47 -9
- data/lib/murlsh/markup.rb +53 -20
- data/lib/murlsh/must_revalidate.rb +2 -4
- data/lib/murlsh/plugin.rb +1 -1
- data/lib/murlsh/sqlite3_adapter.rb +2 -4
- data/lib/murlsh/time_ago.rb +6 -8
- data/lib/murlsh/uri.rb +1 -3
- data/lib/murlsh/uri_ask.rb +23 -25
- data/lib/murlsh/url.rb +4 -6
- data/lib/murlsh/url_body.rb +19 -21
- data/lib/murlsh/url_server.rb +8 -10
- data/lib/murlsh/yaml_ordered_hash.rb +2 -4
- data/lib/murlsh.rb +21 -4
- data/murlsh.gemspec +95 -90
- data/plugins/add_post_50_update_feed.rb +22 -10
- data/plugins/add_post_50_update_podcast.rb +3 -5
- data/plugins/add_post_50_update_rss.rb +4 -6
- data/plugins/add_post_60_notify_hubs.rb +3 -5
- data/plugins/add_pre_40_convert_mobile.rb +4 -10
- data/plugins/add_pre_50_lookup_content_type_title.rb +4 -6
- data/plugins/add_pre_60_flickr.rb +3 -14
- data/plugins/add_pre_60_github_title.rb +4 -6
- data/plugins/add_pre_60_google_code_title.rb +4 -6
- data/plugins/add_pre_60_imgur.rb +4 -16
- data/plugins/add_pre_60_s3_image.rb +7 -6
- data/plugins/add_pre_60_twitter.rb +3 -14
- data/plugins/add_pre_60_vimeo.rb +7 -6
- data/plugins/add_pre_60_youtube.rb +8 -7
- data/plugins/add_pre_65_html_thumb.rb +41 -0
- data/plugins/add_pre_65_img_thumb.rb +39 -0
- data/plugins/html_parse_50_hpricot.rb +2 -4
- data/plugins/url_display_add_45_audio.rb +28 -0
- data/plugins/url_display_add_50_hostrec.rb +15 -18
- data/plugins/url_display_add_55_content_type.rb +6 -8
- data/plugins/url_display_add_60_via.rb +12 -19
- data/plugins/url_display_add_65_time.rb +4 -6
- data/public/css/screen.css +2 -3
- data/public/img/thumb/.gitignore +3 -0
- data/public/js/jquery-1.4.4.min.js +167 -0
- data/public/js/js.js +6 -5
- data/public/js/{twitter-text-1.0.3.js → twitter-text-1.0.4.js} +3 -1
- data/spec/auth_spec.rb +4 -6
- data/spec/dispatch_spec.rb +3 -5
- data/spec/doc_spec.rb +2 -4
- data/spec/img_store_spec.rb +46 -20
- data/spec/markup_spec.rb +22 -24
- data/spec/uri_ask_spec.rb +5 -7
- data/spec/uri_spec.rb +2 -4
- data/spec/url_spec.rb +5 -9
- data/spec/yaml_ordered_hash_spec.rb +1 -3
- metadata +85 -53
- data/.gitignore +0 -6
- data/plugins/add_pre_60_imageshack.rb +0 -31
- data/plugins/url_display_add_45_mp3.rb +0 -30
- data/public/img/thumb/README +0 -0
- data/public/js/jquery-1.4.3.min.js +0 -166
- data/public/swf/player_mp3_mini.swf +0 -0
data/.htaccess
CHANGED
@@ -12,3 +12,8 @@ AddOutputFilterByType DEFLATE text/html
|
|
12
12
|
Header add Expires "Wed, 22 Jun 2019 20:07:00 GMT"
|
13
13
|
FileETag None
|
14
14
|
</FilesMatch>
|
15
|
+
|
16
|
+
<FilesMatch "[\da-z]{32}\.(gif|jpe?g|png)$">
|
17
|
+
Header add Expires "Wed, 22 Jun 2019 20:07:00 GMT"
|
18
|
+
FileETag None
|
19
|
+
</FilesMatch>
|
data/COPYING
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
Copyright (c) 2010 Matthew M. Boedicker
|
2
|
+
|
3
|
+
All code is licensed under the GPLv3 with the following exceptions, which are
|
4
|
+
included third-party libraries:
|
5
|
+
|
6
|
+
- jQuery by John Resig
|
7
|
+
|
8
|
+
License: MIT or GPLv2
|
9
|
+
|
10
|
+
Files:
|
11
|
+
public/js/jquery-1.4.4.min.js
|
12
|
+
|
13
|
+
- jGrowl by Stan Lemon
|
14
|
+
|
15
|
+
License: MIT or GPLv2
|
16
|
+
|
17
|
+
Files:
|
18
|
+
public/css/jquery.jgrowl.css
|
19
|
+
public/js/jquery.jgrowl_compressed.js
|
20
|
+
|
21
|
+
- twitter-text-js by Twitter, Inc.
|
22
|
+
|
23
|
+
License: Apache 2.0
|
24
|
+
|
25
|
+
Files:
|
26
|
+
public/js/twitter-text-1.0.4.js
|
27
|
+
|
1
28
|
GNU GENERAL PUBLIC LICENSE
|
2
29
|
Version 3, 29 June 2007
|
3
30
|
|
data/README.textile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Site for sharing and archiving links.
|
2
2
|
|
3
|
-
*
|
4
|
-
*
|
3
|
+
* fetches url titles and generates thumbnails
|
4
|
+
* jGrowls embedded versions of Imageshack, Vimeo and YouTube urls
|
5
5
|
* converts Twitter status urls to their full text and adds user thumbnail
|
6
6
|
* generates Atom and RSS feeds
|
7
7
|
* regex search
|
data/Rakefile
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# optional libraries
|
19
|
-
%w{
|
20
|
-
metric_fu
|
21
|
-
}.each { |d| Murlsh::failproof { require d } }
|
3
|
+
require 'cgi'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'net/http'
|
6
|
+
require 'pp'
|
7
|
+
require 'uri'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'RMagick'
|
11
|
+
require 'sqlite3'
|
12
|
+
|
13
|
+
require 'murlsh'
|
14
|
+
|
15
|
+
def gem_not_found(gem_name)
|
16
|
+
puts "#{gem_name} not found, install it with: gem install #{gem_name}"
|
17
|
+
end
|
22
18
|
|
23
19
|
config = YAML.load_file('config.yaml')
|
24
20
|
|
@@ -37,7 +33,7 @@ end
|
|
37
33
|
desc 'Combine and compress static files.'
|
38
34
|
task :compress => %w{css:compress js:compress}
|
39
35
|
|
40
|
-
desc
|
36
|
+
desc 'Test remote content type fetch for a URL and show errors.'
|
41
37
|
task :content_type, :url do |t, args|
|
42
38
|
puts URI(args.url).extend(Murlsh::UriAsk).content_type(:failproof => false,
|
43
39
|
:debug => STDOUT)
|
@@ -53,28 +49,28 @@ namespace :db do
|
|
53
49
|
last = Murlsh::Url.find(:last, :order => 'time')
|
54
50
|
pp last
|
55
51
|
response = ask('Delete this url', '?')
|
56
|
-
last.destroy
|
52
|
+
last.destroy if %w{y yes}.include?(response.downcase)
|
57
53
|
end
|
58
54
|
|
59
|
-
desc
|
55
|
+
desc 'Check for duplicate URLs.'
|
60
56
|
task :dupcheck do
|
61
57
|
db = SQLite3::Database.new(config.fetch('db_file'))
|
62
58
|
db.results_as_hash = true
|
63
59
|
h = {}
|
64
|
-
db.execute(
|
60
|
+
db.execute('SELECT * FROM urls').each do |r|
|
65
61
|
h[r['url']] = h.fetch(r['url'], []).push([r['id'], r['time']])
|
66
62
|
end
|
67
|
-
h.
|
63
|
+
h.find_all { |k,v| v.size > 1 }.each do |k,v|
|
68
64
|
puts k
|
69
65
|
v.each { |id,time| puts " #{id} #{time}" }
|
70
66
|
end
|
71
67
|
end
|
72
68
|
|
73
|
-
desc
|
69
|
+
desc 'Create an empty database.'
|
74
70
|
task :init do
|
75
71
|
puts "creating #{config.fetch('db_file')}"
|
76
72
|
db = SQLite3::Database.new(config.fetch('db_file'))
|
77
|
-
db.execute(
|
73
|
+
db.execute('CREATE TABLE urls (
|
78
74
|
id INTEGER PRIMARY KEY,
|
79
75
|
time TIMESTAMP,
|
80
76
|
url TEXT,
|
@@ -85,7 +81,7 @@ namespace :db do
|
|
85
81
|
content_type TEXT,
|
86
82
|
via TEXT,
|
87
83
|
thumbnail_url TEXT);
|
88
|
-
|
84
|
+
')
|
89
85
|
end
|
90
86
|
|
91
87
|
desc 'Interact with the database.'
|
@@ -93,6 +89,17 @@ namespace :db do
|
|
93
89
|
exec "sqlite3 #{config['db_file']}"
|
94
90
|
end
|
95
91
|
|
92
|
+
desc 'Search urls and titles in the database.'
|
93
|
+
task :grep, :search do |t,args|
|
94
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3',
|
95
|
+
:database => config.fetch('db_file'))
|
96
|
+
|
97
|
+
Murlsh::Url.all(:conditions => [
|
98
|
+
'MURLSHMATCH(title, :search) OR MURLSHMATCH(url, :search)',
|
99
|
+
{ :search => args.search }]).each do |url|
|
100
|
+
puts "#{url.id} #{url.url} #{url.title}"
|
101
|
+
end
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
105
|
directory 'tmp'
|
@@ -106,29 +113,43 @@ namespace :passenger do
|
|
106
113
|
|
107
114
|
end
|
108
115
|
|
109
|
-
desc
|
116
|
+
desc 'Run flog on ruby and report on complexity.'
|
110
117
|
task :flog do
|
111
|
-
|
112
|
-
|
113
|
-
|
118
|
+
begin
|
119
|
+
require 'flog'
|
120
|
+
|
121
|
+
flog = Flog.new
|
122
|
+
flog.flog 'lib'
|
123
|
+
flog.report
|
124
|
+
rescue LoadError
|
125
|
+
gem_not_found 'flog'
|
126
|
+
end
|
114
127
|
end
|
115
128
|
|
116
|
-
desc
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
129
|
+
desc 'Run test suite.'
|
130
|
+
begin
|
131
|
+
require 'spec/rake/spectask'
|
132
|
+
|
133
|
+
Spec::Rake::SpecTask.new('test') do |t|
|
134
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
135
|
+
t.spec_opts = %w{--color}
|
136
|
+
# list of places to check for unicode_formatter.rb and use it if found
|
137
|
+
%w{unicode_formatter.rb}.map { |x| File.expand_path(x) }.each do |f|
|
138
|
+
if File.exists?(f)
|
139
|
+
t.spec_opts.push(*%W{--require #{f} --format UnicodeFormatter})
|
140
|
+
break
|
141
|
+
end
|
125
142
|
end
|
143
|
+
t.verbose = true
|
144
|
+
t.warning = true
|
145
|
+
end
|
146
|
+
rescue LoadError
|
147
|
+
task :test do
|
148
|
+
gem_not_found 'rspec'
|
126
149
|
end
|
127
|
-
t.verbose = true
|
128
|
-
t.warning = true
|
129
150
|
end
|
130
151
|
|
131
|
-
desc
|
152
|
+
desc 'Test remote title fetch for a URL and show errors.'
|
132
153
|
task :title, :url do |t, args|
|
133
154
|
puts URI(args.url).extend(Murlsh::UriAsk).title(:failproof => false,
|
134
155
|
:debug => STDOUT)
|
@@ -148,7 +169,7 @@ end
|
|
148
169
|
|
149
170
|
namespace :user do
|
150
171
|
|
151
|
-
desc
|
172
|
+
desc 'Add a new user.'
|
152
173
|
task :add do
|
153
174
|
puts "adding to #{config.fetch('auth_file')}"
|
154
175
|
username = ask(:username)
|
@@ -168,7 +189,7 @@ def validate(check_url, options={})
|
|
168
189
|
:validator_port => 80,
|
169
190
|
:validator_path =>
|
170
191
|
"/check?uri=#{CGI::escape(check_url)}&charset=(detect+automatically)&doctype=Inline&group=0",
|
171
|
-
}.merge
|
192
|
+
}.merge options
|
172
193
|
|
173
194
|
net_http = Net::HTTP.new(opts[:validator_host], opts[:validator_port])
|
174
195
|
# net_http.set_debug_output(STDOUT)
|
@@ -229,7 +250,7 @@ def cat(in_files, sep=nil)
|
|
229
250
|
in_files.each do |fname|
|
230
251
|
open(fname) do |h|
|
231
252
|
while (line = h.gets) do; result << line; end
|
232
|
-
result << sep
|
253
|
+
result << sep if sep
|
233
254
|
end
|
234
255
|
end
|
235
256
|
result
|
@@ -319,9 +340,49 @@ namespace :js do
|
|
319
340
|
|
320
341
|
end
|
321
342
|
|
343
|
+
namespace :thumb do
|
344
|
+
|
345
|
+
desc 'Check that local thumbnails in database are consistent with filesystem.'
|
346
|
+
task :check do
|
347
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
|
348
|
+
:database => config.fetch('db_file')
|
349
|
+
Murlsh::Url.all(
|
350
|
+
:conditions => "thumbnail_url like 'img/thumb/%'").each do |u|
|
351
|
+
identity = "url #{u.id} (#{u.url})"
|
352
|
+
|
353
|
+
path = File.join(%w{public}.concat(File.split(u.thumbnail_url)))
|
354
|
+
if File.readable?(path)
|
355
|
+
img_data = open(path) { |f| f.read }
|
356
|
+
|
357
|
+
unless img_data.empty?
|
358
|
+
img = Magick::ImageList.new.from_blob(img_data).extend(
|
359
|
+
Murlsh::ImageList)
|
360
|
+
|
361
|
+
ext = File.extname(path)
|
362
|
+
expected_ext = img.preferred_extension
|
363
|
+
if ext != expected_ext
|
364
|
+
puts "#{identity} thumbnail #{path} has an extension of '#{ext}' but is actually a '#{expected_ext}'"
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
md5 = Digest::MD5.hexdigest(img_data)
|
369
|
+
if File.basename(path, ext) != md5
|
370
|
+
puts "#{identity} thumbnail #{path} filename does not match file content md5 (#{md5})"
|
371
|
+
end
|
372
|
+
else
|
373
|
+
puts "#{identity} thumbnail #{path} is empty"
|
374
|
+
end
|
375
|
+
else
|
376
|
+
puts "#{identity} thumbnail #{path} does not exist or is not readable"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|
322
383
|
def ask(prompt, sep=':')
|
323
384
|
print "#{prompt}#{sep} "
|
324
|
-
|
385
|
+
STDIN.gets.chomp
|
325
386
|
end
|
326
387
|
|
327
388
|
begin
|
@@ -339,25 +400,31 @@ begin
|
|
339
400
|
# gemspec.cert_chain = %w{/home/mmb/src/keys/gem-public_cert.pem}
|
340
401
|
|
341
402
|
%w{
|
342
|
-
activerecord 2.3.4
|
343
|
-
bcrypt-ruby 2.1.2
|
344
|
-
builder 2.1.2
|
345
|
-
flickraw 0.8.3
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
push-notify 0.1.0
|
351
|
-
rack 1.0.0
|
352
|
-
rack-cache 0.5.2
|
353
|
-
rack-rewrite 1.0.2
|
354
|
-
rack-throttle 0.3.0
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
gemspec.add_dependency(
|
403
|
+
activerecord >= 2.3.4
|
404
|
+
bcrypt-ruby >= 2.1.2
|
405
|
+
builder >= 2.1.2
|
406
|
+
flickraw >= 0.8.3
|
407
|
+
hpricot >= 0.8.1
|
408
|
+
htmlentities >= 4.2.0
|
409
|
+
json >= 1.2.3
|
410
|
+
plumnailer >= 0.1.0
|
411
|
+
push-notify >= 0.1.0
|
412
|
+
rack >= 1.0.0
|
413
|
+
rack-cache >= 0.5.2
|
414
|
+
rack-rewrite >= 1.0.2
|
415
|
+
rack-throttle >= 0.3.0
|
416
|
+
rmagick >= 1.15.14
|
417
|
+
sqlite3-ruby >= 1.2.1
|
418
|
+
tinyatom >= 0.3.3
|
419
|
+
twitter >= 0.9.12
|
420
|
+
vimeo >= 1.2.2
|
421
|
+
}.each_slice(3) { |g,o,v| gemspec.add_dependency(g, "#{o} #{v}") }
|
422
|
+
%w{
|
423
|
+
flog >= 2.5.0
|
424
|
+
rspec ~> 1.3
|
425
|
+
}.each_slice(3) do |g,o,v|
|
426
|
+
gemspec.add_development_dependency(g, "#{o} #{v}")
|
427
|
+
end
|
361
428
|
end
|
362
429
|
rescue LoadError
|
363
430
|
puts "Jeweler not available. Install it with: gem install jeweler"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/config.ru
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
2
|
|
3
|
-
|
4
|
-
yaml
|
3
|
+
require 'yaml'
|
5
4
|
|
6
|
-
rack/cache
|
7
|
-
rack/rewrite
|
8
|
-
rack/throttle
|
5
|
+
require 'rack/cache'
|
6
|
+
require 'rack/rewrite'
|
7
|
+
require 'rack/throttle'
|
9
8
|
|
10
|
-
murlsh
|
11
|
-
}.each { |m| require m }
|
9
|
+
require 'murlsh'
|
12
10
|
|
13
11
|
config = YAML.load_file('config.yaml')
|
14
12
|
|
@@ -24,7 +22,10 @@ end
|
|
24
22
|
use Rack::ConditionalGet
|
25
23
|
use Murlsh::EtagAddEncoding
|
26
24
|
use Rack::Deflater
|
27
|
-
use Murlsh::FarFutureExpires, :patterns =>
|
25
|
+
use Murlsh::FarFutureExpires, :patterns => [
|
26
|
+
%r{[\da-z]{32}\.(?:gif|jpe?g|png)$}i,
|
27
|
+
%r{\.gen\.(css|js)$}
|
28
|
+
]
|
28
29
|
|
29
30
|
feed_url = URI.join(config.fetch('root_url'), config.fetch('feed_file'))
|
30
31
|
use Murlsh::MustRevalidate, :patterns => %r{^#{Regexp.escape(feed_url.path)}$}
|
data/config.yaml
CHANGED
@@ -12,9 +12,9 @@ feed_file: atom.atom
|
|
12
12
|
flickr_api_key:
|
13
13
|
gravatar_size: 32
|
14
14
|
js_files:
|
15
|
-
- js/jquery-1.4.
|
15
|
+
- js/jquery-1.4.4.min.js
|
16
16
|
- js/jquery.jgrowl_compressed.js
|
17
|
-
- js/twitter-text-1.0.
|
17
|
+
- js/twitter-text-1.0.4.js
|
18
18
|
- js/js.js
|
19
19
|
meta_tag_description: URLs found interesting by Matthew M. Boedicker
|
20
20
|
meta_tag_verify-v1:
|
@@ -26,4 +26,5 @@ pubsubhubbub_hubs: []
|
|
26
26
|
|
27
27
|
root_url: http://urls.matthewm.boedicker.org/
|
28
28
|
show_names: true
|
29
|
+
thumbnail_max_side: 90
|
29
30
|
user_agent: murlsh (http://github.com/mmb/murlsh)
|
data/lib/murlsh/auth.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
digest/md5
|
1
|
+
require 'csv'
|
2
|
+
require 'digest/md5'
|
4
3
|
|
5
|
-
bcrypt
|
6
|
-
}.each { |m| require m }
|
4
|
+
require 'bcrypt'
|
7
5
|
|
8
6
|
module Murlsh
|
9
7
|
|
@@ -22,7 +20,7 @@ module Murlsh
|
|
22
20
|
# Authenticate a user by password. Return their name and email if correct.
|
23
21
|
def auth(password)
|
24
22
|
CSV::Reader.parse(open(@file)) do |row|
|
25
|
-
return { :name => row[0], :email => row[1] }
|
23
|
+
return { :name => row[0], :email => row[1] } if
|
26
24
|
BCrypt::Password.new(row[2]) == password
|
27
25
|
end
|
28
26
|
end
|
@@ -30,8 +28,8 @@ module Murlsh
|
|
30
28
|
# Add a user to the authentication file.
|
31
29
|
def add_user(username, email, password)
|
32
30
|
Murlsh::openlock(@file, 'a') do |f|
|
33
|
-
f.write
|
34
|
-
BCrypt::Password.create(password)].join(',')}\n"
|
31
|
+
f.write "#{[username, Digest::MD5.hexdigest(email),
|
32
|
+
BCrypt::Password.create(password)].join(',')}\n"
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
data/lib/murlsh/config_server.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
|
2
|
-
digest/sha1
|
1
|
+
require 'digest/sha1'
|
3
2
|
|
4
|
-
json
|
5
|
-
rack
|
6
|
-
}.each { |m| require m }
|
3
|
+
require 'json'
|
4
|
+
require 'rack'
|
7
5
|
|
8
6
|
module Murlsh
|
9
7
|
|
@@ -27,7 +25,7 @@ module Murlsh
|
|
27
25
|
end
|
28
26
|
|
29
27
|
# Serve a JSON subset of the configuration.
|
30
|
-
def get(req); Rack::Response.new
|
28
|
+
def get(req); Rack::Response.new @config_json, 200, @headers; end
|
31
29
|
|
32
30
|
end
|
33
31
|
|
data/lib/murlsh/dispatch.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
rack
|
1
|
+
require 'active_record'
|
2
|
+
require 'rack'
|
4
3
|
|
5
|
-
murlsh
|
6
|
-
}.each { |m| require m }
|
4
|
+
require 'murlsh'
|
7
5
|
|
8
6
|
module Murlsh
|
9
7
|
|
@@ -51,11 +49,11 @@ module Murlsh
|
|
51
49
|
|
52
50
|
# Called if the request is not found.
|
53
51
|
def not_found(req)
|
54
|
-
Rack::Response.new
|
52
|
+
Rack::Response.new "<p>#{req.url} not found</p>
|
55
53
|
|
56
54
|
<p><a href=\"#{@config['root_url']}\">root<a></p>
|
57
55
|
",
|
58
|
-
404, { 'Content-Type' => 'text/html' }
|
56
|
+
404, { 'Content-Type' => 'text/html' }
|
59
57
|
end
|
60
58
|
|
61
59
|
end
|
data/lib/murlsh/doc.rb
CHANGED
data/lib/murlsh/failproof.rb
CHANGED
data/lib/murlsh/head_from_get.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Murlsh
|
2
2
|
|
3
|
-
#
|
3
|
+
# Mixin for adding head() that calls get() and removed the body.
|
4
4
|
module HeadFromGet
|
5
5
|
|
6
|
-
#
|
6
|
+
# Call get() and remove the body.
|
7
7
|
def head(req)
|
8
8
|
resp = get(req)
|
9
9
|
resp.body = ''
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Murlsh
|
4
|
+
|
5
|
+
# Magick::ImageList mixin.
|
6
|
+
module ImageList
|
7
|
+
|
8
|
+
# For each image, if the width or height is larger than max_side, resize so
|
9
|
+
# that the longest side = max_side.
|
10
|
+
def resize_down!(max_side)
|
11
|
+
each do |i|
|
12
|
+
if i.columns > max_side or i.rows > max_side
|
13
|
+
i.resize_to_fit! max_side, max_side
|
14
|
+
end
|
15
|
+
i.strip!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the preferred extension for this image.
|
20
|
+
def preferred_extension; FormatExtensions[self.format]; end
|
21
|
+
|
22
|
+
def data_uri; "data:#{mime_type};base64,#{Base64.encode64(to_blob)}"; end
|
23
|
+
|
24
|
+
FormatExtensions = {
|
25
|
+
'GIF' => '.gif',
|
26
|
+
'JPEG' => '.jpg',
|
27
|
+
'PNG' => '.png',
|
28
|
+
}
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/murlsh/img_store.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
open-uri
|
4
|
-
|
1
|
+
require 'cgi'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'RMagick'
|
7
|
+
|
8
|
+
require 'murlsh'
|
5
9
|
|
6
10
|
module Murlsh
|
7
11
|
|
8
12
|
# Fetch images from urls and store them locally.
|
9
13
|
class ImgStore
|
10
14
|
|
15
|
+
# Fetch images from urls and store them locally.
|
16
|
+
# Options:
|
17
|
+
# * :user_agent - user agent to send with http requests
|
11
18
|
def initialize(storage_dir, options={})
|
12
19
|
@storage_dir = storage_dir
|
13
20
|
@user_agent = options[:user_agent]
|
@@ -16,16 +23,47 @@ module Murlsh
|
|
16
23
|
# Build headers to send with request.
|
17
24
|
def headers
|
18
25
|
result = {}
|
19
|
-
result['User-Agent'] = @user_agent
|
26
|
+
result['User-Agent'] = @user_agent if @user_agent
|
20
27
|
result
|
21
28
|
end
|
22
29
|
|
23
30
|
# Fetch an image from a url and store it locally.
|
24
|
-
|
25
|
-
|
31
|
+
#
|
32
|
+
# The filename will be the md5sum of the contents plus the correct
|
33
|
+
# extension.
|
34
|
+
#
|
35
|
+
# If a block is given the Magick::ImageList created will be yielded
|
36
|
+
# before storage.
|
37
|
+
def store_url(url, &block)
|
38
|
+
open(url, headers) { |fin| store_img_data fin.read, &block }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Accept a blob of image data and store it locally.
|
42
|
+
#
|
43
|
+
# The filename will be the md5sum of the contents plus the correct
|
44
|
+
# extension.
|
45
|
+
#
|
46
|
+
# If a block is given the Magick::ImageList created will be yielded
|
47
|
+
# before storage.
|
48
|
+
def store_img_data(img_data, &block)
|
49
|
+
img = Magick::ImageList.new.from_blob(img_data)
|
50
|
+
yield img if block_given?
|
51
|
+
store_img img
|
52
|
+
end
|
53
|
+
|
54
|
+
# Accept a Magick::ImageList and store it locally.
|
55
|
+
#
|
56
|
+
# The filename will be the md5sum of the contents plus the correct
|
57
|
+
# extension.
|
58
|
+
def store_img(img)
|
59
|
+
img.extend(Murlsh::ImageList) unless img.is_a?(Murlsh::ImageList)
|
60
|
+
img_data = img.to_blob
|
61
|
+
md5 = Digest::MD5.hexdigest(img_data)
|
62
|
+
|
63
|
+
local_file = "#{md5}#{img.preferred_extension}"
|
26
64
|
local_path = File.join(storage_dir, local_file)
|
27
|
-
|
28
|
-
|
65
|
+
unless File.exists?(local_path)
|
66
|
+
Murlsh::openlock(local_path, 'w') { |fout| fout.write img_data }
|
29
67
|
end
|
30
68
|
local_file
|
31
69
|
end
|