Browse files

initial commit

  • Loading branch information...
0 parents commit 55e82294139c939d0605125c77ad10d160284472 Kellen Presley committed Mar 14, 2010
Showing with 6,250 additions and 0 deletions.
  1. +6 −0 .gitignore
  2. +25 −0 README.textile
  3. +10 −0 Rakefile
  4. +10 −0 app/controllers/application_controller.rb
  5. +99 −0 app/controllers/links_controller.rb
  6. +100 −0 app/controllers/posts_controller.rb
  7. +57 −0 app/controllers/source_urls_controller.rb
  8. +3 −0 app/controllers/welcome_controller.rb
  9. +5 −0 app/helpers/application_helper.rb
  10. +122 −0 app/models/link.rb
  11. +49 −0 app/models/post.rb
  12. +30 −0 app/models/source_url.rb
  13. +65 −0 app/views/layouts/application.html.haml
  14. +42 −0 app/views/links/_details.html.haml
  15. +14 −0 app/views/links/_index_actions.html.haml
  16. +30 −0 app/views/links/index.html.haml
  17. +38 −0 app/views/links/show.html.haml
  18. +82 −0 app/views/posts/_builder.html.haml
  19. +6 −0 app/views/posts/_index_actions.html.haml
  20. +2 −0 app/views/posts/_link.html.haml
  21. +1 −0 app/views/posts/edit.html.haml
  22. +14 −0 app/views/posts/index.html.haml
  23. +1 −0 app/views/posts/new.html.haml
  24. +28 −0 app/views/posts/show.html.haml
  25. +7 −0 app/views/source_urls/_index_actions.html.haml
  26. +18 −0 app/views/source_urls/index.html.haml
  27. +33 −0 app/views/source_urls/show.html.haml
  28. +2 −0 app/views/welcome/index.html.haml
  29. +110 −0 config/boot.rb
  30. +23 −0 config/database.yml
  31. +19 −0 config/environment.rb
  32. +17 −0 config/environments/development.rb
  33. +28 −0 config/environments/production.rb
  34. +31 −0 config/environments/test.rb
  35. +7 −0 config/initializers/backtrace_silencers.rb
  36. +5 −0 config/initializers/delayed_job_config.rb
  37. +10 −0 config/initializers/inflections.rb
  38. +5 −0 config/initializers/mime_types.rb
  39. +21 −0 config/initializers/new_rails_defaults.rb
  40. +15 −0 config/initializers/session_store.rb
  41. +5 −0 config/locales/en.yml
  42. +37 −0 config/routes.rb
  43. +14 −0 db/migrate/20091011213055_create_source_urls.rb
  44. +24 −0 db/migrate/20091014161553_create_links.rb
  45. +16 −0 db/migrate/20091102170506_create_posts.rb
  46. +20 −0 db/migrate/20091108212340_create_delayed_jobs.rb
  47. +9 −0 db/migrate/20091112172527_add_image_path_prefix_to_posts.rb
  48. +60 −0 db/schema.rb
  49. +7 −0 db/seeds.rb
  50. +2 −0 doc/README_FOR_APP
  51. +108 −0 lib/link_job.rb
  52. +39 −0 lib/post_job.rb
  53. +30 −0 public/404.html
  54. +30 −0 public/422.html
  55. +30 −0 public/500.html
  56. 0 public/favicon.ico
  57. BIN public/images/ajax-loading.gif
  58. BIN public/images/arrow-down.gif
  59. BIN public/images/arrow-up.gif
  60. BIN public/images/bad_icon.gif
  61. BIN public/images/black-70.png
  62. BIN public/images/black.png
  63. BIN public/images/cancel.gif
  64. BIN public/images/cancel.png
  65. BIN public/images/cancel_old.png
  66. BIN public/images/check.png
  67. BIN public/images/close.gif
  68. BIN public/images/database_lightning.png
  69. BIN public/images/edit.png
  70. BIN public/images/gradient.jpeg
  71. BIN public/images/header.png
  72. BIN public/images/help.png
  73. BIN public/images/img01.jpg
  74. BIN public/images/img02.jpg
  75. BIN public/images/img03.jpg
  76. BIN public/images/img04.jpg
  77. BIN public/images/marqueeHoriz.gif
  78. BIN public/images/marqueeVert.gif
  79. BIN public/images/missing.png
  80. BIN public/images/new.png
  81. BIN public/images/nextlabel.gif
  82. BIN public/images/pattern_148-70.png
  83. BIN public/images/pattern_148.gif
  84. BIN public/images/prevlabel.gif
  85. BIN public/images/rails.png
  86. BIN public/images/remove.png
  87. BIN public/images/replace.png
  88. BIN public/images/spacer.gif
  89. BIN public/images/swap.png
  90. BIN public/images/trash.png
  91. +58 −0 public/javascripts/application.js
  92. +298 −0 public/javascripts/jquery-ui-1.7.2.custom.min.js
  93. +1,197 −0 public/javascripts/jquery.Jcrop.js
  94. +163 −0 public/javascripts/jquery.Jcrop.min.js
  95. +281 −0 public/javascripts/jquery.in-place-edit.js
  96. +19 −0 public/javascripts/jquery.js
  97. +19 −0 public/javascripts/jquery.min.js
  98. +119 −0 public/javascripts/links.js
  99. +254 −0 public/javascripts/posts.js
  100. +197 −0 public/javascripts/source_urls.js
  101. +5 −0 public/robots.txt
  102. BIN public/stylesheets/Jcrop.gif
  103. +35 −0 public/stylesheets/jquery.Jcrop.css
  104. +425 −0 public/stylesheets/style.css
  105. +4 −0 script/about
  106. +3 −0 script/console
  107. +3 −0 script/dbconsole
  108. +5 −0 script/delayed_job
  109. +3 −0 script/destroy
  110. +3 −0 script/generate
  111. +3 −0 script/performance/benchmarker
  112. +3 −0 script/performance/profiler
  113. +3 −0 script/plugin
  114. +3 −0 script/runner
  115. +3 −0 script/server
  116. +20 −0 vendor/plugins/delayed_job/MIT-LICENSE
  117. +185 −0 vendor/plugins/delayed_job/README.textile
  118. +34 −0 vendor/plugins/delayed_job/Rakefile
  119. +1 −0 vendor/plugins/delayed_job/VERSION
  120. +14 −0 vendor/plugins/delayed_job/contrib/delayed_job.monitrc
  121. +66 −0 vendor/plugins/delayed_job/delayed_job.gemspec
  122. +22 −0 vendor/plugins/delayed_job/generators/delayed_job/delayed_job_generator.rb
  123. +20 −0 vendor/plugins/delayed_job/generators/delayed_job/templates/migration.rb
  124. +5 −0 vendor/plugins/delayed_job/generators/delayed_job/templates/script
  125. +1 −0 vendor/plugins/delayed_job/init.rb
  126. +76 −0 vendor/plugins/delayed_job/lib/delayed/command.rb
  127. +273 −0 vendor/plugins/delayed_job/lib/delayed/job.rb
  128. +22 −0 vendor/plugins/delayed_job/lib/delayed/message_sending.rb
  129. +55 −0 vendor/plugins/delayed_job/lib/delayed/performable_method.rb
  130. +27 −0 vendor/plugins/delayed_job/lib/delayed/recipes.rb
  131. +15 −0 vendor/plugins/delayed_job/lib/delayed/tasks.rb
  132. +56 −0 vendor/plugins/delayed_job/lib/delayed/worker.rb
  133. +13 −0 vendor/plugins/delayed_job/lib/delayed_job.rb
  134. +1 −0 vendor/plugins/delayed_job/recipes/delayed_job.rb
  135. +43 −0 vendor/plugins/delayed_job/spec/database.rb
  136. +150 −0 vendor/plugins/delayed_job/spec/delayed_method_spec.rb
  137. +406 −0 vendor/plugins/delayed_job/spec/job_spec.rb
  138. +17 −0 vendor/plugins/delayed_job/spec/story_spec.rb
  139. +1 −0 vendor/plugins/delayed_job/tasks/jobs.rake
6 .gitignore
@@ -0,0 +1,6 @@
+log/*.log
+db/*.sqlite3
+public/images/link/*
+public/system
+tmp/*
+public/posts/*
25 README.textile
@@ -0,0 +1,25 @@
+h1. Crawwwl: A thumbnail generating web crawler and content management system
+
+p. Crawwwl is a web aggregator and content management system. Simply put, Crawwwl helps you sort through the vast universe that is the internet to find links to only the best content. Attract new hits and grow your userbase by displaying relevant links collected by Crawwwl.
+
+h1. Examples
+
+p. To see example posts generated by Crawwwl, visit "here":http://crawwwl.com/posts/best-geek-pumpkins-of-2009 or "here":http://crawwwl.com/posts/making-biofuel-from-algae
+
+h1. Usage
+
+h2. Source URLs
+
+p. First create a source url. From here you can click the lightning bolt to begin gathering all the links from this source URL (by clicking "Extract Links from this URL"). Once you have done this, you can add rules to filter out bad or irrelevant links. Then save the links that are left.
+
+h2. Links
+
+p. After you have saved links, make sure delayed job is running. From the project root you can type <pre>rake jobs:work</pre> to begin processing links.
+
+p. You may then click on "Links" at the top of the page, and begin seeing links as they go from the uncrawwwled to crawwwled state. From here you can click "Select image" (lightning bolt link) to being selecting images for each link. The image selected here is the one that will represent this link on your site. From this view, you can either select an image, upload your own, or mark the link as "bad" and it will never come up again.
+
+h2. Posts
+
+p. After you have gathered enough links to create a post, you can click the "Posts" link at the top of the page. Clicking add a post, will allow you to use known good links to create a post that will be visible from your website. From this view you can remove, replace, or mark bad any links in the post, as well as change the thumbnail dimensions, link descriptions and more.
+
+p. After saving a post, you can press the publish link, (make sure deplayed job is running) and get a tar.gz archive of your post. This archive will contain all the image thumbnails, as well as the html markup of your post. It is ready to be extracted in place on your site or blog. Enjoy!
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
10 app/controllers/application_controller.rb
@@ -0,0 +1,10 @@
+# Filters added to this controller apply to all controllers in the application.
+# Likewise, all the methods added will be available for all controllers.
+
+class ApplicationController < ActionController::Base
+ helper :all # include all helpers, all the time
+ protect_from_forgery # See ActionController::RequestForgeryProtection for details
+
+ # Scrub sensitive parameters from your log
+ # filter_parameter_logging :password
+end
99 app/controllers/links_controller.rb
@@ -0,0 +1,99 @@
+class LinksController < ApplicationController
+ before_filter :controller_js
+
+ def controller_js
+ @controller_js = "links"
+ end
+
+ def index
+ @links = Link.list(params)
+ end
+
+ def show
+ @link = Link.find_by_id(params[:id])
+ @images = @link.images
+ end
+
+ def create
+ @link = Link.new(params[:link])
+
+ if @link.save
+ render :json => @link, :status => 200
+ else
+ render :json => { :errors => @link.errors.full_messages }, :status => 422
+ end
+ end
+
+ def update
+ @link = Link.find_by_id(params[:id])
+ @link.url = params[:update_value]
+
+ if @link.save
+ render :text => @link.url
+ else
+ render :json => { :errors => @link.errors.full_messages }, :status => 422
+ end
+ end
+
+ def update_description
+ @link = Link.find_by_id(params[:id])
+ @link.description = params[:update_value]
+
+ if @link.save
+ render :text => @link.description
+ else
+ render :json => { :errors => @link.errors.full_messages }, :status => 422
+ end
+ end
+
+ def index_actions
+ @link = Link.find_by_id(params[:id])
+ render :partial => 'index_actions', :locals => {:link => @link}
+ end
+
+ def details
+ @link = Link.find_by_id(params[:id])
+ render :partial => 'details', :locals => {:link => @link}
+ end
+
+ def destroy
+ @link = Link.find_by_id(params[:id])
+ @link.destroy if @link
+ render :text => "deleted"
+ end
+
+ def assign_image
+ @link = Link.find_by_id(params[:id])
+ if @link.assign_image_and_description(params)
+ render :text => nil, :status => 200
+ else
+ render :text => nil, :status => 422
+ end
+ end
+
+ def upload_image
+ @link = Link.find_by_id(params[:id])
+ if @link.assign_image_and_description(params)
+ redirect_to next_links_path
+ else
+ flash[:error] = "Problem uploading file"
+ redirect_to @link
+ end
+ end
+
+ def next
+ if @link = Link.find_by_status("crawwwled", :select => "id")
+ redirect_to @link
+ else
+ flash[:notice] = "No more uncrawwwled links!"
+ redirect_to links_path
+ end
+ end
+
+ def mark_as_bad
+ @link = Link.find_by_id(params[:id])
+ @link.update_attribute(:status, 'bad')
+ render :text => "ok"
+ end
+
+end
100 app/controllers/posts_controller.rb
@@ -0,0 +1,100 @@
+class PostsController < ApplicationController
+ before_filter :controller_js, :except => [:show]
+
+ def controller_js
+ @controller_js = "posts"
+ end
+
+ def index
+ @posts = Post.list
+ end
+
+ def new
+ @post = Post.new
+ @post.name = "post-from-#{Date.today}"
+ @post.thumbnail_dimension = "60x50"
+ @post.columns = 5
+ @post.total_links = 25
+ end
+
+ def more
+ @links = Link.more(params)
+ render :partial => 'link', :collection => @links
+ end
+
+ def show
+ @post = Post.find_by_id(params[:id])
+ end
+
+ def create
+ @post = Post.new(params[:post])
+ handle_builder_submits()
+
+ if @post_saved
+ redirect_to @post
+ else
+ render :action => 'new'
+ end
+ end
+
+ def edit
+ @post = Post.find_by_id(params[:id])
+ end
+
+ def update
+ @post = Post.find_by_id(params[:id])
+ @post.attributes = params[:post]
+ handle_builder_submits()
+
+ if @post_saved
+ redirect_to @post
+ else
+ render :action => 'edit'
+ end
+ end
+
+ def change_name
+ @post = Post.find_by_id(params[:id])
+ @post.update_attribute(:name, params[:update_value])
+ render :text => params[:update_value]
+ end
+
+ def destroy
+ Post.find_by_id(params[:id]).destroy
+ end
+
+ def publish
+ @post = Post.find_by_id(params[:id])
+ Delayed::Job.enqueue PostJob.new(@post.id)
+ render :text => 'ok'
+ end
+
+ def poll_for_publish
+ if Post.find_by_id(params[:id]).published?
+ render :text => 'ok'
+ else
+ render :text => 'not ready yet', :status => 403
+ end
+ end
+
+ protected
+
+ def handle_builder_submits
+ if params[:commit].downcase.include?('crop') # crop a link image
+ link = Link.find_by_id(params[:link][:id])
+ link.recrop_image(params[:thumbnail])
+ elsif params[:commit].downcase.include?('upload') # crop a
+ link = Link.find_by_id(params[:link][:id])
+ link.assign_image_and_description(params)
+ elsif params[:commit].downcase.include?('link')
+ @link = Link.new(params[:link])
+ if @link.save
+ @link.assign_image_and_description(params)
+ @post.link_order << ",#{@link.id}"
+ end
+ elsif params[:commit].downcase.include?('post')
+ @post_saved = @post.save
+ end
+ end
+
+end
57 app/controllers/source_urls_controller.rb
@@ -0,0 +1,57 @@
+class SourceUrlsController < ApplicationController
+ before_filter :controller_js
+
+ def controller_js
+ @controller_js = "source_urls"
+ end
+
+ def index
+ @source_urls = SourceUrl.all(:order => "url")
+ end
+
+ def show
+ @source_url = SourceUrl.find_by_id(params[:id])
+ end
+
+ def create
+ @source_url = SourceUrl.new(params[:source_url])
+
+ if @source_url.save
+ render :json => @source_url, :status => 200
+ else
+ render :json => { :errors => @source_url.errors.full_messages }, :status => 422
+ end
+ end
+
+ def update
+ @source_url = SourceUrl.find_by_id(params[:id])
+ @source_url.url = params[:update_value]
+
+ if @source_url.save
+ render :text => @source_url.url, :status => 200
+ else
+ render :json => { :errors => @source_url.errors.full_messages }, :status => 422
+ end
+ end
+
+ def destroy
+ @source_url = SourceUrl.find_by_id(params[:id])
+ @source_url.destroy if @source_url
+ render :text => nil, :status => :ok
+ end
+
+ def index_actions
+ @source_url = SourceUrl.find_by_id(params[:id])
+ render :partial => 'index_actions', :locals => {:source_url => @source_url }
+ end
+
+ def extract_links
+ @source_url = SourceUrl.find_by_id(params[:id])
+ render :json => @source_url.extracted_links, :status => 200
+ end
+
+ def save_links
+ Link.create_from_params(params)
+ render :json => { :status => "ok" }
+ end
+end
3 app/controllers/welcome_controller.rb
@@ -0,0 +1,3 @@
+class WelcomeController < ApplicationController
+ before_filter :login_required, :except => [:index]
+end
5 app/helpers/application_helper.rb
@@ -0,0 +1,5 @@
+module ApplicationHelper
+ def filename(path) # really a link helper
+ path.sub(/^.*\//,'')
+ end
+end
122 app/models/link.rb
@@ -0,0 +1,122 @@
+class Link < ActiveRecord::Base
+ belongs_to :source_url
+ belongs_to :post
+
+ serialize :images
+
+ validates_uniqueness_of :url
+ validates_presence_of :url
+ validates_format_of :url, :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix
+
+ def self.style_hash
+ {
+ :extra_small => "60x50>#",
+ :small => "96x80>#",
+ :medium => "120x100>#",
+ :large => "144x120>#",
+ :extra_large => "180x150>#",
+ :huge => "192x160>#"
+ }
+ end
+
+ has_attached_file :image, :styles => self.style_hash, :default_url => "/images/missing.png"
+
+ %w(bad good uncrawwwled crawwwled live).each do |s, page|
+ named_scope s,
+ :order => "created_at desc",
+ :conditions => { :status => s.to_s }
+ end
+
+ def self.create_from_params(params)
+ params[:bad_links].each {|l| self.create(:url => l, :status => "bad") } if params[:bad_links]
+
+ if params[:good_links]
+ params[:good_links].each do |l|
+ good_link = self.create(:url => l)
+ Delayed::Job.enqueue LinkJob.new(good_link.id) if good_link.id
+ end
+ end
+ end
+
+ def self.list(params)
+ hsh = {:page => params[:page], :per_page => 30, :order => "updated_at desc" }
+ case params[:status]
+ when "good"
+ return Link.good.paginate(hsh)
+ when "bad"
+ return Link.bad.paginate(hsh)
+ when "uncrawwwled"
+ return Link.uncrawwwled.paginate(hsh)
+ else
+ return Link.crawwwled.paginate(hsh)
+ end
+ end
+
+ def assign_image_and_description(params) # returns boolean
+ img = params[:link][:image]
+ return false unless img
+ desc = params[:link][:description]
+ self.description = desc if desc
+
+ if img == "/images/cancel.png"
+ self.status = "bad"
+ else
+ self.status = "good"
+
+ if img.is_a? String # image is a selection from those that are aleardy crawwwled
+ file = File.join(RAILS_ROOT, "public", img)
+ return false unless File.exist?(file)
+ self.image.assign(File.open(file))
+ self.original_image_path = img
+ else # image was uploaded and params[:image].is_a?(TempFile)
+ self.handle_upload(img)
+ end
+ end
+ self.save
+ end
+
+ def self.more(params)
+ good.all(
+ :limit => params[:number].to_i,
+ :conditions => [ "id NOT IN (?)", params[:post_order].split(',') ],
+ :order => "RANDOM()"
+ )
+ end
+
+ def recrop_image(opts={})
+ return if opts.blank?
+ x1 = opts[:x1].to_i
+ y1 = opts[:y1].to_i
+ crop_width = opts[:w].to_i
+ crop_height = opts[:h].to_i
+
+ img = Magick::Image.read(File.join(RAILS_ROOT, 'public', self.original_image_path)).first
+ img = img.crop(x1, y1, crop_width, crop_height) # values from thumbnailer script
+ img.write(self.image.path)
+ file = File.open(self.image.path)
+ self.image.assign(file)
+ self.save
+ rescue => e
+ logger.warn "something went wrong in recrop_image for link ##{self.id}:"
+ logger.warn e
+ end
+
+ protected
+
+ def handle_upload(tempfile)
+ self.image.assign(tempfile)
+ self.save # paperclip creates the needed dir and file on save
+ path = File.join(RAILS_ROOT, 'public', 'images', 'link', self.id.to_s)
+ FileUtils.mkdir_p(path)
+ FileUtils.cp(self.image.path, path)
+ self.original_image_path = File.join('/images', 'link', self.id.to_s, self.image.original_filename)
+
+ if self.images.nil?
+ self.images = [self.original_image_path]
+ else
+ self.images << self.original_image_path
+ end
+ self.images.uniq!
+ end
+
+end
49 app/models/post.rb
@@ -0,0 +1,49 @@
+class Post < ActiveRecord::Base
+
+ has_many :links
+
+ validates_numericality_of :columns, :greater_than => 0
+ validates_numericality_of :total_links, :greater_than => 0
+ validates_presence_of :thumbnail_dimension, :columns, :total_links, :name
+ validates_uniqueness_of :name
+
+ def self.list(page=1)
+ self.paginate({:page => page, :per_page => 10, :order => "created_at desc"})
+ end
+
+ def self.sorted_thumbnail_dimensions # for select box
+ Link.style_hash.map do |k,v|
+ [ k.to_s.gsub('_', ' '), v.gsub('>#', '') ]
+ end.sort_by { |x| x.last.split('x').first.to_i }
+ end
+
+ def published?
+ File.exist?(File.join(RAILS_ROOT, 'public', 'posts', self.id.to_s, "#{self.name}.tgz"))
+ end
+
+ def thumbnail_dimension_name
+ Link.style_hash.invert["#{self.thumbnail_dimension}>#"]
+ end
+
+ def thumbnail_width
+ thumbnail_dimension.split('x').first
+ end
+
+ def thumbnail_height
+ thumbnail_dimension.split('x').last
+ end
+
+ def ordered_links
+ if self.link_order.blank?
+ return Link.good.all(:limit => self.total_links, :order => "RANDOM()")
+ end
+
+ id_ara = link_order.split(',')
+ the_links = Link.all(:conditions => ["id in (?)", id_ara])
+
+ id_ara.map do |id|
+ the_links.select {|link| link.id == id.to_i }.compact.first
+ end
+ end
+
+end
30 app/models/source_url.rb
@@ -0,0 +1,30 @@
+class SourceUrl < ActiveRecord::Base
+ has_many :links, :dependent => :destroy
+
+ validates_presence_of :url
+ validates_format_of :url, :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix
+ validates_uniqueness_of :url
+
+ def extracted_links
+ html = Curl::Easy.perform(self.url) do |c|
+ c.follow_location = true
+ c.headers["User-Agent"]= "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"
+ end.body_str
+
+ doc = Nokogiri::HTML(html)
+ links = (doc / :a).map {|x| x[:href] }.uniq
+
+ links.reject! {|x| !Link.new(:url => x).valid? } # uniqueness
+
+ parent = URI.parse(self.url)
+ links.map! do |x|
+ begin
+ parent + x
+ rescue
+ puts "invalid uri!"
+ end
+ end
+ links.compact.uniq.map(&:to_s)
+ end
+
+end
65 app/views/layouts/application.html.haml
@@ -0,0 +1,65 @@
+!!!
+%html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml" }
+ %head
+ %meta{ :content => "text/html;charset=UTF-8", "http-equiv" => "content-type" }
+ %title
+ = params[:controller].titleize
+ = javascript_include_tag 'jquery', 'jquery.in-place-edit', 'application', 'jquery-ui-1.7.2.custom.min'
+ = javascript_include_tag @controller_js if @controller_js
+ %link{ :href => "/stylesheets/style.css", :rel => "stylesheet", :type => "text/css", :media => "screen" }
+ = yield :head
+
+ :javascript
+ rails_authenticity_token = '#{form_authenticity_token}'
+
+ %body
+
+ #header
+ #menu
+ %ul
+ %li{ :class => ("current_page_item" if params[:controller] == "welcome") }
+ %a{ :href => "/" }
+ Home
+ %li{ :class => ("current_page_item" if params[:controller] == "source_urls") }
+ = link_to "Source URLs", source_urls_path
+ %li{ :class => ("current_page_item" if params[:controller] == "links") }
+ = link_to "Links", links_path
+ %li{ :class => ("current_page_item" if params[:controller] == "posts") }
+ = link_to "Posts", posts_path
+
+ #wrapper
+ #wrapper-btm
+ #page
+ #content
+ - if params[:controller] == "welcome"
+ #banner
+ \&nbsp;
+ .post
+ = yield
+
+ .space
+
+ #sidebar
+ %ul
+ %li
+ %h2
+ Menu
+ %ul
+ %li
+ %a{ :href => "#" }
+ Home
+ %li
+ = link_to "Source URLs", source_urls_path
+ %li
+ = link_to "Links", links_path
+ %li
+ = link_to "Posts", posts_path
+
+ = yield :menu
+
+ .space
+
+ #footer
+ #footer-wrap
+ %p#legal
+ \&copy; 2010 Crawwwl
42 app/views/links/_details.html.haml
@@ -0,0 +1,42 @@
+%h2
+ Link
+
+%h3 URL
+%ul
+ %li= link_to 'Check', link.url, :target => "blank"
+
+.space
+
+%h3 Position
+%ul
+ %li= link_to 'Remove', 'javascript:void(0)', :id => "remove_link"
+ %li= link_to 'Replace', 'javascript:void(0)', :id => "replace_link"
+ %li= link_to 'Mark Bad', 'javascript:void(0)', :id => "mark_as_bad"
+
+.space
+
+%h3 Image
+%ul
+ %li= link_to 'Upload New', 'javascript:void(0)', :id => "upload_new_image"
+ %li= link_to 'Re-crop Thumbnail', 'javascript:void(0)', :id => "recrop_thumbnail"
+
+.space
+
+%h3 Description
+%ul
+ %li#link_description=link.description
+
+#temp_recrop_holder.hidden
+ %label Preview
+
+ .space
+
+ #thumbnail_mask
+ =image_tag(link.original_image_path, :id => 'thumbnail_preview')
+ .space
+
+ =image_tag(link.original_image_path, :id => 'jcrop_target')
+ .space
+ %input#cancel_recrop.button{ :type => "button", :value => "Cancel" }
+ %input#save_recrop.button{ :name => "commit", :type => "submit", :value => "Re-crop Thumbnail" }
+ .space
14 app/views/links/_index_actions.html.haml
@@ -0,0 +1,14 @@
+.link_right
+ -if params[:status] == "bad"
+ =image_tag 'cancel.png', :width => 60, :height => 50
+ -else
+ =image_tag link.image(:extra_small)
+
+.link_left
+ = link_to "&nbsp;", link, :title => "select image", :class => "link_show action"
+
+ = link_to "&nbsp;", link.url, :title => "check", :class => "link_check action", :target => "_blank"
+
+ = link_to "&nbsp;", "javascript:void(0)", :title => "delete", :class => "link_delete action"
+
+.link_title{ :id => link.id }= link.url
30 app/views/links/index.html.haml
@@ -0,0 +1,30 @@
+%ul#link_messages
+ - if @links.blank?
+ %li= "No #{params[:status] || 'uncrawwwled' } links found!"
+
+= link_to "Add a Link", "javascript:void(0);", :id => "add_link"
+
+#add_link_snippet.hidden
+ %div
+ %input#url_field{:type => "text", :value => "http://" }
+ %input.button{:class => "cancel_link", :type => "button", :value => "Cancel" }
+ %input.button{:class => "save_link", :type => "button", :value => "Save" }
+
+.space
+
+%ul#link_list
+ - for link in @links
+ %li
+ = render :partial => 'index_actions', :locals => { :link => link }
+
+=will_paginate @links
+
+-content_for(:menu) do
+ %li
+ %h2
+ Sort
+ %ul
+ %li= link_to "Show crawwwled links (#{Link.crawwwled.count})", links_path(:status => "crawwwled")
+ %li= link_to "Show uncrawwwled links (#{Link.uncrawwwled.count})", links_path(:status => "uncrawwwled")
+ %li= link_to "Show good links (#{Link.good.count})", links_path(:status => "good")
+ %li= link_to "Show bad links (#{Link.bad.count})", links_path(:status => "bad")
38 app/views/links/show.html.haml
@@ -0,0 +1,38 @@
+%form{ :enctype => "multipart/form-data", :method => "post", :action => upload_image_link_path(@link) }
+ %label URL
+ %br
+ =link_to @link.url, @link.url, :target => "_blank"
+
+ .space
+
+ %label Description
+ %input#link_description{:name => "link[description]", :type => "text", :size => 60, :value => @link.description }
+
+ .space
+
+ %a#upload_image_link{ :href => "javascript:void(0)" }Upload an image
+
+ #image_upload.hidden
+ %input{ :name => "_method", :type => "hidden", :value => "put" }
+ %input{ :name => "authenticity_token", :type => "hidden", :value => form_authenticity_token }
+ %input#link_photo{ :name => "link[image]", :size => "30", :type => "file" }
+ %input#upload_image_cancel{ :type => "button", :value => "Cancel", :class => "button" }
+ %input#link_submit{ :name => "commit", :type => "submit", :value => "Save", :class => "button" }
+
+.space
+
+.loading_gif.hidden
+ =image_tag 'ajax-loading.gif'
+ %span#loading_text updating...
+ .space
+
+#image_holder
+ %img{:src => "/images/cancel.png", :alt => "mark link as bad", :title => "mark link as bad"}
+ -if !@images.blank?
+ - for img in @images
+ %img{:src => img, :alt => "", :class => (filename(img) == filename(@link.image.url)) ? "previously_selected" : "" }
+ -else
+ -if @link.status == "crawwwled"
+ %p No images found for this link!
+ -else
+ %p This link has not yet been crawwwled!
82 app/views/posts/_builder.html.haml
@@ -0,0 +1,82 @@
+=content_for :head do
+ =javascript_include_tag 'jquery.Jcrop'
+ =stylesheet_link_tag 'jquery.Jcrop'
+
+-form_for @post, :html => {:class => "labeled_form", :enctype => "multipart/form-data"} do |f|
+ =error_messages_for :post
+ =error_messages_for :link
+
+ =f.label :name
+ =f.text_field :name
+
+ =f.label :total_links
+ =f.text_field :total_links
+
+ =f.label :columns
+ =f.text_field :columns
+
+ =f.label :thumbnail_dimension
+ =f.select :thumbnail_dimension, Post.sorted_thumbnail_dimensions
+
+ =f.label :image_path_prefix
+ =f.text_field :image_path_prefix
+
+ =f.hidden_field :link_order
+
+ =hidden_field :thumbnail, :x1
+ =hidden_field :thumbnail, :y1
+ =hidden_field :thumbnail, :w
+ =hidden_field :thumbnail, :h
+
+ %input#link_id{ :name => "link[id]", :type => "hidden" }
+
+ .space
+
+ =f.submit 'Save Post', :id => 'save_post', :class => 'button'
+
+ .space
+
+ #link_form_stuff.hidden.linky
+ %label URL
+ %input#link_url{:name => "link[url]", :type => "text", :size => 30 }
+
+ %label Description
+ %input#the_link_description{:name => "link[description]", :type => "text", :size => 30 }
+
+ %label Image
+
+ #image_upload.hidden.linky
+ %input{ :name => "authenticity_token", :type => "hidden", :value => form_authenticity_token }
+ %input#link_photo{ :name => "link[image]", :size => "30", :type => "file" }
+
+ #add_link_submit_buttons.hidden.linky
+ .space
+ %input#add_link_cancel{ :type => "button", :value => "Cancel", :class => "button" }
+ %input#add_link_submit{ :name => "commit", :type => "submit", :value => "Add Link", :class => "button" }
+ .space
+
+ #crop_image_submit_buttons.hidden.linky
+ .space
+ %input#upload_image_cancel{ :type => "button", :value => "Cancel", :class => "button" }
+ %input#link_submit{ :name => "commit", :type => "submit", :value => "Upload Image", :class => "button" }
+ .space
+
+ #recrop_holder.hidden.linky
+
+#post_holder
+ - if @post.ordered_links.blank?
+ %p No available links!
+ - else
+ =render :partial => 'link', :collection => @post.ordered_links
+
+.space
+
+-content_for(:menu) do
+ %li
+ %h2
+ Post
+ %ul
+ %li=link_to 'Start Over', 'javascript:void(0)', :id => "post_start_over"
+ %li=link_to 'Add a link', 'javascript:void(0)', :id => "post_add_link"
+
+ %li#link_details
6 app/views/posts/_index_actions.html.haml
@@ -0,0 +1,6 @@
+.post_actions
+ = link_to "&nbsp;", post, :title => "view post", :class => "post_show action"
+ = link_to "&nbsp;", edit_post_path(post), :title => "edit post", :class => "edit action"
+ = link_to "&nbsp;", "javascript:void(0)", :title => "delete", :class => "post_delete action"
+
+.post_title{ :id => post.id }= post.name
2 app/views/posts/_link.html.haml
@@ -0,0 +1,2 @@
+.post_thumb{:id => link.id}
+ =image_tag link.image(:extra_small)
1 app/views/posts/edit.html.haml
@@ -0,0 +1 @@
+=render :partial => 'builder'
14 app/views/posts/index.html.haml
@@ -0,0 +1,14 @@
+%ul#post_messages
+ - if @posts.blank?
+ %li= "No posts links found!"
+
+= link_to "Add a Post", new_post_path
+
+.space
+
+%ul#post_list
+ - for post in @posts
+ %li
+ = render :partial => 'index_actions', :locals => { :post => post }
+
+=will_paginate @posts
1 app/views/posts/new.html.haml
@@ -0,0 +1 @@
+=render :partial => 'builder'
28 app/views/posts/show.html.haml
@@ -0,0 +1,28 @@
+:javascript
+ $(document).ready(function(){
+ $('.post_thumb').css({
+ width: '#{@post.thumbnail_width}px',
+ height: '#{@post.thumbnail_height}px'
+ });
+ $('#post_holder').css({
+ width: '#{@post.columns * (@post.thumbnail_width.to_i + 12)}px'
+ });
+ });
+
+#post_holder
+ -@post.ordered_links.each do |link|
+ .post_thumb
+ =link_to(image_tag(link.image(@post.thumbnail_dimension_name)), link.url, :target => "_blank", :title => link.description)
+
+-content_for(:menu) do
+ %li
+ %h2
+ Post
+ %ul
+ %li=link_to 'Edit', edit_post_path(@post)
+ %li=link_to 'Publish', 'javascript:void(0)', :id => 'publish_post'
+ %li.hidden=link_to 'Download Archive', "/posts/#{@post.id}/#{@post.name}.tgz", :id => "archive_link"
+ %li
+ .loading_gif.hidden
+ =image_tag 'ajax-loading.gif'
+ %span#loading_text processing...
7 app/views/source_urls/_index_actions.html.haml
@@ -0,0 +1,7 @@
+= link_to "&nbsp;", source_url, :title => "crawwwl", :class => "source_url_crawwwl action"
+
+= link_to "&nbsp;", source_url.url, :title => "check", :class => "source_url_check action", :target => "_blank"
+
+= link_to "&nbsp;", "javascript:void(0)", :title => "delete", :class => "source_url_delete action"
+
+.source_url_title{ :id => source_url.id }= source_url.url
18 app/views/source_urls/index.html.haml
@@ -0,0 +1,18 @@
+%ul#source_url_messages
+ - if @source_urls.blank?
+ %li No Source URLs found!
+
+= link_to "Add a Source URL", "javascript:void(0);", :id => "add_source_url"
+
+#add_source_url_snippet.hidden
+ %div
+ %input#url_field{:type => "text", :value => "http://" }
+ %input.button{:class => "cancel_source_url", :type => "button", :value => "Cancel" }
+ %input.button{:class => "save_source_url", :type => "button", :value => "Save" }
+
+.space
+
+%ul#source_url_list
+ - for url in @source_urls
+ %li
+ = render :partial => 'index_actions', :locals => { :source_url => url }
33 app/views/source_urls/show.html.haml
@@ -0,0 +1,33 @@
+
+%h1.title=@source_url.url
+
+.space
+
+%a#save_links.hidden{:href => "javascript:void(0)"}Save These Links
+
+%a#crawwwl_link{:href => "javascript:void(0)"}Extract Links From This URL
+
+.space
+
+.loading_gif.hidden
+ =image_tag 'ajax-loading.gif'
+ %span#loading_text loading links...
+
+%ul#extracted_links.hidden
+
+-content_for :menu do
+ %li
+ %h2
+ Rules
+ %ul
+ %li
+ = link_to "Add a Rule", "javascript:void(0)", :id => "add_rule", :class => "hidden"
+ #rule_div.hidden
+ %input{:name => "type", :value => "ignore", :type => "radio", :checked => "checked"}
+ Ignore links containing
+ %br
+ %input{:name => "type", :value => "keep", :type => "radio"}
+ Keep links containing
+ %br
+ %input.sm-field#rule_field{ :type => "text" }
+ %input.button#rule_add_button{ :type => "button", :value => "Add" }
2 app/views/welcome/index.html.haml
@@ -0,0 +1,2 @@
+%h1.title
+ Welcome to Crawwwl
110 config/boot.rb
@@ -0,0 +1,110 @@
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
+
+RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
+
+module Rails
+ class << self
+ def boot!
+ unless booted?
+ preinitialize
+ pick_boot.run
+ end
+ end
+
+ def booted?
+ defined? Rails::Initializer
+ end
+
+ def pick_boot
+ (vendor_rails? ? VendorBoot : GemBoot).new
+ end
+
+ def vendor_rails?
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
+ end
+
+ def preinitialize
+ load(preinitializer_path) if File.exist?(preinitializer_path)
+ end
+
+ def preinitializer_path
+ "#{RAILS_ROOT}/config/preinitializer.rb"
+ end
+ end
+
+ class Boot
+ def run
+ load_initializer
+ Rails::Initializer.run(:set_load_path)
+ end
+ end
+
+ class VendorBoot < Boot
+ def load_initializer
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ Rails::Initializer.run(:install_gem_spec_stubs)
+ Rails::GemDependency.add_frozen_gem_path
+ end
+ end
+
+ class GemBoot < Boot
+ def load_initializer
+ self.class.load_rubygems
+ load_rails_gem
+ require 'initializer'
+ end
+
+ def load_rails_gem
+ if version = self.class.gem_version
+ gem 'rails', version
+ else
+ gem 'rails'
+ end
+ rescue Gem::LoadError => load_error
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+ exit 1
+ end
+
+ class << self
+ def rubygems_version
+ Gem::RubyGemsVersion rescue nil
+ end
+
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
+ end
+ end
+
+ def load_rubygems
+ min_version = '1.3.2'
+ require 'rubygems'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ exit 1
+ end
+
+ rescue LoadError
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
+
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
+ end
+
+ private
+ def read_environment_rb
+ File.read("#{RAILS_ROOT}/config/environment.rb")
+ end
+ end
+ end
+end
+
+# All that for this:
+Rails.boot!
23 config/database.yml
@@ -0,0 +1,23 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 15000 # custom
+
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
19 config/environment.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ config.gem 'rmagick', :version => "2.12.2"
+ config.gem 'haml', :version => "2.2.2"
+ config.gem 'thoughtbot-factory_girl', :lib => 'factory_girl', :source => 'http://gems.github.com'
+ config.gem 'mislav-will_paginate', :version => '~> 2.3.11', :lib => 'will_paginate', :source => 'http://gems.github.com'
+ config.gem 'thoughtbot-paperclip', :lib => 'paperclip', :source => 'http://gems.github.com'
+ config.gem 'curb', :version => "0.5.4.0"
+ config.gem 'nokogiri', :version => "1.3.3"
+end
+
+ActiveRecord::Base.default_timezone = :utc
17 config/environments/development.rb
@@ -0,0 +1,17 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request. This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs = true
+config.action_controller.perform_caching = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
28 config/environments/production.rb
@@ -0,0 +1,28 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching = true
+config.action_view.cache_template_loading = true
+
+# See everything in the log (default is :info)
+# config.log_level = :debug
+
+# Use a different logger for distributed setups
+# config.logger = SyslogLogger.new
+
+# Use a different cache store in production
+# config.cache_store = :mem_cache_store
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host = "http://assets.example.com"
+
+# Disable delivery errors, bad email addresses will be ignored
+# config.action_mailer.raise_delivery_errors = false
+
+# Enable threaded mode
+# config.threadsafe!
31 config/environments/test.rb
@@ -0,0 +1,31 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
+config.cache_classes = true
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching = false
+config.action_view.cache_template_loading = true
+
+# Disable request forgery protection in test environment
+config.action_controller.allow_forgery_protection = false
+
+# Tell Action Mailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
+
+# Use SQL instead of Active Record's schema dumper when creating the test database.
+# This is necessary if your schema can't be completely dumped by the schema dumper,
+# like if you have constraints or database-specific column types
+# config.active_record.schema_format = :sql
+
+config.gem 'rspec-rails', :lib => false
+config.gem 'rspec', :lib => false
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
5 config/initializers/delayed_job_config.rb
@@ -0,0 +1,5 @@
+silence_warnings do
+ Delayed::Worker::sleep_delay = 5
+ Delayed::Job::max_attempts = 2
+ Delayed::Job::max_run_time = 5.minutes
+end
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
21 config/initializers/new_rails_defaults.rb
@@ -0,0 +1,21 @@
+# Be sure to restart your server when you modify this file.
+
+# These settings change the behavior of Rails 2 apps and will be defaults
+# for Rails 3. You can remove this initializer when Rails 3 is released.
+
+if defined?(ActiveRecord)
+ # Include Active Record class name as root for JSON serialized output.
+ ActiveRecord::Base.include_root_in_json = true
+
+ # Store the full class name (including module namespace) in STI type column.
+ ActiveRecord::Base.store_full_sti_class = true
+end
+
+ActionController::Routing.generate_best_match = false
+
+# Use ISO 8601 format for JSON serialized times and dates.
+ActiveSupport.use_standard_json_time_format = true
+
+# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
+# if you're including raw json in an HTML page.
+ActiveSupport.escape_html_entities_in_json = false
15 config/initializers/session_store.rb
@@ -0,0 +1,15 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying cookie session data integrity.
+# If you change this key, all old sessions will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+ActionController::Base.session = {
+ :key => '_crawwwl2.0_session',
+ :secret => 'a426afd6a6cc9ee5a116bcbdf2632f5ffd723f0ef2bab6d39b4dfad6488232649163239ef284db71ac973f692f543f97fb2cc70c687f7e54b1053f956903d29b'
+}
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rake db:sessions:create")
+# ActionController::Base.session_store = :active_record_store
5 config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
37 config/routes.rb
@@ -0,0 +1,37 @@
+ActionController::Routing::Routes.draw do |map|
+
+ map.resources :source_urls,
+ :except => [:edit],
+ :member => {
+ :index_actions => :get,
+ :extract_links => :get,
+ :save_links => :post
+ }
+
+ map.resources :links,
+ :member => {
+ :index_actions => :get,
+ :assign_image => :put,
+ :upload_image => :put,
+ :details => :get,
+ :update_description => :put,
+ :mark_as_bad => :put
+ },
+ :collection => {
+ :next => :get
+ }
+
+ map.resources :posts,
+ :member => {
+ :change_name => :put,
+ :publish => :post,
+ :poll_for_publish => :get
+ }
+
+ map.more_posts '/posts/more/:number',
+ :controller => :posts,
+ :action => :more,
+ :method => :get
+
+ map.root :controller => "welcome"
+end
14 db/migrate/20091011213055_create_source_urls.rb
@@ -0,0 +1,14 @@
+class CreateSourceUrls < ActiveRecord::Migration
+ def self.up
+ create_table :source_urls do |t|
+ t.string :url
+ t.text :html
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :source_urls
+ end
+end
24 db/migrate/20091014161553_create_links.rb
@@ -0,0 +1,24 @@
+class CreateLinks < ActiveRecord::Migration
+ def self.up
+ create_table :links do |t|
+ t.integer :source_url_id
+ t.integer :post_id
+ t.string :url
+ t.string :status, :default => "uncrawwwled"
+ t.string :original_image_path, :default => "/images/missing.png"
+ t.text :description
+ t.text :images
+
+ # paperclip
+ t.string :image_file_name
+ t.string :image_content_type
+ t.string :image_file_size
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :links
+ end
+end
16 db/migrate/20091102170506_create_posts.rb
@@ -0,0 +1,16 @@
+class CreatePosts < ActiveRecord::Migration
+ def self.up
+ create_table :posts do |t|
+ t.string :name
+ t.string :thumbnail_dimension
+ t.integer :total_links
+ t.integer :columns
+ t.string :link_order
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :posts
+ end
+end
20 db/migrate/20091108212340_create_delayed_jobs.rb
@@ -0,0 +1,20 @@
+class CreateDelayedJobs < ActiveRecord::Migration
+ def self.up
+ create_table :delayed_jobs, :force => true do |table|
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
+ table.text :handler # YAML-encoded string of the object that will do work
+ table.text :last_error # reason for last failure (See Note below)
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
+ table.datetime :locked_at # Set when a client is working on this object
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
+ table.string :locked_by # Who is working on this object (if locked)
+ table.timestamps
+ end
+
+ end
+
+ def self.down
+ drop_table :delayed_jobs
+ end
+end
9 db/migrate/20091112172527_add_image_path_prefix_to_posts.rb
@@ -0,0 +1,9 @@
+class AddImagePathPrefixToPosts < ActiveRecord::Migration
+ def self.up
+ add_column :posts, :image_path_prefix, :string, :default => ''
+ end
+
+ def self.down
+ remove_column :posts, :image_path_prefix
+ end
+end
60 db/schema.rb
@@ -0,0 +1,60 @@
+# This file is auto-generated from the current state of the database. Instead of editing this file,
+# please use the migrations feature of Active Record to incrementally modify your database, and
+# then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your database schema. If you need
+# to create the application database on another system, you should be using db:schema:load, not running
+# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20091112172527) do
+
+ create_table "delayed_jobs", :force => true do |t|
+ t.integer "priority", :default => 0
+ t.integer "attempts", :default => 0
+ t.text "handler"
+ t.text "last_error"
+ t.datetime "run_at"
+ t.datetime "locked_at"
+ t.datetime "failed_at"
+ t.string "locked_by"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "links", :force => true do |t|
+ t.integer "source_url_id"
+ t.integer "post_id"
+ t.string "url"
+ t.string "status", :default => "uncrawwwled"
+ t.string "original_image_path", :default => "/images/missing.png"
+ t.text "description"
+ t.text "images"
+ t.string "image_file_name"
+ t.string "image_content_type"
+ t.string "image_file_size"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "posts", :force => true do |t|
+ t.string "name"
+ t.string "thumbnail_dimension"
+ t.integer "total_links"
+ t.integer "columns"
+ t.string "link_order"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "image_path_prefix", :default => ""
+ end
+
+ create_table "source_urls", :force => true do |t|
+ t.string "url"
+ t.text "html"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+end
7 db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Major.create(:name => 'Daley', :city => cities.first)
2 doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
108 lib/link_job.rb
@@ -0,0 +1,108 @@
+class LinkJob < Struct.new(:link_id)
+
+ def perform
+ begin
+ link = Link.find_by_id(link_id)
+ get_images(link) if link
+ rescue Exception => e
+ link.update_attribute(:status, 'bad') if link
+ puts "Uncaught exception bubbled up: \n#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")} "
+ end
+ end
+
+ def get_images(link)
+ puts "\nProcessing: #{link.url}"
+
+ if link.url.downcase.match(/jpg|jpeg|gif|tiff|png|bmp^/)
+ img_urls = [link.url]
+ else
+ puts "\nAttempting to get html for link ##{link.id} \n\n"
+ html = open(link.url)
+ raise "HTML retrieval FAIL" unless html
+ doc = Nokogiri::HTML(html)
+ description = doc.at("title").inner_text
+ img_urls = extract_image_urls(link, doc)
+ end
+
+ raise "No image URLs found FAIL" if img_urls.nil? or img_urls.empty?
+
+ puts "Fetching #{img_urls.length} image URLs:"
+
+ image_dir = File.dirname(__FILE__) + "/../public/images/link/#{link.id}/"
+ FileUtils.mkdir_p image_dir
+ file_ara = []
+
+ img_urls.each_with_index do |img_url,i|
+ break if i > 200
+ filename = "#{i}_#{img_url.to_s.sub(/^.*\//,'')}"
+ filename.gsub!(/\?|\+|\;|\=|\%/,'') # remove stuff that messes with rails routing
+ ((i+1) % 10 == 0) ? (print i+1) : (print '.')
+ local_path = image_dir + filename
+ public_path = "/images/link/#{link.id}/" + filename
+
+ begin
+ file = open(img_url)
+ File.open(local_path, 'w') {|f| f.write(file) }
+
+ if File.exist?(local_path)
+ the_type = image_type(local_path)
+ if the_type == "unknown"
+ FileUtils.rm_f(local_path)
+ else
+ if not local_path.downcase.end_with?(the_type) # image has no extension
+ public_path = "#{public_path}#{the_type}"
+ FileUtils.mv(local_path, "#{local_path}#{the_type}")
+ end
+ file_ara << public_path
+ end
+ end
+ rescue => e
+ puts "Saving #{filename} failed \n#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
+ end
+ end
+ file_ara = file_ara.to_yaml
+ puts "\n#{file_ara}"
+ link.update_attributes(:status => 'crawwwled', :images => file_ara, :description => description)
+ puts "Done!"
+ end
+
+ def parse(uri)
+ begin
+ retval = URI::parse(uri)
+ rescue
+ retval = URI::parse(URI.escape(URI.unescape(uri))) # if escaped chars, unescape, then re-escape
+ end
+ retval
+ end
+
+ def image_type(file)
+ return 'unknown' unless File.exist?(file)
+ case IO.read(file, 10)
+ when /^GIF8/: '.gif'
+ when /^\x89PNG/: '.png'
+ when /^\211PNG/: '.png' # is this valid?
+ when /^\xff\xd8\xff\xe0\x00\x10JFIF/: '.jpg'
+ when /^\xff\xd8\xff\xe1(.*){2}Exif/: '.jpg'
+ else 'unknown'
+ end
+ end
+
+ def open(url)
+ Curl::Easy.perform(url.to_s) do |c|
+ c.follow_location = true
+ c.headers["User-Agent"]= "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)" # IE7
+ end.body_str
+ end
+
+ def extract_image_urls(link, doc)
+ image_urls = doc.search('img').map {|tag| tag[:src] || tag[:SRC] }.compact.uniq
+ image_urls.map! {|img| parse(img) }
+ image_urls.reject! {|img| img.is_a? String } # didn't parse; not a valid URI?
+ absolute_urls = image_urls.select {|img| img.absolute? }
+ relative_urls = image_urls.select {|img| img.relative? }
+ parent = parse(link.url)
+ relative_urls.map! {|img| parent + img } # absolutize
+ (absolute_urls + relative_urls).compact.uniq.reject {|img| img.to_s == "" }
+ end
+
+end
39 lib/post_job.rb
@@ -0,0 +1,39 @@
+class PostJob < Struct.new(:post_id)
+ def perform
+ @post = Post.find_by_id(post_id)
+
+ # build the template from haml
+ template = %Q{
+#crawwwl_post_holder{:style => 'width:' + (post.columns * (post.thumbnail_width.to_i + 12)).to_s + 'px;'}
+ -post.ordered_links.each do |link|
+ .crawwwl_link_thumb{:style => 'width:' + post.thumbnail_width + 'px;height:' + post.thumbnail_height + 'px;' }
+ %a{:href => link.url}
+ %img{:src => post.image_path_prefix + link.image.original_filename, :alt => link.description, :title => link.description}
+ }
+
+ # render the template
+ rendered_template = Haml::Engine.new(template).
+ render_proc(Object.new, :post).
+ call :post => @post
+
+ # create the directory
+ tar_dir = File.join(RAILS_ROOT, 'public', 'posts', post_id.to_s, @post.name)
+ FileUtils.mkdir_p tar_dir
+
+ # copy in the images
+ @post.ordered_links.each do |link|
+ img_path = link.image.path(@post.thumbnail_dimension_name)
+ FileUtils.cp(img_path, tar_dir)
+ end
+
+ # write the markup
+ html_file = File.open(File.join(tar_dir, '_index.html'), 'w') { |file| file.puts rendered_template }
+
+ # create the archive
+ system "tar czf #{tar_dir}.tgz -C #{File.join(RAILS_ROOT, 'public', 'posts', post_id.to_s)} #{@post.name}"
+
+ # establish links and update status
+ @post.links = @post.ordered_links
+ Link.update_all("status = 'live'", "post_id = #{@post.id}")
+ end
+end
30 public/404.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
30 public/422.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
30 public/500.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
+ </div>
+</body>
+</html>
0 public/favicon.ico
No changes.
BIN public/images/ajax-loading.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/arrow-down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/arrow-up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/bad_icon.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/black-70.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/black.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/cancel.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/cancel.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/cancel_old.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/check.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/close.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/database_lightning.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/edit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/gradient.jpeg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/header.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/help.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/img01.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/img02.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/img03.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/img04.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/marqueeHoriz.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/marqueeVert.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/missing.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/new.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/nextlabel.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/pattern_148-70.png
Diff not rendered.
BIN public/images/pattern_148.gif
Diff not rendered.
BIN public/images/prevlabel.gif
Diff not rendered.
BIN public/images/rails.png
Diff not rendered.
BIN public/images/remove.png
Diff not rendered.
BIN public/images/replace.png
Diff not rendered.
BIN public/images/spacer.gif
Diff not rendered.
BIN public/images/swap.png
Diff not rendered.
BIN public/images/trash.png
Diff not rendered.
58 public/javascripts/application.js
@@ -0,0 +1,58 @@
+$.fn.inPlaceEdit = function(url){
+ var field = $(this);
+
+ $(this).editInPlace({
+ url: url,
+ bg_over: '#333',
+ params: "_method=put&authenticity_token=" + encodeURIComponent(rails_authenticity_token),
+ error: function(json){ handle_errors(json); },
+ success: function(){
+ $('#source_url_messages').html('');
+ $('#link_messages').html('');
+ field.siblings('.source_url_check').attr('href', field.html());
+ field.siblings('.link_check').attr('href', field.html());
+ }
+ });
+
+};
+
+$(document).ready(function(){
+ var attempts = 0;
+
+ function poll_for_publish(path) {
+ attempts += 1;
+ $.ajax({
+ type: 'GET',
+ url: path,
+ success: function(){
+ $('.loading_gif').hide();
+ $('#archive_link').parent().show();
+ },
+ error: function(){
+ // not ready yet...
+ if(attempts > 20){
+ $('.loading_gif').hide();
+ $('#publish_post').parent().show();
+ attempts = 0;
+ alert('There was an error creating your archive. Please try again later.');
+ } else {
+ setTimeout(function(){
+ poll_for_publish(path);
+ }, 1000);
+ }
+ }
+ });
+ }
+
+ $('#publish_post').click(function(){
+ var sure = confirm('Create an archive of this post?');
+ if(!sure){ return false; }
+
+ $('#publish_post').parent().hide();
+ $('.loading_gif').show();
+
+ $.post(document.location.pathname + '/publish', function(){
+ poll_for_publish(document.location.pathname + '/poll_for_publish')
+ });
+ });
+});
298 public/javascripts/jquery-ui-1.7.2.custom.min.js
298 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
1,197 public/javascripts/jquery.Jcrop.js
@@ -0,0 +1,1197 @@
+/**
+ * jquery.Jcrop.js v0.9.8
+ * jQuery Image Cropping Plugin
+ * @author Kelly Hallman <khallman@gmail.com>
+ * Copyright (c) 2008-2009 Kelly Hallman - released under MIT License {{{
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,