refactor screenshots to work for all pages

This commit is contained in:
Kyle Drake 2014-04-20 19:04:41 -07:00
parent 72c971ae98
commit b488cf4347
7 changed files with 70 additions and 70 deletions

View file

@ -11,7 +11,6 @@ gem 'puma', require: nil
gem 'rubyzip', require: 'zip' gem 'rubyzip', require: 'zip'
gem 'rack-recaptcha', require: 'rack/recaptcha' gem 'rack-recaptcha', require: 'rack/recaptcha'
gem 'rmagick', require: nil gem 'rmagick', require: nil
gem 'selenium-webdriver', require: nil
gem 'sidekiq' gem 'sidekiq'
gem 'ago' gem 'ago'
gem 'mail' gem 'mail'
@ -19,6 +18,7 @@ gem 'google-api-client', require: 'google/api_client'
gem 'tilt' gem 'tilt'
gem 'erubis' gem 'erubis'
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby' gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'
gem 'screencap'
platform :mri do platform :mri do
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic

View file

@ -30,8 +30,6 @@ GEM
minitest (>= 2) minitest (>= 2)
celluloid (0.15.2) celluloid (0.15.2)
timers (~> 1.1.0) timers (~> 1.1.0)
childprocess (0.5.2)
ffi (~> 1.0, >= 1.0.11)
cliver (0.3.2) cliver (0.3.2)
coderay (1.1.0) coderay (1.1.0)
columnize (0.3.6) columnize (0.3.6)
@ -136,11 +134,8 @@ GEM
rmagick (2.13.2) rmagick (2.13.2)
rubyzip (1.1.2) rubyzip (1.1.2)
safe_yaml (1.0.1) safe_yaml (1.0.1)
selenium-webdriver (2.40.0) screencap (0.1.1)
childprocess (>= 0.5.0) phantomjs
multi_json (~> 1.0)
rubyzip (~> 1.0)
websocket (~> 1.0.4)
sequel (4.8.0) sequel (4.8.0)
sequel_pg (1.6.9) sequel_pg (1.6.9)
pg (>= 0.8.0) pg (>= 0.8.0)
@ -189,7 +184,6 @@ GEM
webmock (1.17.4) webmock (1.17.4)
addressable (>= 2.2.7) addressable (>= 2.2.7)
crack (>= 0.3.2) crack (>= 0.3.2)
websocket (1.0.7)
websocket-driver (0.3.2) websocket-driver (0.3.2)
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
@ -230,7 +224,7 @@ DEPENDENCIES
ruby-debug ruby-debug
rubyzip rubyzip
sass sass
selenium-webdriver screencap
sequel sequel
sequel_pg sequel_pg
shotgun shotgun

11
app.rb
View file

@ -437,7 +437,7 @@ end
post '/site_files/delete' do post '/site_files/delete' do
require_login require_login
sanitized_filename = params[:filename].gsub(/[^a-zA-Z0-9_\-.]/, '') sanitized_filename = Site.sanitize_filename params[:filename]
current_site.delete_file(sanitized_filename) current_site.delete_file(sanitized_filename)
@ -485,17 +485,10 @@ post '/site_files/save/:filename' do |filename|
halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.' halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.'
end end
sanitized_filename = filename.gsub(/[^a-zA-Z0-9_\-.]/, '') sanitized_filename = Site.sanitize_filename filename
current_site.store_file sanitized_filename, tempfile current_site.store_file sanitized_filename, tempfile
if sanitized_filename =~ /index\.html/
ScreenshotWorker.perform_async current_site.username
current_site.update site_changed: true
end
current_site.update changed_count: 1+current_site.changed_count, updated_at: Time.now
'ok' 'ok'
end end

View file

@ -24,7 +24,7 @@ class Site < Sequel::Model
application/xml application/xml
audio/midi audio/midi
} }
VALID_EXTENSIONS = %w{ VALID_EXTENSIONS = %w{
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json
geojson csv tsv mf ico pdf asc key pgp xml mid midi geojson csv tsv mf ico pdf asc key pgp xml mid midi
} }
@ -40,28 +40,30 @@ class Site < Sequel::Model
VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123 VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
# FIXME smarter DIR_ROOT discovery # FIXME smarter DIR_ROOT discovery
DIR_ROOT = './' DIR_ROOT = './'
TEMPLATE_ROOT = File.join DIR_ROOT, 'views', 'templates' TEMPLATE_ROOT = File.join DIR_ROOT, 'views', 'templates'
PUBLIC_ROOT = File.join DIR_ROOT, 'public' PUBLIC_ROOT = File.join DIR_ROOT, 'public'
SITE_FILES_ROOT = File.join PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites') SITE_FILES_ROOT = File.join PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites')
SCREENSHOTS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_screenshots_test' : 'site_screenshots'))
SCREENSHOTS_URL_ROOT = '/site_screenshots'
many_to_one :server many_to_one :server
many_to_many :tags many_to_many :tags
one_to_many :follows one_to_many :follows
one_to_many :followings, key: :actioning_site_id, class: :Follow one_to_many :followings, key: :actioning_site_id, class: :Follow
one_to_many :tips one_to_many :tips
one_to_many :tippings, key: :actioning_site_id, class: :Tip one_to_many :tippings, key: :actioning_site_id, class: :Tip
one_to_many :blocks one_to_many :blocks
one_to_many :blockings, key: :actioning_site_id, class: :Block one_to_many :blockings, key: :actioning_site_id, class: :Block
one_to_many :stats one_to_many :stats
one_to_many :events one_to_many :events
one_to_many :changes one_to_many :changes
class << self class << self
@ -128,6 +130,7 @@ class Site < Sequel::Model
%w{index not_found}.each do |name| %w{index not_found}.each do |name|
File.write file_path("#{name}.html"), render_template("#{name}.erb") File.write file_path("#{name}.html"), render_template("#{name}.erb")
ScreenshotWorker.perform_async values[:username], "#{name}.html"
end end
FileUtils.cp template_file_path('cat.png'), file_path('cat.png') FileUtils.cp template_file_path('cat.png'), file_path('cat.png')
@ -162,7 +165,7 @@ class Site < Sequel::Model
def self.valid_file_type?(uploaded_file) def self.valid_file_type?(uploaded_file)
mime_type = Magic.guess_file_mime_type uploaded_file[:tempfile].path mime_type = Magic.guess_file_mime_type uploaded_file[:tempfile].path
return true if (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/) && return true if (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/) &&
Site::VALID_EXTENSIONS.include?(File.extname(uploaded_file[:filename]).sub(/^./, '').downcase) Site::VALID_EXTENSIONS.include?(File.extname(uploaded_file[:filename]).sub(/^./, '').downcase)
false false
end end
@ -171,13 +174,13 @@ class Site < Sequel::Model
FileUtils.mv uploaded.path, file_path(filename) FileUtils.mv uploaded.path, file_path(filename)
File.chmod(0640, file_path(filename)) File.chmod(0640, file_path(filename))
if filename =~ /index\.html/ ScreenshotWorker.perform_async values[:username], filename
ScreenshotWorker.perform_async values[:username]
self.site_changed = true self.site_changed = true
save(validate: false) self.changed_count += 1
end save(validate: false)
end end
def increment_changed_count def increment_changed_count
self.changed_count += 1 self.changed_count += 1
self.updated_at = Time.now self.updated_at = Time.now
@ -250,14 +253,14 @@ class Site < Sequel::Model
if new? && values[:username].length > 2 && !values[:username].match(VALID_HOSTNAME) if new? && values[:username].length > 2 && !values[:username].match(VALID_HOSTNAME)
errors.add :username, 'A valid user/site name is required.' errors.add :username, 'A valid user/site name is required.'
end end
if new? && values[:username].length > 32 if new? && values[:username].length > 32
errors.add :username, 'User/site name cannot exceed 32 characters.' errors.add :username, 'User/site name cannot exceed 32 characters.'
end end
# Check for existing user # Check for existing user
user = self.class.select(:id, :username).filter(username: values[:username]).first user = self.class.select(:id, :username).filter(username: values[:username]).first
if user if user
if user.id != values[:id] if user.id != values[:id]
errors.add :username, 'This username is already taken. Try using another one.' errors.add :username, 'This username is already taken. Try using another one.'
@ -267,7 +270,7 @@ class Site < Sequel::Model
if values[:password].nil? || (@password_length && @password_length < MINIMUM_PASSWORD_LENGTH) if values[:password].nil? || (@password_length && @password_length < MINIMUM_PASSWORD_LENGTH)
errors.add :password, "Password must be at least #{MINIMUM_PASSWORD_LENGTH} characters." errors.add :password, "Password must be at least #{MINIMUM_PASSWORD_LENGTH} characters."
end end
if !values[:domain].nil? && !values[:domain].empty? if !values[:domain].nil? && !values[:domain].empty?
if !(values[:domain] =~ /^[a-zA-Z0-9.-]+\.[a-zA-Z0-9]+$/i) || values[:domain].length > 90 if !(values[:domain] =~ /^[a-zA-Z0-9.-]+\.[a-zA-Z0-9]+$/i) || values[:domain].length > 90
errors.add :domain, "Domain provided is not valid. Must take the form of domain.com" errors.add :domain, "Domain provided is not valid. Must take the form of domain.com"
@ -291,7 +294,7 @@ class Site < Sequel::Model
def files_path(name=nil) def files_path(name=nil)
File.join SITE_FILES_ROOT, (name || username) File.join SITE_FILES_ROOT, (name || username)
end end
def file_path(filename) def file_path(filename)
File.join files_path, filename File.join files_path, filename
end end
@ -309,7 +312,7 @@ class Site < Sequel::Model
space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x} space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x}
space.nil? ? 0 : space space.nil? ? 0 : space
end end
def used_space_in_megabytes def used_space_in_megabytes
(used_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2) (used_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2)
end end
@ -318,7 +321,7 @@ class Site < Sequel::Model
remaining = maximum_space_in_bytes - used_space_in_bytes remaining = maximum_space_in_bytes - used_space_in_bytes
remaining < 0 ? 0 : remaining remaining < 0 ? 0 : remaining
end end
def available_space_in_megabytes def available_space_in_megabytes
(available_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2) (available_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2)
end end
@ -339,18 +342,22 @@ class Site < Sequel::Model
def supporter? def supporter?
!values[:stripe_customer_id].nil? !values[:stripe_customer_id].nil?
end end
# This will return false if they have ended their support plan. # This will return false if they have ended their support plan.
def ended_supporter? def ended_supporter?
values[:ended_plan] values[:ended_plan]
end end
def plan_name def plan_name
return 'Free Plan' if !supporter? || (supporter? && ended_supporter?) return 'Free Plan' if !supporter? || (supporter? && ended_supporter?)
'Supporter Plan' 'Supporter Plan'
end end
def title def title
values[:title] || values[:username] values[:title] || values[:username]
end end
end
def screenshot_url(filename, resolution)
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.jpg"
end
end

View file

@ -49,7 +49,7 @@
<% @sites.each do |site| %> <% @sites.each do |site| %>
<li> <li>
<a href="http://<%= site.username %>.neocities.org" class="neo-Screen-Shot" target="_blank" title="Website of <%= site.username %>"> <a href="http://<%= site.username %>.neocities.org" class="neo-Screen-Shot" target="_blank" title="Website of <%= site.username %>">
<span class="img-Holder" style="background:url(/site_screenshots/<%= site.username %>.jpg) no-repeat;"> <span class="img-Holder" style="background:url(<%= site.screenshot_url('index.html', '270x162') %>) no-repeat;">
<img src="/img/placeholder.png" alt="<%= site.username %>" /> <img src="/img/placeholder.png" alt="<%= site.username %>" />
</span> </span>
</a> </a>

View file

@ -30,7 +30,7 @@
<div class="col col-50 signup-Area" style="width: 289px;"> <div class="col col-50 signup-Area" style="width: 289px;">
<div class="signup-Form"> <div class="signup-Form">
<fieldset class="content"> <fieldset class="content">
<img class="screenshot" src="/site_screenshots/<%= current_site.username %>.jpg"> <img class="screenshot" src="<%= current_site.screenshot_url('index.html', '270x162') %>">
</fieldset> </fieldset>
</div> </div>
</div> </div>
@ -78,7 +78,7 @@
<div class="file filehover"> <div class="file filehover">
<% if file.ext.match(/html|htm/) %> <% if file.ext.match(/html|htm/) %>
<div class="html-thumbnail html fileimagehover"> <div class="html-thumbnail html fileimagehover">
<img src="https://neocities.org//site_screenshots/bigpig.jpg"> <img src="<%= current_site.screenshot_url(file.filename, '105x63') %>">
<div class="overlay"></div> <div class="overlay"></div>
</div> </div>
<% elsif file.ext.match(/jpg|png|bmp|gif/) %> <% elsif file.ext.match(/jpg|png|bmp|gif/) %>

View file

@ -1,34 +1,40 @@
require 'selenium-webdriver'
require 'RMagick' require 'RMagick'
class ScreenshotWorker class ScreenshotWorker
REQUIRED_RESOLUTIONS = ['235x141', '105x63', '270x162']
SCREENSHOTS_PATH = File.join DIR_ROOT, 'public', 'site_screenshots'
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :screenshots, retry: 3, backtrace: true sidekiq_options queue: :screenshots, retry: 3, backtrace: true
def perform(username) def perform(username, filename)
screenshot = Tempfile.new 'neocities_screenshot' screenshot = Tempfile.new 'neocities_screenshot'
screenshot.close screenshot.close
screenshot_output_path = screenshot.path+'.png'
caps = Selenium::WebDriver::Remote::Capabilities.htmlunit javascript_enabled: true, takesScreenshot: true f = Screencap::Fetcher.new("http://#{username}.neocities.org/#{filename}")
f.fetch(
output: screenshot_output_path,
width: 1280,
height: 720
)
driver = Selenium::WebDriver.for :remote, url: $config['phantomjs_url'][rand($config['phantomjs_url'].length)], desired_capabilities: caps
driver.manage.window.resize_to 1280, 720
wait = Selenium::WebDriver::Wait.new(timeout: 10) # seconds
wait.until {
driver.navigate.to "http://#{username}.neocities.org"
driver.save_screenshot screenshot.path
}
driver.quit
img_list = Magick::ImageList.new img_list = Magick::ImageList.new
img_list.read screenshot.path img_list.from_blob File.read(screenshot_output_path)
screenshot.unlink screenshot.unlink
File.unlink screenshot_output_path
img_list.new_image(img_list.first.columns, img_list.first.rows) { self.background_color = "white" } img_list.new_image(img_list.first.columns, img_list.first.rows) { self.background_color = "white" }
img = img_list.reverse.flatten_images img = img_list.reverse.flatten_images
img.crop!(0, 0, 1280, 720)
img.resize! 208, 125 user_screenshots_path = File.join SCREENSHOTS_PATH, username
img.write File.join(DIR_ROOT, 'public', 'site_screenshots', "#{username}.jpg") FileUtils.mkdir_p user_screenshots_path
REQUIRED_RESOLUTIONS.each do |res|
img.scale(*res.split('x').collect {|r| r.to_i}).write(File.join(user_screenshots_path, "#{filename}.#{res}.jpg")) {
self.quality = 90
}
end
end end
end end