Merge pull request #5685 from margori/1802_export_photos

Export user photos
This commit is contained in:
Jonne Haß 2015-03-04 01:45:10 +01:00
commit abc4203840
20 changed files with 289 additions and 50 deletions

View file

@ -182,6 +182,7 @@ diaspora.yml file**. The existing settings from 0.4.x and before will not work a
* Add reshares to the stream view immediately [#5699](https://github.com/diaspora/diaspora/pull/5699) * Add reshares to the stream view immediately [#5699](https://github.com/diaspora/diaspora/pull/5699)
* Update and improve help section [#5665](https://github.com/diaspora/diaspora/pull/5665), [#5706](https://github.com/diaspora/diaspora/pull/5706) * Update and improve help section [#5665](https://github.com/diaspora/diaspora/pull/5665), [#5706](https://github.com/diaspora/diaspora/pull/5706)
* Expose participation controls in the stream view [#5511](https://github.com/diaspora/diaspora/pull/5511) * Expose participation controls in the stream view [#5511](https://github.com/diaspora/diaspora/pull/5511)
* Reimplement photo export [#5685](https://github.com/diaspora/diaspora/pull/5685)
# 0.4.1.2 # 0.4.1.2

View file

@ -148,8 +148,13 @@ class UsersController < ApplicationController
end end
def export_photos def export_photos
tar_path = PhotoMover::move_photos(current_user) current_user.queue_export_photos
send_data( File.open(tar_path).read, :filename => "#{current_user.id}.tar" ) flash[:notice] = I18n.t('users.edit.export_photos_in_progress')
redirect_to edit_user_path
end
def download_photos
redirect_to current_user.exported_photos_file.url
end end
def user_photo def user_photo

View file

@ -19,4 +19,21 @@ class ExportMailer < ActionMailer::Base
end end
end end
def export_photos_complete_for(user)
@user = user
mail(to: @user.email, subject: I18n.t('notifier.export_photos_email.subject', name: @user.name)) do |format|
format.html { render 'users/export_photos_email' }
format.text { render 'users/export_photos_email' }
end
end
def export_photos_failure_for(user)
@user = user
mail(to: @user.email, subject: I18n.t('notifier.export_photos_failure_email.subject', name: @user.name)) do |format|
format.html { render 'users/export_photos_failure_email' }
format.text { render 'users/export_photos_failure_email' }
end
end
end end

View file

@ -314,6 +314,40 @@ class User < ActiveRecord::Base
ActiveSupport::Gzip.compress Diaspora::Exporter.new(self).execute ActiveSupport::Gzip.compress Diaspora::Exporter.new(self).execute
end end
######### Photos export ##################
mount_uploader :exported_photos_file, ExportedPhotos
def queue_export_photos
update exporting_photos: true
Workers::ExportPhotos.perform_async(id)
end
def perform_export_photos!
temp_zip = Tempfile.new([username, '_photos.zip'])
begin
Zip::ZipOutputStream.open(temp_zip.path) do |zos|
photos.each do |photo|
begin
photo_data = photo.unprocessed_image.file.read
zos.put_next_entry(photo.remote_photo_name)
zos.print(photo_data)
rescue Errno::ENOENT
logger.info "Export photos error: #{photo.unprocessed_image.file.path} not found"
end
end
end
ensure
temp_zip.close
end
begin
update exported_photos_file: temp_zip, exported_photos_at: Time.zone.now if temp_zip.present?
ensure
restore_attributes if invalid? || temp_zip.present?
update exporting_photos: false
end
end
######### Mailer ####################### ######### Mailer #######################
def mail(job, *args) def mail(job, *args)
pref = job.to_s.gsub('Workers::Mail::', '').underscore pref = job.to_s.gsub('Workers::Mail::', '').underscore
@ -534,6 +568,8 @@ class User < ActiveRecord::Base
"created_at", "updated_at", "locked_at", "created_at", "updated_at", "locked_at",
"serialized_private_key", "getting_started", "serialized_private_key", "getting_started",
"disable_mail", "show_community_spotlight_in_stream", "disable_mail", "show_community_spotlight_in_stream",
"strip_exif", "email", "remove_after", "export", "exporting", "exported_at"] "strip_exif", "email", "remove_after",
"export", "exporting", "exported_at",
"exported_photos_file", "exporting_photos", "exported_photos_at"]
end end
end end

View file

@ -0,0 +1,21 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class ExportedPhotos < CarrierWave::Uploader::Base
def store_dir
"uploads/users"
end
def filename
"#{model.username}_photos_#{secure_token}.zip" if original_filename.present?
end
protected
def secure_token(bytes = 16)
var = :"@#{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.urlsafe_base64(bytes))
end
end

View file

@ -182,23 +182,32 @@
%h3 %h3
= t('.export_data') = t('.export_data')
- if current_user.exporting - if current_user.exporting
.small-horizontal-spacer
.export-in-progress= t('.export_in_progress') .export-in-progress= t('.export_in_progress')
- elsif current_user.export.present? - elsif current_user.export.present?
.small-horizontal-spacer = link_to t('.download_export'), download_profile_user_path, class: "btn btn-success"
= link_to t('.download_export'), download_profile_user_path, class: "button" %h6
.small-horizontal-spacer
= t('.last_exported_at', timestamp: current_user.exported_at) = t('.last_exported_at', timestamp: current_user.exported_at)
= link_to t('.request_export_update'), export_profile_user_path, class: "btn"
- else
= link_to t('.request_export'), export_profile_user_path, :class => "btn"
- if current_user.exporting_photos
.small-horizontal-spacer .small-horizontal-spacer
= link_to t('.request_export_update'), export_profile_user_path .export-in-progress= t('.export_photos_in_progress')
- elsif current_user.exported_photos_file.present?
.small-horizontal-spacer
= link_to t('.download_export_photos'), download_photos_user_path, class: "btn btn-success"
%h6
= t('.last_exported_at', timestamp: current_user.exported_photos_at)
= link_to t('.request_export_photos_update'), export_photos_user_path, class: "btn"
- else - else
.small-horizontal-spacer .small-horizontal-spacer
= link_to t('.request_export'), export_profile_user_path, :class => "button" = link_to t('.request_export_photos'), export_photos_user_path, :class => "btn"
.span6 .span6
%h3 %h3
= t('.close_account_text') = t('.close_account_text')
=link_to t('.close_account_text'), '#close_account_pane', :rel => 'facebox', :class => "button", :id => "close_account" =link_to t('.close_account_text'), '#close_account_pane', :rel => 'facebox', :class => "btn btn-danger", :id => "close_account"
.hidden#close_account_pane{:rel => 'facebox'} .hidden#close_account_pane{:rel => 'facebox'}
#inner_account_delete #inner_account_delete

View file

@ -162,23 +162,31 @@
%h4 %h4
= t('.export_data') = t('.export_data')
- if current_user.exporting - if current_user.exporting
.small-horizontal-spacer
.export-in-progress= t('.export_in_progress') .export-in-progress= t('.export_in_progress')
- elsif current_user.export.present? - elsif current_user.export.present?
.small-horizontal-spacer = link_to t('.download_export'), download_profile_user_path, class: "btn btn-success"
= link_to t('.download_export'), download_profile_user_path, class: "button" %h6
.small-horizontal-spacer
= t('.last_exported_at', timestamp: current_user.exported_at) = t('.last_exported_at', timestamp: current_user.exported_at)
.small-horizontal-spacer = link_to t('.request_export_update'), export_profile_user_path, class: "btn"
= link_to t('.request_export_update'), export_profile_user_path
- else - else
.small-horizontal-spacer = link_to t('.request_export'), export_profile_user_path, :class => "btn"
= link_to t('.request_export'), export_profile_user_path, :class => "button" %br
%br
- if current_user.exporting_photos
.export-in-progress= t('.export_photos_in_progress')
- elsif current_user.exported_photos_file.present?
= link_to t('.download_export_photos'), download_photos_user_path, class: "btn btn-success"
%h6
= t('.last_exported_at', timestamp: current_user.exported_photos_at)
= link_to t('.request_export_photos_update'), export_photos_user_path, class: "btn"
- else
= link_to t('.request_export_photos'), export_photos_user_path, :class => "btn"
%hr
.span-5.last .span-5.last
%h4 %h4
= t('.close_account_text') = t('.close_account_text')
=link_to t('.close_account_text'), '#close_account_pane', :rel => 'facebox', :class => "btn", :id => "close_account" =link_to t('.close_account_text'), '#close_account_pane', :rel => 'facebox', :class => "btn btn-danger", :id => "close_account"
.hidden#close_account_pane{:rel => 'facebox'} .hidden#close_account_pane{:rel => 'facebox'}
#inner_account_delete #inner_account_delete
@ -213,3 +221,4 @@
= f.password_field :current_password, :id => :close_account_password = f.password_field :current_password, :id => :close_account_password
%p %p
= f.submit t('.close_account_text'), :id => "close_account_confirm", :data => { :confirm => t('are_you_sure_delete_account') } = f.submit t('.close_account_text'), :id => "close_account_confirm", :data => { :confirm => t('are_you_sure_delete_account') }
%br

View file

@ -0,0 +1 @@
<%= t('notifier.export_photos_email.body', url: download_photos_user_url, name: @user.first_name) %>

View file

@ -0,0 +1 @@
<%= t('notifier.export_photos_failure_email.body', name: @user.first_name) %>

View file

@ -0,0 +1,21 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module Workers
class ExportPhotos < Base
sidekiq_options queue: :export_photos
def perform(user_id)
@user = User.find(user_id)
@user.perform_export_photos!
if @user.reload.exported_photos_file.present?
ExportMailer.export_photos_complete_for(@user)
else
ExportMailer.export_photos_failure_for(@user)
end
end
end
end

View file

@ -13,7 +13,6 @@ require 'typhoeus'
require 'post_presenter' require 'post_presenter'
# Our libs # Our libs
require 'collect_user_photos'
require 'diaspora' require 'diaspora'
require 'direction_detector' require 'direction_detector'
require 'email_inviter' require 'email_inviter'

View file

@ -817,8 +817,29 @@ en:
Weve encountered an issue while processing your personal data for download. Weve encountered an issue while processing your personal data for download.
Please try again! Please try again!
Sorry,
The diaspora* email robot!
export_photos_email:
subject: "Your photos are ready for download, %{name}"
body: |-
Hello %{name},
Your photos have been processed and are ready for download by following [this link](%{url}).
Cheers, Cheers,
The diaspora* email robot!
export_photos_failure_email:
subject: "There was an issue with your photos, %{name}"
body: |-
Hello %{name}
Weve encountered an issue while processing your photos for download.
Please try again!
Sorry,
The diaspora* email robot! The diaspora* email robot!
accept_invite: "Accept your diaspora* invite!" accept_invite: "Accept your diaspora* invite!"
invited_you: "%{name} invited you to diaspora*" invited_you: "%{name} invited you to diaspora*"
@ -1319,6 +1340,11 @@ en:
export_data: "Export data" export_data: "Export data"
export_in_progress: "We are currently processing your data. Please check back in a few moments." export_in_progress: "We are currently processing your data. Please check back in a few moments."
last_exported_at: "(Last updated at %{timestamp})" last_exported_at: "(Last updated at %{timestamp})"
download_export_photos: "Download my photos"
request_export_photos: "Request my photos"
request_export_photos_update: "Refresh my photos"
download_photos: "Download my photos"
export_photos_in_progress: "We are currently processing your photos. Please check back in a few moments."
close_account: close_account:
dont_go: "Hey, please dont go!" dont_go: "Hey, please dont go!"

View file

@ -104,6 +104,7 @@ Diaspora::Application.routes.draw do
get :export_profile get :export_profile
get :download_profile get :download_profile
get :export_photos get :export_photos
get :download_photos
end end
controller :users do controller :users do

View file

@ -0,0 +1,13 @@
class AddPhotosExportToUser < ActiveRecord::Migration
def up
add_column :users, :exported_photos_file, :string
add_column :users, :exported_photos_at, :datetime
add_column :users, :exporting_photos, :boolean, default: false
end
def down
remove_column :users, :exported_photos_file
remove_column :users, :exported_photos_at
remove_column :users, :exporting_photos
end
end

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150209230946) do ActiveRecord::Schema.define(version: 20150220001357) do
create_table "account_deletions", force: :cascade do |t| create_table "account_deletions", force: :cascade do |t|
t.string "diaspora_handle", limit: 255 t.string "diaspora_handle", limit: 255
@ -562,6 +562,9 @@ ActiveRecord::Schema.define(version: 20150209230946) do
t.datetime "exported_at" t.datetime "exported_at"
t.boolean "exporting", default: false t.boolean "exporting", default: false
t.boolean "strip_exif", default: true t.boolean "strip_exif", default: true
t.string "exported_photos_file", limit: 255
t.datetime "exported_photos_at"
t.boolean "exporting_photos", default: false
end end
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree

View file

@ -1,25 +0,0 @@
module PhotoMover
def self.move_photos(user)
Dir.chdir Rails.root
temp_dir = "tmp/exports/#{user.id}"
FileUtils::mkdir_p temp_dir
Dir.chdir 'tmp/exports'
photos = user.visible_shareables(Post).where(:author_id => user.person_id, :type => 'Photo')
photos_dir = "#{user.id}/photos"
FileUtils::mkdir_p photos_dir
photos.each do |photo|
current_photo_location = "#{Rails.root}/public/uploads/images/#{photo.remote_photo_name}"
new_photo_location = "#{photos_dir}/#{photo.remote_photo_name}"
FileUtils::cp current_photo_location, new_photo_location
end
`tar c #{user.id} > #{user.id}.tar`
#system("tar", "c", "#{user.id}",">", "#{user.id}.tar")
FileUtils::rm_r "#{user.id.to_s}/", :secure => true, :force => true
"#{Rails.root}/#{temp_dir}.tar"
end
end

View file

@ -30,9 +30,19 @@ describe UsersController, :type => :controller do
end end
describe '#export_photos' do describe '#export_photos' do
it 'returns a tar file' do it 'queues an export photos job' do
expect(@user).to receive :queue_export_photos
get :export_photos get :export_photos
expect(response.header["Content-Type"]).to include "application/octet-stream" expect(request.flash[:notice]).to eql(I18n.t('users.edit.export_photos_in_progress'))
expect(response).to redirect_to(edit_user_path)
end
end
describe '#download_photos' do
it "redirects to user's photos zip file" do
@user.perform_export_photos!
get :download_photos
expect(response).to redirect_to(@user.exported_photos_file.url)
end end
end end

View file

@ -34,4 +34,34 @@ describe ExportMailer, :type => :mailer do
expect(ActionMailer::Base.deliveries[0].to[0]).to include(alice.email) expect(ActionMailer::Base.deliveries[0].to[0]).to include(alice.email)
end end
end end
describe '#export_photos_complete_for' do
it "should deliver successfully" do
expect { ExportMailer.export_photos_complete_for(alice).deliver_now }.to_not raise_error
end
it "should be added to the delivery queue" do
expect { ExportMailer.export_photos_complete_for(alice).deliver_now }.to change(ActionMailer::Base.deliveries, :size).by(1)
end
it "should include correct recipient" do
ExportMailer.export_photos_complete_for(alice).deliver_now
expect(ActionMailer::Base.deliveries[0].to[0]).to include(alice.email)
end
end
describe '#export_photos_failure_for' do
it "should deliver successfully" do
expect { ExportMailer.export_photos_failure_for(alice).deliver_now }.to_not raise_error
end
it "should be added to the delivery queue" do
expect { ExportMailer.export_photos_failure_for(alice).deliver_now }.to change(ActionMailer::Base.deliveries, :size).by(1)
end
it "should include correct recipient" do
ExportMailer.export_photos_failure_for(alice).deliver_now
expect(ActionMailer::Base.deliveries[0].to[0]).to include(alice.email)
end
end
end end

View file

@ -1035,6 +1035,41 @@ describe User, :type => :model do
end end
end end
describe "queue_export_photos" do
it "queues up a job to perform the export photos" do
user = FactoryGirl.create :user
expect(Workers::ExportPhotos).to receive(:perform_async).with(user.id)
user.queue_export_photos
expect(user.exporting_photos).to be_truthy
end
end
describe "perform_export_photos!" do
before do
@user = alice
filename = 'button.png'
image = File.join(File.dirname(__FILE__), '..', 'fixtures', filename)
@saved_image = @user.build_post(:photo, :user_file => File.open(image), :to => alice.aspects.first.id)
@saved_image.save!
end
it "saves a zip export to the user" do
@user.perform_export_photos!
expect(@user.exported_photos_file).to be_present
expect(@user.exported_photos_at).to be_present
expect(@user.exporting_photos).to be_falsey
expect(@user.exported_photos_file.filename).to match /.zip/
expect(Zip::ZipFile.open(@user.exported_photos_file.path).entries.count).to eq(1)
end
it "does not add empty entries when photo not found" do
File.unlink @user.photos.first.unprocessed_image.path
@user.perform_export_photos!
expect(@user.exported_photos_file.filename).to match /.zip/
expect(Zip::ZipFile.open(@user.exported_photos_file.path).entries.count).to eq(0)
end
end
describe "sign up" do describe "sign up" do
before do before do
params = {:username => "ohai", params = {:username => "ohai",

View file

@ -0,0 +1,26 @@
require 'spec_helper'
describe Workers::ExportPhotos do
before do
allow(User).to receive(:find).with(alice.id).and_return(alice)
end
it 'calls export_photos! on user with given id' do
expect(alice).to receive(:perform_export_photos!)
Workers::ExportPhotos.new.perform(alice.id)
end
it 'sends a success message when the export photos is successful' do
allow(alice).to receive(:exported_photos_file).and_return(OpenStruct.new)
expect(ExportMailer).to receive(:export_photos_complete_for).with(alice).and_call_original
Workers::ExportPhotos.new.perform(alice.id)
end
it 'sends a failure message when the export photos fails' do
allow(alice).to receive(:exported_photos_file).and_return(nil)
expect(alice).to receive(:perform_export_photos!).and_return(false)
expect(ExportMailer).to receive(:export_photos_failure_for).with(alice).and_call_original
Workers::ExportPhotos.new.perform(alice.id)
end
end