diff options
61 files changed, 453 insertions, 285 deletions
@@ -3,6 +3,7 @@ source 'https://rubygems.org' gem 'rails', '4.0.0.beta1' gem 'mysql2' +gem 'dalli' gem 'unicorn' gem 'daemon-spawn', :require => 'daemon_spawn' @@ -18,9 +19,9 @@ group :assets do end gem 'haml-rails' +gem 'jbuilder' gem 'msgpack' gem 'omniauth-twitter', :github => "re4k/omniauth-twitter" gem 'em-twitter' -gem 'twitter' diff --git a/Gemfile.lock b/Gemfile.lock index d6c2a46..5638adf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,7 +33,7 @@ GEM multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.33) - arel (4.0.0.beta1) + arel (4.0.0.beta2) atomic (1.0.1) builder (3.1.4) coffee-rails (4.0.0.beta1) @@ -42,8 +42,9 @@ GEM coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.6.1) + coffee-script-source (1.6.2) daemon-spawn (0.4.2) + dalli (2.6.2) em-twitter (0.2.1) eventmachine (~> 1.0) http_parser.rb (~> 0.5) @@ -51,12 +52,10 @@ GEM em-work_queue (0.0.1) eventmachine erubis (2.7.0) - eventmachine (1.0.1) + eventmachine (1.0.3) execjs (1.4.0) multi_json (~> 1.0) - faraday (0.8.6) - multipart-post (~> 1.1) - haml (4.0.0) + haml (4.0.1) tilt haml-rails (0.4) actionpack (>= 3.1, < 4.1) @@ -67,6 +66,8 @@ GEM hike (1.2.1) http_parser.rb (0.5.3) i18n (0.6.4) + jbuilder (1.0.2) + activesupport (>= 3.0.0) json (1.7.7) kaminari (0.14.1) actionpack (>= 3.0.0) @@ -77,10 +78,9 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.21) - minitest (4.6.2) + minitest (4.7.0) msgpack (0.5.4) - multi_json (1.6.1) - multipart-post (1.2.0) + multi_json (1.7.2) mysql2 (0.3.11) oauth (0.4.7) omniauth (1.1.3) @@ -113,7 +113,7 @@ GEM rake (10.0.3) rdoc (3.12.2) json (~> 1.4) - sass (3.2.6) + sass (3.2.7) sass-rails (4.0.0.beta1) railties (>= 4.0.0.beta, < 5.0) sass (>= 3.1.10) @@ -132,15 +132,11 @@ GEM thor (0.17.0) thread_safe (0.1.0) atomic - tilt (1.3.4) + tilt (1.3.6) treetop (1.4.12) polyglot polyglot (>= 0.3.1) - twitter (4.5.0) - faraday (~> 0.8, < 0.10) - multi_json (~> 1.0) - simple_oauth (~> 0.2) - tzinfo (0.3.36) + tzinfo (0.3.37) uglifier (1.3.0) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) @@ -155,9 +151,11 @@ PLATFORMS DEPENDENCIES coffee-rails (~> 4.0.0.beta1) daemon-spawn + dalli em-twitter em-work_queue haml-rails + jbuilder kaminari msgpack mysql2 @@ -165,6 +163,5 @@ DEPENDENCIES rails (= 4.0.0.beta1) rails_config sass-rails (~> 4.0.0.beta1) - twitter uglifier (>= 1.0.3) unicorn diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9157caa..93eb126 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -16,16 +16,4 @@ class ApplicationController < ActionController::Base end return 1 end - - def prepare_cache - # check - #return unless @items - - #@favorite_cache = Favorite.where(@items.map{|m| "tweet_id = #{m.id}"}.join(" OR ")).sort_by{|m| m.id}.group_by{|m| m.tweet_id} - #@retweet_cache = Retweet.where(@items.map{|m| "tweet_id = #{m.id}"}.join(" OR ")).sort_by{|m| m.id}.group_by{|m| m.tweet_id} - #@user_cache = Hash[User.where( - # (@items.to_a + @favorite_cache.values + @retweet_cache.values).flatten.map{|m| m.user_id}.uniq - # .map{|m| "id = #{m}"}.join(" OR ")) - # .map{|m| [m.id, m]}] - end end diff --git a/app/controllers/i_controller.rb b/app/controllers/i_controller.rb index 74a1701..7ee38da 100644 --- a/app/controllers/i_controller.rb +++ b/app/controllers/i_controller.rb @@ -1,12 +1,16 @@ class IController < ApplicationController - def show - id = params[:id].to_i - items = Tweet.where(:id => id) - if items.count > 0 - @items = items.page(1) - @title = "\"#{ApplicationController.helpers.strip_tags(ApplicationController.helpers.format_tweet_text(items.first.text))[0...30]}\" from @#{items.first.user.screen_name}" - else - @items = [] - end + def best + @items = Tweet + .reacted + .order_by_reactions + .limit(Settings.page_per) + end + + def recent + @items = Tweet + .recent + .reacted + .order_by_reactions + .limit(Settings.page_per) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 3a75894..20afa15 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -6,12 +6,19 @@ class SessionsController < ApplicationController user = Account.find_or_initialize_by(:user_id => auth["uid"]) user.oauth_token = auth["credentials"]["token"] user.oauth_token_secret = auth["credentials"]["secret"] + user.consumer_version = Settings.consumer_version user.save! session[:user_id] = user.user_id session[:screen_name] = auth["info"]["nickname"] - UNIXSocket.open(Settings.register_server_path) do |socket| - socket.write({:type => "register", :id => user.id, :user_id => user.user_id}.to_msgpack) + begin + UNIXSocket.open(Settings.register_server_path) do |socket| + socket.write({:type => "register", :id => user.id, :user_id => user.user_id}.to_msgpack) + end + rescue Errno::ECONNREFUSED + # receiver not started? + warn $! + warn $@ end redirect_to root_url diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 670c1c6..1ebb5a6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,84 +1,83 @@ class UsersController < ApplicationController + def show + tweet_id = Integer(params[:id]) + + @item = Tweet.find(tweet_id) + @user = @item.user + helpers = ApplicationController.helpers + @title = "\"#{helpers.strip_tags(helpers.format_tweet_text(@item.text))[0...30]}\" from @#{@item.user.screen_name}" + + respond_to do |format| + format.html do + prepare_info + end + + format.json do + @trim_user = params[:trim_user] == "true" + end + end + end + def best - page = get_page_number(params) - screen_name = params[:screen_name] - user = User.where(:screen_name => screen_name).first - if user - @items = user.tweets - .where("favorites_count > 0 OR retweets_count > 0") - .order("COALESCE(favorites_count, 0) + COALESCE(retweets_count, 0) DESC") - .page(page) - .per(Settings.page_per) - else - @items = [] + user = render_timeline(params) do |tweets, user| + tweets.of(user).reacted.order_by_reactions end - @title = "@#{screen_name}'s Best Tweets" + @title = "@#{user.screen_name}'s Best Tweets" end def recent - page = get_page_number(params) - screen_name = params[:screen_name] - user = User.where(:screen_name => screen_name).first - if user - @items = user.tweets - .where("favorites_count > 0 OR retweets_count > 0") - .order("id DESC") - .page(page) - .per(Settings.page_per) - else - @items = [] + user = render_timeline(params) do |tweets, user| + tweets.of(user).recent.reacted.order_by_reactions end - @title = "@#{screen_name}'s Newest Favorited Tweets" + @title = "@#{user.screen_name}'s Recent Best Tweets" end def timeline - page = get_page_number(params) - screen_name = params[:screen_name] - user = User.where(:screen_name => screen_name).first - if user - @items = user.tweets - .order("id DESC") - .page(page) - .per(Settings.page_per) - else - @items = [] + user = render_timeline(params) do |tweets, user| + if params[:tweets] == "all" + tweets.of(user).order_by_id + else + tweets.of(user).reacted.order_by_id + end + end + @title = "@#{user.screen_name}'s Newest Tweets" + + if user.protected + raise Exception # FIXME end - @title = "@#{screen_name}'s Newest Tweets" end def my + user = render_timeline(params) do |tweets, user| + tweets.discovered(user).order_by_id + end + @title = "@#{user.screen_name}'s Recent Discoveries" + end + + private + def render_timeline(params, &g) page = get_page_number(params) screen_name = params[:screen_name] - user = User.where(:screen_name => screen_name).first - if user - @items = Tweet - .where("id IN (SELECT tweet_id FROM (" + - "SELECT tweet_id FROM favorites WHERE user_id = #{user.id} " + - "UNION " + - "SELECT tweet_id FROM retweets WHERE user_id = #{user.id}" + - ") AS rf)") - .order("id DESC") - .page(page) - .per(Settings.page_per) - else - @items = [] + @user = User.where(:screen_name => screen_name).first + unless @user + raise ActiveRecord::RecordNotFound.new(screen_name) end - @title = "@#{screen_name}'s Recent Discoveries" - prepare_cache - end - def info - screen_name = params[:screen_name] - user = User.where(:screen_name => screen_name).first - if user - @tweets_count = user.tweets.count - @favorites_count = user.favorites.count - @retweets_count = user.retweets.count - @favorited_count = user.tweets.inject(0){|s, m| s + m.favorites_count} - @retweeted_count = user.tweets.inject(0){|s, m| s + m.retweets_count} - else - @info = nil + @items = g.call(Tweet, @user) + .page(page) + .per(Settings.page_per) + + respond_to do |format| + format.html do + prepare_info + end + + format.json do + @trim_user = params[:trim_user] == "true" + end end + + return @user end end diff --git a/app/models/account.rb b/app/models/account.rb index 50229f9..31fe26d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -1,2 +1,5 @@ class Account < ActiveRecord::Base + def user + User.cached(user_id) + end end diff --git a/app/models/favorite.rb b/app/models/favorite.rb index c764d08..6e15dc4 100644 --- a/app/models/favorite.rb +++ b/app/models/favorite.rb @@ -1,4 +1,12 @@ class Favorite < ActiveRecord::Base belongs_to :tweet, :counter_cache => true belongs_to :user + + def user + User.cached(user_id) + end + + def tweet + Tweet.cached(tweet_id) + end end diff --git a/app/models/retweet.rb b/app/models/retweet.rb index 34da286..296b877 100644 --- a/app/models/retweet.rb +++ b/app/models/retweet.rb @@ -1,4 +1,12 @@ class Retweet < ActiveRecord::Base belongs_to :tweet, :counter_cache => true belongs_to :user + + def user + User.cached(user_id) + end + + def tweet + Tweet.cached(tweet_id) + end end diff --git a/app/models/tweet.rb b/app/models/tweet.rb index a1124f9..6b6311d 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -2,4 +2,42 @@ class Tweet < ActiveRecord::Base belongs_to :user has_many :favorites, :dependent => :delete_all has_many :retweets, :dependent => :delete_all + + scope :recent, -> do + where("tweeted_at > ?", Time.zone.now - 3.days) + end + + scope :reacted, -> do + where("favorites_count > 0 OR retweets_count > 0") + end + + scope :order_by_id, -> do + order("id DESC") + end + + scope :order_by_reactions, -> do + order("COALESCE(favorites_count, 0) + COALESCE(retweets_count, 0) DESC") + end + + scope :discovered, -> user do + where("id IN (" + + "SELECT tweet_id FROM favorites WHERE user_id = ?" + + " UNION ALL " + + "SELECT tweet_id FROM retweets WHERE user_id = ?" + + ")", user.id, user.id) + end + + scope :of, -> user do + where("user_id = ?", user.id) + end + + def self.cached(id) + Rails.cache.fetch("tweet/#{id}", :expires_in => 3.hour) do + where(:id => id).first + end + end + + def user + User.cached(user_id) + end end diff --git a/app/models/user.rb b/app/models/user.rb index d268fcb..b93eac9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,11 +5,21 @@ class User < ActiveRecord::Base attrs = {:id => u} end attrs[:profile_image_url] ||= ActionController::Base.helpers.asset_path("missing_profile_image.png") - attrs[:name] ||= "Missing: id=#{u}" + attrs[:name] ||= "Missing name: #{u}" super(attrs) end has_many :tweets, :dependent => :delete_all has_many :favorites, :dependent => :delete_all has_many :retweets, :dependent => :delete_all + + def self.cached(uid) + Rails.cache.fetch("user/#{uid}", :expires_in => 1.hour) do + where(:id => uid).first + end + end + + def registered? + Account.exists?(:user_id => id) + end end diff --git a/app/views/errors/error_404.json.jbuilder b/app/views/errors/error_404.json.jbuilder new file mode 100644 index 0000000..a58331f --- /dev/null +++ b/app/views/errors/error_404.json.jbuilder @@ -0,0 +1,4 @@ +json.error do |json| + json.status 404 + json.message "Not found" +end diff --git a/app/views/errors/error_500.json.jbuilder b/app/views/errors/error_500.json.jbuilder new file mode 100644 index 0000000..1cb2a1b --- /dev/null +++ b/app/views/errors/error_500.json.jbuilder @@ -0,0 +1,4 @@ +json.error do |json| + json.status 500 + json.message "Internal error" +end diff --git a/app/views/i/_list.html.haml b/app/views/i/_list.html.haml new file mode 100644 index 0000000..91a14b4 --- /dev/null +++ b/app/views/i/_list.html.haml @@ -0,0 +1,2 @@ +.items + = render :partial => "shared/tweet", :collection => @items, :as => :item diff --git a/app/views/i/best.html.haml b/app/views/i/best.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/i/best.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/i/recent.html.haml b/app/views/i/recent.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/i/recent.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/i/show.haml b/app/views/i/show.haml deleted file mode 100644 index 2672287..0000000 --- a/app/views/i/show.haml +++ /dev/null @@ -1 +0,0 @@ -= render :partial => "shared/tweets", :locals => {:items => @items} diff --git a/app/views/layouts/application.haml b/app/views/layouts/application.html.haml index b36d91a..5627ac7 100644 --- a/app/views/layouts/application.haml +++ b/app/views/layouts/application.html.haml @@ -7,7 +7,7 @@ .container .left %h1>< - Aclog + = link_to "Aclog", :controller => "main", :action => "index" %div - if session[:screen_name] %span= link_to "@#{session[:screen_name]}", :controller => "users", :action => "best", :screen_name => session[:screen_name] diff --git a/app/views/main/index.haml b/app/views/main/index.haml deleted file mode 100644 index bf7e2eb..0000000 --- a/app/views/main/index.haml +++ /dev/null @@ -1,29 +0,0 @@ -%p - %strong (´へεへ`*) < トップページだよ〜〜 -%p - Favstar クローンです。UserStreams つかってます。 - = link_to "@KOBA789", "https://twitter.com/KOBA789" - さんにサーバーを貸していただけることになりました…!!ありがとうございます!ありがとうございます!!! -%strong まだ開発途中段階のものです。うごかなかったらごめんね -%strong 動作のテスト中ですので、途中で登録をうちきったりあまりにふぁぼられ・ふぁぼりが多いユーザーの登録を削除する可能性もあります。 -%div - URLとかについて - %div - best: - = link_to "/cat", "/cat" - %div - recent: - = link_to "/cat/recent", "/cat/recent" - %div - timeline(exclude RTs): - = link_to "/cat/timeline", "/cat/timeline" - %div - discovery: - = link_to "/cat/my", "/cat/my" - %div - こんどほかのところもFavstar互換にする - %div - API つくりたいですねハイ -%div - = link_to "@cat", "https://twitter.com/cat" - diff --git a/app/views/main/index.html.haml b/app/views/main/index.html.haml new file mode 100644 index 0000000..dfebde1 --- /dev/null +++ b/app/views/main/index.html.haml @@ -0,0 +1,42 @@ +%p + %strong (´へεへ`*) < トップページだよ〜〜 +%p + Favstar クローンです。UserStreams つかってます。 + = link_to "@KOBA789", "https://twitter.com/KOBA789" + さんにサーバーを貸していただけることになりました…!!ありがとうございます!ありがとうございます!!! +%p + %strong まだ開発途中段階のものです。うごかなかったらごめんね +%p + Read/Write 要求するけど今のところ UserStreams しか使ってないです。こんど Favstar みたいにページ内から Fav/RT できるようにしたいなあと… +%p + %strong 動作のテスト中ですので、途中で登録をうちきったりあまりにふぁぼられ・ふぁぼりが多いユーザーの登録を削除する可能性もあります。 +%p + 登録ユーザーだったら一番下に統計(?)が表示されてるんじゃないですかね +%div + URLとかについて + %div + best: + = link_to "/cat", "/cat" + %div + recent(最新3日のbest): + = link_to "/cat/recent", "/cat/recent" + %div + timeline(Favstar の Recent、ただ0Fav0RTも含む): + = link_to "/cat/timeline", "/cat/timeline" + %div + discovery: + = link_to "/cat/my", "/cat/my" + %div + %div + 全体のbest: + = link_to "/i/best", "/i/best" + %div + 全体の最新3日のbest + = link_to "/i/recent", "/i/recent" + %div + URL 変えちゃうかもしれないです + %div + API つくりたいですねハイ +%div + = link_to "@cat", "https://twitter.com/cat" + diff --git a/app/views/shared/_tweet.haml b/app/views/shared/_tweet.html.haml index 2f7adb2..e706079 100644 --- a/app/views/shared/_tweet.haml +++ b/app/views/shared/_tweet.html.haml @@ -15,7 +15,7 @@ %span.twitter_bird = link_to image_tag("bird_gray_16.png", :alt => "Twitter"), twitter_status_url(item), :target => "_blank" %span.created_at - = link_to format_tweet_created_at(item.tweeted_at), :controller => "i", :action => "show", :id => item.id + = link_to format_tweet_created_at(item.tweeted_at), :controller => "users", :action => "show", :id => item.id %span.source = raw format_source_text(item.source) .stats @@ -26,7 +26,7 @@ %span.type= type .height_fix %ul.favoriters.clearfix - - actions.take(params[:controller] == "i" && params[:action] == "show" ? actions.size : 20).each do |a| | + - actions.take(params[:controller] == "users" && params[:action] == "show" ? actions.size : 20).each do |a| | - m = a.user || User.new %li - if m.screen_name diff --git a/app/views/shared/_tweet.json.jbuilder b/app/views/shared/_tweet.json.jbuilder new file mode 100644 index 0000000..a6c22ae --- /dev/null +++ b/app/views/shared/_tweet.json.jbuilder @@ -0,0 +1,19 @@ +json.(item, :id, :text, :source, :tweeted_at, :favorites_count, :retweets_count) + +json.user do |json| + json.partial! "shared/user", :user => item.user +end + +unless @trim_user + json.favorites item.favorites.order("id") do |json, favorite| + json.user do |json| + json.partial! "shared/user", :user => favorite.user || User.new + end + end + json.retweets item.retweets.order("id") do |json, retweet| + json.id retweet.id + json.user do |json| + json.partial! "shared/user", :user => retweet.user || User.new + end + end +end diff --git a/app/views/shared/_tweets.haml b/app/views/shared/_tweets.haml deleted file mode 100644 index 681b9cc..0000000 --- a/app/views/shared/_tweets.haml +++ /dev/null @@ -1,4 +0,0 @@ -.items - = render :partial => "shared/tweet", :collection => items, :as => :item, :local => {:showallusers => @showallusers} -- if items.size > 0 - = paginate items diff --git a/app/views/shared/_user.json.jbuilder b/app/views/shared/_user.json.jbuilder new file mode 100644 index 0000000..a0447c0 --- /dev/null +++ b/app/views/shared/_user.json.jbuilder @@ -0,0 +1,2 @@ +json.(user, :id, :screen_name, :name, :profile_image_url) + diff --git a/app/views/shared/_user_info.html.haml b/app/views/shared/_user_info.html.haml new file mode 100644 index 0000000..6188bdc --- /dev/null +++ b/app/views/shared/_user_info.html.haml @@ -0,0 +1,17 @@ +- if user.registered? + %ul + %li + Tweets: + = user.tweets.length + %li + Favorites: + = user.favorites.length + %li + Retweets: + = user.retweets.length + %li + Favorited: + = user.tweets.inject(0){|s, m| s + m.favorites_count} + %li + Retweeted: + = user.tweets.inject(0){|s, m| s + m.retweets_count} diff --git a/app/views/shared/_user_nav.html.haml b/app/views/shared/_user_nav.html.haml new file mode 100644 index 0000000..93c7d35 --- /dev/null +++ b/app/views/shared/_user_nav.html.haml @@ -0,0 +1,11 @@ +- if user + .links + %ul + %li + = link_to "Best", :controller => "users", :action => "best", :screen_name => user.screen_name + %li + = link_to "Recent", :controller => "users", :action => "recent", :screen_name => user.screen_name + %li + = link_to "Timeline", :controller => "users", :action => "timeline", :screen_name => user.screen_name + %li + = link_to "Discovered", :controller => "users", :action => "my", :screen_name => user.screen_name diff --git a/app/views/users/_list.html.haml b/app/views/users/_list.html.haml new file mode 100644 index 0000000..3147f1b --- /dev/null +++ b/app/views/users/_list.html.haml @@ -0,0 +1,6 @@ += render :partial => "shared/user_nav", :locals => {:user => @user} +.items + = render :partial => "shared/tweet", :collection => @items, :as => :item +- if @items + = paginate @items += render :partial => "shared/user_info", :locals => {:user => @user} diff --git a/app/views/users/best.haml b/app/views/users/best.haml deleted file mode 100644 index 2672287..0000000 --- a/app/views/users/best.haml +++ /dev/null @@ -1 +0,0 @@ -= render :partial => "shared/tweets", :locals => {:items => @items} diff --git a/app/views/users/best.html.haml b/app/views/users/best.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/users/best.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/users/best.json.jbuilder b/app/views/users/best.json.jbuilder new file mode 100644 index 0000000..d2637d3 --- /dev/null +++ b/app/views/users/best.json.jbuilder @@ -0,0 +1,4 @@ +json.array! @items do |json, item| + json.partial! "shared/tweet", :item => item +end + diff --git a/app/views/users/info.haml b/app/views/users/info.haml deleted file mode 100644 index e326422..0000000 --- a/app/views/users/info.haml +++ /dev/null @@ -1,10 +0,0 @@ -Tweets: - = @tweets_count -Favorite: - = @favorites_count -Retweets: - = @retweets_count -Favorited: - = @favorited_count -Retweetted: - = @retweeted_count diff --git a/app/views/users/my.haml b/app/views/users/my.haml deleted file mode 100644 index 2672287..0000000 --- a/app/views/users/my.haml +++ /dev/null @@ -1 +0,0 @@ -= render :partial => "shared/tweets", :locals => {:items => @items} diff --git a/app/views/users/my.html.haml b/app/views/users/my.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/users/my.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/users/my.json.jbuilder b/app/views/users/my.json.jbuilder new file mode 100644 index 0000000..d2637d3 --- /dev/null +++ b/app/views/users/my.json.jbuilder @@ -0,0 +1,4 @@ +json.array! @items do |json, item| + json.partial! "shared/tweet", :item => item +end + diff --git a/app/views/users/recent.haml b/app/views/users/recent.haml deleted file mode 100644 index 2672287..0000000 --- a/app/views/users/recent.haml +++ /dev/null @@ -1 +0,0 @@ -= render :partial => "shared/tweets", :locals => {:items => @items} diff --git a/app/views/users/recent.html.haml b/app/views/users/recent.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/users/recent.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/users/recent.json.jbuilder b/app/views/users/recent.json.jbuilder new file mode 100644 index 0000000..d2637d3 --- /dev/null +++ b/app/views/users/recent.json.jbuilder @@ -0,0 +1,4 @@ +json.array! @items do |json, item| + json.partial! "shared/tweet", :item => item +end + diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 0000000..f56e0c0 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,4 @@ += render :partial => "shared/user_nav", :locals => {:user => @user} +.items + = render :partial => "shared/tweet", :locals => {:item => @item} += render :partial => "shared/user_info", :locals => {:user => @user} diff --git a/app/views/users/show.json.jbuilder b/app/views/users/show.json.jbuilder new file mode 100644 index 0000000..049306e --- /dev/null +++ b/app/views/users/show.json.jbuilder @@ -0,0 +1,2 @@ +json.partial! "shared/tweet", :item => @items.first + diff --git a/app/views/users/timeline.haml b/app/views/users/timeline.haml deleted file mode 100644 index 2672287..0000000 --- a/app/views/users/timeline.haml +++ /dev/null @@ -1 +0,0 @@ -= render :partial => "shared/tweets", :locals => {:items => @items} diff --git a/app/views/users/timeline.html.haml b/app/views/users/timeline.html.haml new file mode 100644 index 0000000..1410df9 --- /dev/null +++ b/app/views/users/timeline.html.haml @@ -0,0 +1 @@ += render :partial => "list" diff --git a/app/views/users/timeline.json.jbuilder b/app/views/users/timeline.json.jbuilder new file mode 100644 index 0000000..d2637d3 --- /dev/null +++ b/app/views/users/timeline.json.jbuilder @@ -0,0 +1,4 @@ +json.array! @items do |json, item| + json.partial! "shared/tweet", :item => item +end + diff --git a/client/settings.yml.default b/client/settings.yml.default index 2f8e787..874e239 100644 --- a/client/settings.yml.default +++ b/client/settings.yml.default @@ -1,5 +1,7 @@ -consumer_key: -consumer_secret: +consumer: + - + - **key** + - **secret** secret_key: worker_count: worker_number: diff --git a/client/worker.rb b/client/worker.rb index d4cc1cd..1ed3414 100644 --- a/client/worker.rb +++ b/client/worker.rb @@ -1,15 +1,9 @@ -require "time" require "em-twitter" require "yajl" require "msgpack" require "./settings" require "./logger" -module EM - class Connection - end -end - class Worker class DBProxyClient < EM::Connection def send_object(data) @@ -66,8 +60,8 @@ class Worker :host => "userstream.twitter.com", :path => "/1.1/user.json", :oauth => { - :consumer_key => Settings.consumer_key, - :consumer_secret => Settings.consumer_secret, + :consumer_key => Settings.consumer[msg["consumer_version"].to_i].key, + :consumer_secret => Settings.consumer[msg["consumer_version"].to_i].secret, :token => msg["oauth_token"], :token_secret => msg["oauth_token_secret"]}, :method => "GET"}) @@ -77,7 +71,8 @@ class Worker :id => user[:id], :screen_name => user[:screen_name], :name => user[:name], - :profile_image_url => user[:profile_image_url_https]} + :profile_image_url => user[:profile_image_url_https], + :protected => user[:protected]} send_object(out) $logger.debug("User(##{account_id}/#{user_id}): #{user[:id]} = #{user[:screen_name]}") end @@ -123,6 +118,14 @@ class Worker $logger.debug("Retweet(##{account_id}/#{user_id}): #{status[:user][:id]} => #{status[:retweeted_status][:id]}") end + send_delete = -> deleted_status_id, deleted_user_id do + out = {:type => "delete", + :id => deleted_status_id, + :user_id => deleted_user_id} + send_object(out) + $logger.debug("Delete(##{account_id}/#{user_id}): #{deleted_user_id} => #{deleted_status_id}") + end + client.on_error do |message| $logger.warn("Unknown Error(##{account_id}/#{user_id}): #{message}") end @@ -160,11 +163,7 @@ class Worker elsif hash[:delete] && hash[:delete][:status] deleted_status_id = hash[:delete][:status][:id] deleted_user_id = hash[:delete][:status][:user_id] - out = {:type => "delete", - :id => deleted_status_id, - :user_id => deleted_user_id} - send_object(out) - $logger.debug("Delete(##{account_id}/#{user_id}): #{deleted_user_id} => #{deleted_status_id}") + send_delete.call(deleted_status_id, deleted_user_id) elsif hash[:limit] $logger.warn("UserStreams Limit Event(##{account_id}/#{user_id}): #{hash[:limit][:track]}") elsif hash[:event] @@ -213,8 +212,7 @@ class Worker def post_init out = {:type => "init", :secret_key => Settings.secret_key, - :worker_number => Settings.worker_number, - :worker_count => Settings.worker_count} + :worker_number => Settings.worker_number} send_object(out) end @@ -228,7 +226,7 @@ class Worker def receive_data(data) @pac.feed_each(data) do |msg| - unless msg["type"] + unless msg.is_a?(Hash) && msg["type"] $logger.warn("Unknown data: #{msg}") return end @@ -240,8 +238,17 @@ class Worker $logger.info("error: #{msg["message"]}") when "fatal" $logger.info("fatal: #{msg["message"]}") + when "bye" + $logger.info("bye: #{msg["message"]}") when "account" - receive_account(msg) + begin + receive_account(msg) + rescue + $logger.error($!) + $logger.error($@) + end + else + $logger.info("Unknown message type: #{msg}") end end end @@ -253,7 +260,7 @@ class Worker end def initialize - $logger = Aclog::Logger.new(:debug) + $logger = Aclog::Logger.new(:info) end def start diff --git a/config/application.rb b/config/application.rb index 7df2cca..ab8b888 100644 --- a/config/application.rb +++ b/config/application.rb @@ -21,5 +21,7 @@ module Aclog # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + + config.exceptions_app = self.routes end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 5f95ca6..5089ff4 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -10,7 +10,7 @@ Aclog::Application.configure do config.eager_load = false # Show full error reports and disable caching. - config.consider_all_requests_local = true + config.consider_all_requests_local = false#true config.action_controller.perform_caching = false # Don't care if the mailer can't send. @@ -24,4 +24,6 @@ Aclog::Application.configure do # Expands the lines which load the assets. config.assets.debug = true + + # config.cache_store = :dalli_store end diff --git a/config/environments/production.rb b/config/environments/production.rb index c219686..fc529ba 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -52,7 +52,7 @@ Aclog::Application.configure do # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production. - # config.cache_store = :mem_cache_store + config.cache_store = :dalli_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 540652c..eaee9f6 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,7 +1,18 @@ +OmniAuth.config.full_host = -> env do + scheme = env["rack.url_scheme"] + forwarded_host = env["HTTP_X_FORWARDED_HOST"] + + host = forwarded_host.blank? ? + env["HTTP_HOST"] : + forwarded_host + + "#{scheme}://#{host}" +end + Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, - Settings.consumer_key, - Settings.consumer_secret, + Settings.consumer[Settings.consumer_version].key, + Settings.consumer[Settings.consumer_version].secret, :request_path => "/i/login", :callback_path => "/i/callback" end diff --git a/config/routes.rb b/config/routes.rb index a7b879f..66a28c7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,18 +4,31 @@ Aclog::Application.routes.draw do :screen_name => /[a-zA-Z0-9_]{1,20}/, :page => /[0-9]+/, } + get "/404" => "errors#error_404" + get "/500" => "errors#error_500" root :to => "main#index" - get "i/callback" => "sessions#callback" - get "i/logout" => "sessions#destroy" - - get "i/:id" => "i#show", :constraints => constraints - get ":screen_name/status(es)/:id" => "i#show", :constraints => constraints - - get ":screen_name(/:page)" => "users#best", :constraints => constraints - get ":screen_name/my(/:page)" => "users#my", :constraints => constraints - get ":screen_name/discovered(/:page)" => "users#my", :constraints => constraints - get ":screen_name/timeline(/:page)" => "users#timeline", :constraints => constraints - get ":screen_name/recent(/:page)" => "users#recent", :constraints => constraints - get ":screen_name/info(/:page)" => "users#info", :constraints => constraints + + get "/i/callback" => "sessions#callback" + get "/i/logout" => "sessions#destroy" + + get "/i/best" => "i#best" + get "/i/recent" => "i#recent" + + get "/i/:id" => "users#show", :constraints => constraints + get "/(users)/:screen_name/status(es)/:id" => redirect("/i/%{id}") + + get "/:screen_name(/:page)" => "users#best", :constraints => constraints + get "/:screen_name/best" => redirect("/%{screen_name}") + get "/users/:screen_name" => redirect("/%{screen_name}") + + get "/:screen_name/discovered(/:page)" => "users#my", :constraints => constraints + get "/:screen_name/my(/:page)" => "users#my", :constraints => constraints + get "/users/:screen_name/discovered" => redirect("/%{screen_name}/discovered") + + get "/:screen_name/timeline(/:page)" => "users#timeline", :constraints => constraints + get "/:screen_name/timeline/all(/:page)" => "users#timeline", :constraints => constraints, :defaults => {:tweets => "all"} + get "/users/:screen_name/recent" => redirect("/%{screen_name}/timeline") + + get "/:screen_name/recent(/:page)" => "users#recent", :constraints => constraints end diff --git a/config/settings.yml b/config/settings.yml index c165051..8c3650f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1,5 +1,7 @@ -consumer_key: <%= ENV["CONSUMER_KEY"] %> -consumer_secret: <%= ENV["CONSUMER_SECRET"] %> +#consumer: +# - key: <%= ENV["CONSUMER_KEY"] %> +# secret: <%= ENV["CONSUMER_SECRET"] %> +consumer_version: 1 page_per: <%= ENV["ACLOG_PAGE_PER"] %> worker_count: <%= ENV["ACLOG_WORKER_COUNT"] %> secret_key: <%= ENV["ACLOG_SECRET_KEY"] %> diff --git a/db/migrate/20130322152541_add_user_id_index_to_events.rb b/db/migrate/20130322152541_add_user_id_index_to_events.rb new file mode 100644 index 0000000..8543185 --- /dev/null +++ b/db/migrate/20130322152541_add_user_id_index_to_events.rb @@ -0,0 +1,6 @@ +class AddUserIdIndexToEvents < ActiveRecord::Migration + def change + add_index :favorites, :user_id + add_index :retweets, :user_id + end +end diff --git a/db/migrate/20130322171251_add_column_consumer_version_to_accounts.rb b/db/migrate/20130322171251_add_column_consumer_version_to_accounts.rb new file mode 100644 index 0000000..747287b --- /dev/null +++ b/db/migrate/20130322171251_add_column_consumer_version_to_accounts.rb @@ -0,0 +1,5 @@ +class AddColumnConsumerVersionToAccounts < ActiveRecord::Migration + def change + add_column :accounts, :consumer_version, :integer + end +end diff --git a/db/migrate/20130323045606_add_protected_to_users.rb b/db/migrate/20130323045606_add_protected_to_users.rb new file mode 100644 index 0000000..dcc85f6 --- /dev/null +++ b/db/migrate/20130323045606_add_protected_to_users.rb @@ -0,0 +1,5 @@ +class AddProtectedToUsers < ActiveRecord::Migration + def change + add_column :users, :protected, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 4ec571b..d8b9299 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20130226151042) do +ActiveRecord::Schema.define(version: 20130323045606) do create_table "accounts", force: true do |t| t.integer "user_id", limit: 8, null: false @@ -19,6 +19,7 @@ ActiveRecord::Schema.define(version: 20130226151042) do t.string "oauth_token_secret", null: false t.datetime "created_at" t.datetime "updated_at" + t.integer "consumer_version" end add_index "accounts", ["user_id"], name: "index_accounts_on_user_id", unique: true @@ -30,6 +31,7 @@ ActiveRecord::Schema.define(version: 20130226151042) do add_index "favorites", ["tweet_id", "user_id"], name: "index_favorites_on_tweet_id_and_user_id", unique: true add_index "favorites", ["tweet_id"], name: "index_favorites_on_tweet_id" + add_index "favorites", ["user_id"], name: "index_favorites_on_user_id" create_table "retweets", force: true do |t| t.integer "tweet_id", limit: 8, null: false @@ -37,6 +39,7 @@ ActiveRecord::Schema.define(version: 20130226151042) do end add_index "retweets", ["tweet_id"], name: "index_retweets_on_tweet_id" + add_index "retweets", ["user_id"], name: "index_retweets_on_user_id" create_table "tweets", force: true do |t| t.text "text", null: false @@ -53,6 +56,7 @@ ActiveRecord::Schema.define(version: 20130226151042) do t.text "profile_image_url" t.datetime "created_at" t.datetime "updated_at" + t.boolean "protected" end add_index "users", ["screen_name"], name: "index_users_on_screen_name" diff --git a/lib/receiver/worker.rb b/lib/receiver/worker.rb index b22d410..87b55da 100644 --- a/lib/receiver/worker.rb +++ b/lib/receiver/worker.rb @@ -27,7 +27,8 @@ class Receiver::Worker < DaemonSpawn::Base :id => account.id, :oauth_token => account.oauth_token, :oauth_token_secret => account.oauth_token_secret, - :user_id => account.user_id} + :user_id => account.user_id, + :consumer_version => account.consumer_version.to_i} send_object(out) end @@ -43,13 +44,13 @@ class Receiver::Worker < DaemonSpawn::Base def unbind $connections.delete_if{|k, v| v == self} - $logger.info("Connection closed: #{@worker_number}") + $logger.info("Connection closed(#{@worker_number})") end def receive_data(data) @pac.feed_each(data) do |msg| - unless msg["type"] - $logger.error("???: #{msg}") + unless msg.is_a?(Hash) && msg["type"] + $logger.error("???(#{@worker_number}): #{msg}") send_object({:type => "fatal", :message => "Unknown data"}) close_connection_after_writing return @@ -80,7 +81,7 @@ class Receiver::Worker < DaemonSpawn::Base when "quit" receive_quit(msg) else - $logger.warn("Unknown message type: #{msg["type"]}") + $logger.warn("Unknown message type(#{@worker_number}): #{msg["type"]}") send_object({:type => "error", :message => "Unknown message type: #{msg["type"]}"}) end end @@ -90,7 +91,7 @@ class Receiver::Worker < DaemonSpawn::Base secret_key = msg["secret_key"] worker_number = msg["worker_number"] unless secret_key == Settings.secret_key - $logger.error("Invalid secret_key: #{secret_key}") + $logger.error("Invalid secret_key(?:#{worker_number}): #{secret_key}") send_object({:type => "fatal", :message => "Invalid secret_key"}) close_connection_after_writing return @@ -98,52 +99,59 @@ class Receiver::Worker < DaemonSpawn::Base $connections[worker_number] = self @worker_number = worker_number @authorized = true - $logger.info("Connected: #{worker_number}") + $logger.info("Connected(#{@worker_number})") send_object({:type => "ok", :message => "Connected"}) send_account_all end def receive_unauthorized(msg) - $logger.warn("Unauthorized: #{msg["user_id"]}") + $logger.warn("Unauthorized(#{@worker_number}): #{msg["user_id"]}") # unregister end def receive_user(msg) @@wq.push -> do - $logger.debug("Received User") - rec = User.find_or_initialize_by(:id => msg["id"]) - rec.screen_name = msg["screen_name"] - rec.name = msg["name"] - rec.profile_image_url = msg["profile_image_url"] - rec.save! if rec.changed? + $logger.debug("Received User(#{@worker_number}): #{msg["id"]}") + begin + rec = User.find_or_initialize_by(:id => msg["id"]) + rec.screen_name = msg["screen_name"] + rec.name = msg["name"] + rec.profile_image_url = msg["profile_image_url"] + rec.protected = msg["protected"] + rec.save! if rec.changed? + rescue + $logger.error("Unknown error while inserting user: #{$!}/#{$@}") + end end end def receive_tweet(msg) @@wq.push -> do - $logger.debug("Received Tweet") + $logger.debug("Received Tweet(#{@worker_number}): #{msg["id"]}") begin Tweet.create!(:id => msg["id"], :text => msg["text"], :source => msg["source"], :tweeted_at => Time.parse(msg["tweeted_at"]), :user_id => msg["user_id"]) - $logger.debug("Saved Tweet") rescue ActiveRecord::RecordNotUnique - $logger.info("Can't Save Tweet: Duplicate") + $logger.debug("Duplicate Tweet(#{@worker_number}): #{msg["id"]}") + rescue + $logger.error("Unknown error while inserting tweet: #{$!}/#{$@}") end end end def receive_favorite(msg) @@wq.push -> do - $logger.debug("Received Favorite") + $logger.debug("Receive Favorite(#{@worker_number}): #{msg["user_id"]} => #{msg["tweet_id"]}") begin Favorite.create!(:tweet_id => msg["tweet_id"], :user_id => msg["user_id"]) - $logger.debug("Saved Favorite") rescue ActiveRecord::RecordNotUnique - $logger.info("Can't Save Tweet: Duplicate") + $logger.debug("Duplicate Favorite(#{@worker_number}): #{msg["id"]}") + rescue + $logger.error("Unknown error while inserting favorite: #{$!}/#{$@}") end end end @@ -155,29 +163,36 @@ class Receiver::Worker < DaemonSpawn::Base Retweet.create!(:id => msg["id"], :tweet_id => msg["tweet_id"], :user_id => msg["user_id"]) - $logger.debug("Saved Retweet") rescue ActiveRecord::RecordNotUnique - $logger.info("Can't Save Retweet: Duplicate") + $logger.debug("Duplicate Retweet(#{@worker_number}): #{msg["id"]}") + rescue + $logger.error("Unknown error while inserting retweet: #{$!}/#{$@}") end end end def receive_delete(msg) @@wq.push -> do - if msg["id"] - Tweet.where(:id => msg["id"]).destroy_all - Retweet.where(:id => msg["id"]).destroy_all - elsif msg["tweet_id"] - Favorite - .where("tweet_id = #{msg["tweet_id"]} AND user_id = #{msg["user_id"]}") - .destroy_all + begin + if msg["id"] + $logger.debug("Receive Delete(#{@worker_number}): #{msg["id"]}") + Tweet.where(:id => msg["id"]).destroy_all + Retweet.where(:id => msg["id"]).destroy_all + elsif msg["tweet_id"] + $logger.debug("Receive Unfavorite(#{@worker_number}): #{msg["user_id"]} => #{msg["tweet_id"]}") + Favorite + .where("tweet_id = #{msg["tweet_id"]} AND user_id = #{msg["user_id"]}") + .destroy_all + end + rescue + $logger.error("Unknown error while deleting: #{$!}/#{$@}") end end end def receive_quit(msg) - $logger.warn("Quit: #{@worker_number}") - send_data({:type => "ok", :message => "Bye"}) + $logger.warn("Quit(#{@worker_number}): #{msg["reason"]}") + send_data({:type => "bye", :message => "Bye"}) close_connection_after_writing end end @@ -225,8 +240,8 @@ class Receiver::Worker < DaemonSpawn::Base end def initialize(opts = {}) - #super(opts) - $logger = Receiver::Logger.new(:debug) + super(opts) + $logger = Receiver::Logger.new(:info) $connections = {} end diff --git a/public/404.xhtml b/public/404.xhtml deleted file mode 100644 index 9a48320..0000000 --- a/public/404.xhtml +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <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> diff --git a/public/500.xhtml b/public/500.xhtml deleted file mode 100644 index f3648a0..0000000 --- a/public/500.xhtml +++ /dev/null @@ -1,25 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <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> - </div> -</body> -</html> diff --git a/public/assets/application-47a9a60feaec5625a9c66b1c985a1179.js.gz b/public/assets/application-47a9a60feaec5625a9c66b1c985a1179.js.gz Binary files differdeleted file mode 100644 index a70851a..0000000 --- a/public/assets/application-47a9a60feaec5625a9c66b1c985a1179.js.gz +++ /dev/null diff --git a/public/assets/application-47a9a60feaec5625a9c66b1c985a1179.js b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js index a6f3663..d287f32 100644 --- a/public/assets/application-47a9a60feaec5625a9c66b1c985a1179.js +++ b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js @@ -1,27 +1,22 @@ (function() { - }).call(this); (function() { - }).call(this); (function() { - }).call(this); (function() { - }).call(this); (function() { - }).call(this); // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. diff --git a/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz Binary files differnew file mode 100644 index 0000000..b820f5f --- /dev/null +++ b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz diff --git a/public/assets/manifest-8b2cd06c78837f5f27435d8942b3f60f.json b/public/assets/manifest-53d4de075a772d1eda47ac6355907a22.json index 7803527..db1f481 100644 --- a/public/assets/manifest-8b2cd06c78837f5f27435d8942b3f60f.json +++ b/public/assets/manifest-53d4de075a772d1eda47ac6355907a22.json @@ -1 +1 @@ -{"files":{"bird_gray_16-93f51980875017e26744056b62aaac22.png":{"logical_path":"bird_gray_16.png","mtime":"2013-03-09T14:41:39+09:00","size":1106,"digest":"93f51980875017e26744056b62aaac22"},"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png":{"logical_path":"missing_profile_image.png","mtime":"2013-03-16T16:45:26+09:00","size":9350,"digest":"d5d708680693a18615dfb8f5b1a5a96c"},"rails-bc7d436ef8afbf0f88829742a43ba3a4.png":{"logical_path":"rails.png","mtime":"2013-02-25T21:20:52+09:00","size":6646,"digest":"bc7d436ef8afbf0f88829742a43ba3a4"},"application-47a9a60feaec5625a9c66b1c985a1179.js":{"logical_path":"application.js","mtime":"2013-03-09T01:51:23+09:00","size":743,"digest":"47a9a60feaec5625a9c66b1c985a1179"},"application-8ad8039145e9fbab6b0de0f554e4487b.css":{"logical_path":"application.css","mtime":"2013-03-16T18:11:28+09:00","size":3584,"digest":"8ad8039145e9fbab6b0de0f554e4487b"}},"assets":{"bird_gray_16.png":"bird_gray_16-93f51980875017e26744056b62aaac22.png","missing_profile_image.png":"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png","rails.png":"rails-bc7d436ef8afbf0f88829742a43ba3a4.png","application.js":"application-47a9a60feaec5625a9c66b1c985a1179.js","application.css":"application-8ad8039145e9fbab6b0de0f554e4487b.css"}}
\ No newline at end of file +{"files":{"bird_gray_16-93f51980875017e26744056b62aaac22.png":{"logical_path":"bird_gray_16.png","mtime":"2013-03-09T14:41:39+09:00","size":1106,"digest":"93f51980875017e26744056b62aaac22"},"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png":{"logical_path":"missing_profile_image.png","mtime":"2013-03-16T16:45:26+09:00","size":9350,"digest":"d5d708680693a18615dfb8f5b1a5a96c"},"rails-bc7d436ef8afbf0f88829742a43ba3a4.png":{"logical_path":"rails.png","mtime":"2013-02-25T21:20:52+09:00","size":6646,"digest":"bc7d436ef8afbf0f88829742a43ba3a4"},"application-c0feb24db0871514682a56320c7b2b2b.js":{"logical_path":"application.js","mtime":"2013-03-09T01:51:23+09:00","size":738,"digest":"c0feb24db0871514682a56320c7b2b2b"},"application-8ad8039145e9fbab6b0de0f554e4487b.css":{"logical_path":"application.css","mtime":"2013-03-16T18:11:28+09:00","size":3584,"digest":"8ad8039145e9fbab6b0de0f554e4487b"}},"assets":{"bird_gray_16.png":"bird_gray_16-93f51980875017e26744056b62aaac22.png","missing_profile_image.png":"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png","rails.png":"rails-bc7d436ef8afbf0f88829742a43ba3a4.png","application.js":"application-c0feb24db0871514682a56320c7b2b2b.js","application.css":"application-8ad8039145e9fbab6b0de0f554e4487b.css"}}
\ No newline at end of file |