Skip to content

Commit 92e6d5c

Browse files
committed
Upload migration files from profile settings and start processing
1 parent 22d49b4 commit 92e6d5c

File tree

15 files changed

+264
-39
lines changed

15 files changed

+264
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,4 @@ diaspora.iml
7777

7878
# WebTranslateIt
7979
.wti
80+
/__MACOSX/

app/controllers/users_controller.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ def user_params
154154
:post_default_public,
155155
:otp_required_for_login,
156156
:otp_secret,
157+
:exported_photos_file,
158+
:export,
157159
email_preferences: UserPreference::VALID_EMAIL_TYPES.map(&:to_sym)
158160
)
159161
end
@@ -172,6 +174,8 @@ def update_user(user_data)
172174
change_post_default(user_data)
173175
elsif user_data[:color_theme]
174176
change_settings(user_data, "users.update.color_theme_changed", "users.update.color_theme_not_changed")
177+
elsif user_data[:export] || user_data[:exported_photos_file]
178+
upload_export_files(user_data)
175179
else
176180
change_settings(user_data)
177181
end
@@ -235,6 +239,22 @@ def change_email(user_data)
235239
end
236240
end
237241

242+
def upload_export_files(user_data)
243+
logger.info "Start importing profile"
244+
@user.export = user_data[:export] if user_data[:export]
245+
@user.exported_photos_file = user_data[:exported_photos_file] if user_data[:exported_photos_file]
246+
if @user.save
247+
flash.now[:notice] = "A profile migration is scheduled"
248+
else
249+
flash.now[:error] = "An error occured scheduling a migration: #{@user.errors.full_messages}"
250+
end
251+
start_migration_account
252+
end
253+
254+
def start_migration_account
255+
Workers::ImportProfile.perform_async(@user.username)
256+
end
257+
238258
def change_settings(user_data, successful="users.update.settings_updated", error="users.update.settings_not_updated")
239259
if @user.update_attributes(user_data)
240260
flash.now[:notice] = t(successful)

app/models/photo.rb

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Photo < ApplicationRecord
2222
large: photo.url(:scaled_full),
2323
raw: photo.url
2424
}
25-
}, :as => :sizes
25+
}, as: :sizes
2626
t.add lambda { |photo|
2727
{
2828
height: photo.height,
@@ -48,25 +48,25 @@ class Photo < ApplicationRecord
4848
before_destroy :ensure_user_picture
4949
after_destroy :clear_empty_status_message
5050

51-
after_commit :on => :create do
52-
queue_processing_job if self.author.local?
51+
after_commit on: :create do
52+
queue_processing_job if author.local?
5353

5454
end
5555

5656
scope :on_statuses, ->(post_guids) {
57-
where(:status_message_guid => post_guids)
57+
where(status_message_guid: post_guids)
5858
}
5959

6060
def clear_empty_status_message
61-
if self.status_message && self.status_message.text_and_photos_blank?
62-
self.status_message.destroy
61+
if status_message&.text_and_photos_blank?
62+
status_message.destroy
6363
else
6464
true
6565
end
6666
end
6767

6868
def ownership_of_status_message
69-
message = StatusMessage.find_by_guid(self.status_message_guid)
69+
message = StatusMessage.find_by(guid: status_message_guid)
7070
return unless status_message_guid && message && diaspora_handle != message.diaspora_handle
7171

7272
errors.add(:base, "Photo must have the same owner as status message")
@@ -96,21 +96,23 @@ def processed?
9696
end
9797

9898
def update_remote_path
99-
unless self.unprocessed_image.url.match(/^https?:\/\//)
100-
remote_path = "#{AppConfig.pod_uri.to_s.chomp("/")}#{self.unprocessed_image.url}"
101-
else
102-
remote_path = self.unprocessed_image.url
103-
end
99+
remote_path = if unprocessed_image.url.match(%r{^https?://})
100+
unprocessed_image.url
101+
else
102+
"#{AppConfig.pod_uri.to_s.chomp('/')}#{unprocessed_image.url}"
103+
end
104104

105-
name_start = remote_path.rindex '/'
105+
name_start = remote_path.rindex "/"
106106
self.remote_photo_path = "#{remote_path.slice(0, name_start)}/"
107107
self.remote_photo_name = remote_path.slice(name_start + 1, remote_path.length)
108108
end
109109

110-
def url(name = nil)
111-
if remote_photo_path
112-
name = name.to_s + '_' if name
113-
image_url = remote_photo_path + name.to_s + remote_photo_name
110+
def url(name=nil)
111+
# During migration in bad cases this might be happen
112+
# If this happens, stream loading stops. Better dont show photos as stop loading stream
113+
if remote_photo_path.present? && remote_photo_name.present?
114+
name = "#{name}_"
115+
image_url = remote_photo_path + name + remote_photo_name
114116
if AppConfig.privacy.camo.proxy_remote_pod_images?
115117
Diaspora::Camo.image_url(image_url)
116118
else
@@ -124,15 +126,15 @@ def url(name = nil)
124126
end
125127

126128
def ensure_user_picture
127-
profiles = Profile.where(:image_url => url(:thumb_large))
129+
profiles = Profile.where(image_url: url(:thumb_large))
128130
profiles.each { |profile|
129131
profile.image_url = nil
130132
profile.save
131133
}
132134
end
133135

134136
def queue_processing_job
135-
Workers::ProcessPhoto.perform_async(self.id)
137+
Workers::ProcessPhoto.perform_async(id)
136138
end
137139

138140
def self.visible(current_user, person, limit=:all, max_time=nil)

app/models/user.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ def like_for(target)
312312
######### Data export ##################
313313
mount_uploader :export, ExportedUser
314314

315+
######### Photos export ##################
316+
mount_uploader :exported_photos_file, ExportedPhotos
317+
315318
def queue_export
316319
update exporting: true, export: nil, exported_at: nil
317320
Workers::ExportUser.perform_async(id)
@@ -325,29 +328,26 @@ def perform_export!
325328
else
326329
update exporting: false
327330
end
328-
rescue => error
329-
logger.error "Unexpected error while exporting user '#{username}': #{error.class}: #{error.message}\n" \
330-
"#{error.backtrace.first(15).join("\n")}"
331+
rescue StandardError => e
332+
logger.error "Unexpected error while exporting user '#{username}': #{e.class}: #{e.message}\n" \
333+
"#{e.backtrace.first(15).join("\n")}"
331334
update exporting: false
332335
end
333336

334337
def compressed_export
335338
ActiveSupport::Gzip.compress Diaspora::Exporter.new(self).execute
336339
end
337340

338-
######### Photos export ##################
339-
mount_uploader :exported_photos_file, ExportedPhotos
340-
341341
def queue_export_photos
342342
update exporting_photos: true, exported_photos_file: nil, exported_photos_at: nil
343343
Workers::ExportPhotos.perform_async(id)
344344
end
345345

346346
def perform_export_photos!
347347
PhotoExporter.new(self).perform
348-
rescue => error
349-
logger.error "Unexpected error while exporting photos for '#{username}': #{error.class}: #{error.message}\n" \
350-
"#{error.backtrace.first(15).join("\n")}"
348+
rescue StandardError => e
349+
logger.error "Unexpected error while exporting photos for '#{username}': #{e.class}: #{e.message}\n" \
350+
"#{e.backtrace.first(15).join("\n")}"
351351
update exporting_photos: false
352352
end
353353

app/services/migration_service.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ def initialize(archive_path, new_user_name)
1010
end
1111

1212
def validate
13+
return unless archive_file_exists?
14+
1315
archive_validator.validate
1416
raise ArchiveValidationFailed, errors.join("\n") if errors.any?
1517
raise MigrationAlreadyExists if AccountMigration.where(old_person: old_person).any?
18+
raise SelfMigrationNotAllowed if self_import?
1619
end
1720

1821
def perform!
@@ -22,6 +25,12 @@ def perform!
2225
remove_intermediate_file
2326
end
2427

28+
def self_import?
29+
source_diaspora_id = archive_validator.archive_author_diaspora_id
30+
target_diaspora_id = "#{new_user_name}#{User.diaspora_id_host}"
31+
source_diaspora_id.eql?(target_diaspora_id)
32+
end
33+
2534
# when old person can't be resolved we still import data but we don't create&perform AccountMigration instance
2635
def only_import?
2736
old_person.nil?
@@ -51,6 +60,10 @@ def run_migration
5160
account_migration.perform!
5261
end
5362

63+
def archive_file_exists?
64+
File.exist?(archive_path)
65+
end
66+
5467
def account_migration
5568
@account_migration ||= AccountMigration.new(
5669
old_person: old_person,
@@ -120,4 +133,7 @@ class ArchiveValidationFailed < RuntimeError
120133

121134
class MigrationAlreadyExists < RuntimeError
122135
end
136+
137+
class SelfMigrationNotAllowed < RuntimeError
138+
end
123139
end

app/uploaders/exported_photos.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ def store_dir
99
"uploads/users"
1010
end
1111

12+
def extension_allowlist
13+
%w[zip]
14+
end
15+
1216
def filename
13-
"#{model.username}_photos_#{secure_token}.zip" if original_filename.present?
17+
if original_filename.present?
18+
extension = File.extname(@filename) if @filename
19+
"#{model.username}_photos_#{secure_token}#{extension}"
20+
end
1421
end
1522
end

app/uploaders/exported_user.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ def store_dir
1010
end
1111

1212
def extension_allowlist
13-
%w[gz]
13+
%w[gz zip json]
1414
end
1515

1616
def filename
17-
"#{model.username}_diaspora_data_#{secure_token}.json.gz" if original_filename.present?
17+
if original_filename.present?
18+
extension = File.extname(@filename) if @filename
19+
"#{model.username}_data_#{secure_token}#{extension}"
20+
end
1821
end
1922
end

app/uploaders/secure_uploader.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
class SecureUploader < CarrierWave::Uploader::Base
44
protected
5-
def secure_token(bytes = 16)
5+
6+
def secure_token(bytes=16)
67
var = :"@#{mounted_as}_secure_token"
78
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.urlsafe_base64(bytes))
89
end

app/views/users/_edit.haml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,11 @@
193193
%hr
194194

195195
.row
196-
.col-md-6.account-data
196+
.col-md-12.account-data
197197
%h3= t(".export_data")
198+
%p If you want to migrate this account to another pod, start by exporting your data using the buttons below.
199+
.row
200+
.col-md-6
198201
%h4= t("profile")
199202
.form-group
200203
- if current_user.exporting
@@ -210,6 +213,7 @@
210213
= link_to t(".request_export"), export_profile_user_path, method: :post,
211214
class: "btn btn-default"
212215

216+
.col-md-6
213217
%h4= t("javascripts.profile.photos")
214218
.form-group
215219
- if current_user.exporting_photos
@@ -224,7 +228,24 @@
224228
= link_to t(".request_export_photos"), export_photos_user_path, method: :post,
225229
class: "btn btn-default"
226230

227-
.col-md-6
231+
.row
232+
.col-md-12
233+
%h3
234+
Import another account into
235+
= current_user.diaspora_handle
236+
%p
237+
diaspora* allows you to merge an account into another, even if the accounts are in two different pods.
238+
This way, you can migrate from one pod to another one.
239+
For this, you need to upload into this account the data and the photo archives you have exported from the
240+
settings in the other account.
241+
%strong There is no way back,
242+
an account merged into another one can't be restored.
243+
.form-group
244+
.btn.btn-primary{id: "import_account", data: {toggle: "modal", target: "#importAccountModal"}}
245+
Import another account...
246+
= render "import_account_modal"
247+
.row
248+
.col-md-12
228249
%h3
229250
= t(".close_account_text")
230251
.form-group
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
.modal.fade{id: "importAccountModal",
2+
tabindex: "-1",
3+
role: "dialog",
4+
aria: {labelledby: "importAccountModalLabel", hidden: "true"}}
5+
.modal-dialog
6+
.modal-content
7+
.modal-header
8+
%button.close{type: "button", data: {dismiss: "modal"}, aria: {hidden: "true"}}
9+
&times;
10+
%h3.modal-title{id: "importAccountModalLabel"}
11+
Import another account
12+
.modal-body
13+
%p
14+
You are about to import and merge another account into
15+
= current_user.diaspora_handle
16+
/.
17+
Here is what's going to happen:
18+
%ol
19+
%li
20+
Your profile (name, bio, birthday, gender...) will be updated with the content from the imported account
21+
%li
22+
Your settings (theme, visibility...) will be updated with those from the imported account
23+
%li
24+
All your aspects and contacts will be added to this account. This will be transparent for them, they
25+
will be linked to that account as they were linked with the account you are importing
26+
%li
27+
All posts and comments you made with the imported account will be imported in that account and seen
28+
as posted by this accounts by the other users
29+
%li
30+
%strong
31+
The imported account and all its linked data will be deleted from the previous pod.
32+
33+
%p
34+
If the pod of the account you are importing is offline, your contacts and posts will still be imported,
35+
but your contacts will receive a notification as if you started sharing with them.
36+
= form_for current_user, url: edit_user_path, html: {method: :put, multipart: true, class: "form-horizontal"} do |f|
37+
.row
38+
.col-md-6
39+
%label Select the data archive:
40+
= f.file_field :export, accept: "application/json, application/zip, application/gzip"
41+
.col-md-6
42+
%label Select the photos archive:
43+
= f.file_field :exported_photos_file, accept: "application/zip"
44+
%h4
45+
The original account will be imported
46+
%strong
47+
and deleted. This cannot be undone!
48+
%p
49+
.clearfix
50+
.btn.btn-default{data: {dismiss: "modal"}}
51+
Cancel
52+
= f.submit 'Import and merge', class: "btn btn-primary.pull-right", id: "change_email_preferences"
53+

0 commit comments

Comments
 (0)