2012年5月9日 星期三

Paperclip

# migration

class CreateAttachments < ActiveRecord::Migration
def up
create_table :attachments do |t|
t.string :md5, :null=>false
t.references :user
t.string :attachable_type, :null=>false
t.integer :attachable_id, :null=>false
t.string :photo_file_name, :null=>false
t.string :photo_content_type, :null=>false
t.integer :photo_file_size, :null=>false
t.datetime :photo_updated_at
t.string :photo_fingerprint, :limit => 32
t.boolean :photo_processing
t.boolean :available, :default => true

t.timestamps
end
add_index :attachments, :user_id
execute "ALTER TABLE attachments ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"
end


def down
drop_table :attachments
end
end


# model

# == Schema Information
#
# Table name: attachments
#
# id :integer(4) not null, primary key
# md5 :string(255) not null
# user_id :integer(4)
# attachable_type :string(255) not null
# attachable_id :integer(4) not null
# photo_file_name :string(255) not null
# photo_content_type :string(255) not null
# photo_file_size :integer(4) not null
# photo_updated_at :datetime
# photo_processing :boolean(1)
# available :boolean(1) default(TRUE)
# created_at :datetime not null
# updated_at :datetime not null
#

class Attachment < ActiveRecord::Base
belongs_to :user
belongs_to :attachable, :polymorphic => true
attr_accessible :attachable_id, :attachable_type, :available, :md5, :photo_content_type, :photo_file_name, :photo_file_size, :photo_processing, :photo_updated_at

has_attached_file :photo, :styles => {
:small => "90x60>",
:original => "700x500>"
},
:convert_options => {
:small => "-quality 80",
:original => "-quality 90"
},
:storage=>:s3,
:s3_credentials=>"#{Rails.root}/config/s3.yml",
:path => "attachments/:date/:style/:id_:fingerprint.:extension"
process_in_background :photo

validates_presence_of :md5, :photo_file_name, :photo_content_type, :photo_file_size, :photo_updated_at
validates_attachment_presence :photo
validates_attachment_size :photo, :less_than => 2.megabytes
# validates_attachment_content_type :photo, :content_type => ['image/jpeg', 'image/png']
attr_protected :photo_file_name, :photo_content_type, :photo_size

default_scope where(:available => true)

# Virtual Attributes
def swf_uploaded_data=(data)
data.content_type = MIME::Types.type_for(data.original_filename)
self.photo = data
end

# Factory Method
def self.new_by_user(params, user)
upload=self.new(:swf_uploaded_data=>params[:Filedata], :md5=>params[:md5], :user_id=>user.id, :attachable_type=>params[:attachable_type], :attachable_id=>params[:attachable_id])
end
end


# add config/initialize/paperclip_fix.rb

Paperclip.interpolates :date do |attachment, style|
return attachment.instance.created_at.strftime("%Y_%m")
end
Paperclip.interpolates :extension do |attachment, style|
((style = attachment.styles[style]) && style[:format]) ||
File.extname(attachment.original_filename).gsub(/^\.+/, "").downcase
end


# Helper

module AttachmentsHelper
def new_attachment_path_with_session_information
session_key = Rails.application.config.session_options[:key]
attachments_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token)
end
end


# Controller

class AttachmentsController < ApplicationController
before_filter :authenticate_user!
include ApplicationHelper

def new
@md5=new_md5
end

def create
attachment=Attachment.new_by_user(params,current_user)
attachment.save!
h=Hash.new
h[:file_name]=attachment.photo_file_name
h[:small]=attachment.photo.url(:small)
h[:original]=attachment.photo.url(:original)
respond_to do |format|
format.json {render :json => {:photo=>h} }
end
end
end


# new.html.erb

<h2>上傳相片</h2>
<hr>

<div id="project-uploads">
<div id="uploaderFlashInstance" style="position:absolute; z-index:2"></div>
<div id="step1" class="container-fluid">
<div>
<label>Step 1.</label>
<div id="selectFilesLink" style="z-index:1"><h3><a id="selectLink" href="#">瀏覽檔案</a></h3></div>
</div>
<div>
<label>Step 2.</label>
<h3>上傳</h3>
<input type="hidden" id="attachable_id" value="" />
</div>
<div style="margin-top: 50px;">
或取消
</div>
</div>
<div id="step2" style="display: none;">
<div id="upload_table">
<div class="head"></div>
<div class="body">
<div class="uploader-scroll" style="width: 500px;">
<div id="dataTableContainer"></div>
</div>
</div>
<div class="foot">
<span id="total_files"></span>
<span class="add_more">
<a id="add_more" href="#">增加</a>
</span>
<span id="total_bytes"></span>
</div>
<div class="btnset">
<a id="uploadLink" href="#" class="btn btn-primary">上傳</a>
<div style="margin-top: 20px;">
或取消
</div>
</div>
</div>
</div>
<div id="step3" style="display:none;">
全數上傳完畢,圖片處理須稍候片刻
</div>
</div>

<% content_for :stylesheets do %>
<%= stylesheet_link_tag "http://yui.yahooapis.com/2.9.0/build/fonts/fonts-min.css" %>
<%= stylesheet_link_tag "http://yui.yahooapis.com/2.9.0/build/datatable/assets/skins/sam/datatable.css" %>
<% end %>

<% content_for :javascripts do %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/yahoo/yahoo-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/yahoo-dom-event/yahoo-dom-event.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/element/element-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/uploader/uploader-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/datasource/datasource-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/event-delegate/event-delegate-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/datatable/datatable-min.js" %>
<%= javascript_include_tag "http://yui.yahooapis.com/2.9.0/build/json/json-min.js" %>
<%= javascript_include_tag "js/Extend" %>
<%= javascript_include_tag "js/Uploader" %>
<%= javascript_include_tag "js/UploadTable" %>

<script>
var photo_upload=function(){
var createTable=function(entries){
var tmp = {};
var maxOldNum=photo_upload.maxNum;
var maxNewNum=maxOldNum;
var aRows=[],arr=[];
var dt=photo_upload.dtUploader;

for(var i in entries) {
// only add the new file
var num=parseInt(i.replace("file",""));
if (num>maxOldNum){
// ignore it if file already in the queue.
if (dt){
var entry = entries[i];
entry["progress"] = 0;
entry["ticket_id"] ='';

if (!dt.hasRecord({name: entry['name'], size: entry['size']})){
aRows.push(entry);
}
}else{
var entry = entries[i];
entry["progress"] = 0;
entry["ticket_id"] ='';
arr.unshift(entry);
}

if (num>maxNewNum)
maxNewNum=num;
}
}
photo_upload.maxNum=maxNewNum;

if (dt){
dt.addRows(aRows);
}else{
photo_upload.dtUploader = new cci.UploadTable({
renderTo: "dataTableContainer",
ds: arr
});
dt=photo_upload.dtUploader;
}
photo_upload.refreshFile();

photo_upload.dtUploader.subscribe('linkClickEvent',function(oArgs){
var oRecord=this.getRecord(oArgs.target);
this.deleteRow(oRecord);
var file_id=oRecord.getData('id');
delete photo_upload.fileIdHash[file_id];
photo_upload.refreshFoot();
photo_upload.uploader.removeFile(file_id);
if (dt.getRecordSet().getLength()==0){
$('#uploadLink').attr('disabled',true).unbind('click', photo_upload.onUploadClick);
}else{
$('#uploadLink').attr('disabled',false).bind('click', photo_upload.onUploadClick);
}
});
dt.sortColumn(dt.getColumn('id'));
$('#dataTableContainer table').addClass('table');
photo_upload.refreshFoot();
};

return{
uploader:null,
dtUploader:null,
fileIdHash:null,
totalBytes:null,
maxNum:-1,

init:function(){
// Locate the swfobject position
this.init_swf_location('selectLink');
this.uploader=new cci.Uploader({
renderTo:'uploaderFlashInstance',
fileFilter:'images'
});

function onBrowseClick () {
}

// Fired when the user selects files in the "Browse" dialog and click "OK"
function onFileSelect(event) {
if(event.fileList) {
$('#step1').hide();
$('#step2').show();
createTable(event.fileList);
}
}

$('#uploadLink').bind('click', photo_upload.onUploadClick);

// Do something on each file's upload start.
function onUploadStart(event) {
log('upload start');
}

// Do something on each file's upload progress event.
function onUploadProgress(event) {
var rowNum = photo_upload.fileIdHash[event["id"]];
var prog = Math.round(99*(event["bytesLoaded"]/event["bytesTotal"]));
var oData=photo_upload.dtUploader.getRecordData(rowNum);

photo_upload.dtUploader.updateRow(rowNum, {id: oData.id, name: oData.name, size: oData.size, progress: prog, cDate: oData.cDate, mDate: oData.mDate, ticket_id: oData.ticket_id});
}

// Do something when each file's upload is complete.
function onUploadComplete(event) {
log('upload complete, next file');
}

// Do something if a file upload throws an error.
// (When uploadAll() is used, the Uploader will
// attempt to continue uploading.
function onUploadError(event) {
cci.dialog.error("伺服器發生錯誤,狀態代碼 "+event.status);
}

// Do something if an upload is cancelled.
function onUploadCancel(event) {
log('cancel '+event["id"]);
}

// Do something when data is received back from the server.
var upload_count=0;
function onUploadResponse(event) {
var rowNum = photo_upload.fileIdHash[event["id"]];
var oData=photo_upload.dtUploader.getRecordData(rowNum);

photo_upload.dtUploader.updateRow(rowNum, {id: oData.id, name: oData.name, size: oData.size, progress: 100, cDate: oData.cDate, mDate: oData.mDate, ticket_id: oData.ticket_id});

upload_count+=1;
if (upload_count == photo_upload.dtUploader.getRecordSet().getLength()) {
$('#step2').hide();
$('#step3').show();
}
}

this.uploader.addListener('fileSelect', onFileSelect)
this.uploader.addListener('uploadStart', onUploadStart);
this.uploader.addListener('uploadProgress', onUploadProgress);
this.uploader.addListener('uploadCancel', onUploadCancel);
this.uploader.addListener('uploadComplete', onUploadComplete);
this.uploader.addListener('uploadCompleteData', onUploadResponse);
this.uploader.addListener('uploadError', onUploadError);
this.uploader.addListener('click', onBrowseClick);

},
onUploadClick:function(){
photo_upload.refreshFile();
photo_upload.dtUploader.hideColumnByKey('delete');
photo_upload.uploader.disable();

var setFootInvisible=function(){
$('.foot').hide();
$('.btnset').hide();
}
if (photo_upload.dtUploader.getRecordSet().getLength()>0) {
setFootInvisible();
var rs=photo_upload.dtUploader.getRecordSet().getRecords();
var file_id=null; // find the lastest file that didn't upload yet.
for (var i=0;i<rs.length;i++){
if (rs[i].getData('progress')!=100){
file_id=rs[i].getData('id');
break;
}
}
if (file_id){
photo_upload.uploader.setSimUploadLimit(3);
photo_upload.uploader.uploadAll('<%= new_attachment_path_with_session_information %>', "POST", {
'format' : 'json',
'authenticity_token' : '<%= u form_authenticity_token %>',
'md5' : '<%= @md5 %>',
'attachable_type' : 'reply',
'attachable_id' : 1111
}, "Filedata");

}else{
cci.dialog.error("沒有檔案需要上傳。");
}
}
},
init_swf_location:function(target_id){
if (!target_id){target_id='selectLink'}
if (target_id=='add_more'){$('#add_more').show();}
var uiLayer = YAHOO.util.Dom.getRegion(target_id);
var overlay = YAHOO.util.Dom.get('uploaderFlashInstance');
YAHOO.util.Dom.setStyle(overlay, 'width', uiLayer.right-uiLayer.left + "px");
YAHOO.util.Dom.setStyle(overlay, 'height', uiLayer.bottom-uiLayer.top + "px");
YAHOO.util.Dom.setStyle(overlay, 'top', uiLayer.top + "px");
YAHOO.util.Dom.setStyle(overlay, 'left', uiLayer.left + "px");
},
refreshFoot:function(){
var ds=photo_upload.dtUploader.getRecordSet();
$('#total_files').html(ds.getLength()+"個檔案");
var total_bytes=0;
for (var i=0;i<ds.getLength();i++){
total_bytes+=ds.getRecord(i).getData('size');
}
photo_upload.totalBytes=total_bytes;
$('#total_bytes').html(cci.util.getFileSize(total_bytes));

this.init_swf_location('add_more');
$('#uploadLink').attr('disabled',false);
},
refreshFile:function(){
var arr=this.dtUploader.getRecordSet().getRecords();
var tmp={};
for (var j = 0; j < arr.length; j++) {
tmp[arr[j].getData('id')]=j;
}
this.fileIdHash=tmp;
log(this.fileIdHash);
}
}
}();


$(document).ready(function(){
photo_upload.init();
});
</script>
<% end %>


Reference:
paperclip fingerprint

沒有留言:

張貼留言