murlsh 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|