sub data {
my $self = shift;
- return $self->{data} //= current_storage()->retrieve($self->id);
+ return $self->{data} //= $self->current_storage->get_data();
}
=over
close($data);
$data = $tmp;
}
- current_storage()->store($attachment->id, $data);
+
+ $attachment->current_storage->set_data($data)->set_class();
# Return the new attachment object
return $attachment;
WHERE attach_id = ?', undef, ('text/plain', 0, 1, 0, $self->id)
);
$dbh->bz_commit_transaction();
- current_storage()->remove($self->id);
+ $self->current_storage->remove_data()->remove_class();
# As we don't call SUPER->remove_from_db we need to manually clear
# memcached here.
}
sub current_storage {
- return state $storage
- //= get_storage_by_name(Bugzilla->params->{attachment_storage});
+ my ($self, $override_class) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Sometimes we might want to copy attachment data from one
+ # storage class to another. With this param, we can override
+ # the current class, and then call ->set_data().
+ if ($override_class) {
+ return $self->{current_storage} = $self->get_storage_by_name($override_class);
+ }
+
+ if (!$self->{current_storage}) {
+ my ($current_storage)
+ = $dbh->selectrow_array(
+ "SELECT storage_class FROM attachment_storage_class WHERE id = ?",
+ undef, $self->id);
+ if (!$current_storage) {
+ $self->{current_storage}
+ = $self->get_storage_by_name(Bugzilla->params->{attachment_storage});
+ }
+ else {
+ $self->{current_storage} = $self->get_storage_by_name($current_storage);
+ }
+ }
+
+ return $self->{current_storage};
}
sub get_storage_names {
}
sub get_storage_by_name {
- my ($name) = @_;
+ my ($self, $name) = @_;
# all options for attachment_storage need to be handled here
if ($name eq 'database') {
- require Bugzilla::Attachment::Database;
- return Bugzilla::Attachment::Database->new();
+ require Bugzilla::Attachment::Storage::Database;
+ return Bugzilla::Attachment::Storage::Database->new({attachment => $self});
}
elsif ($name eq 'filesystem') {
- require Bugzilla::Attachment::FileSystem;
- return Bugzilla::Attachment::FileSystem->new();
+ require Bugzilla::Attachment::Storage::FileSystem;
+ return Bugzilla::Attachment::Storage::FileSystem->new({attachment => $self});
}
elsif ($name eq 's3') {
- require Bugzilla::Attachment::S3;
- return Bugzilla::Attachment::S3->new();
+ require Bugzilla::Attachment::Storage::S3;
+ return Bugzilla::Attachment::Storage::S3->new({attachment => $self});
}
else {
return undef;
+++ /dev/null
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-
-package Bugzilla::Attachment::Database;
-
-use 5.10.1;
-use strict;
-use warnings;
-
-sub new {
- return bless({}, shift);
-}
-
-sub store {
- my ($self, $attach_id, $data) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare(
- "INSERT INTO attach_data (id, thedata) VALUES ($attach_id, ?)");
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
-}
-
-sub retrieve {
- my ($self, $attach_id) = @_;
- my $dbh = Bugzilla->dbh;
- my ($data)
- = $dbh->selectrow_array("SELECT thedata FROM attach_data WHERE id = ?",
- undef, $attach_id);
- return $data;
-}
-
-sub remove {
- my ($self, $attach_id) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->do("DELETE FROM attach_data WHERE id = ?", undef, $attach_id);
-}
-
-sub exists {
- my ($self, $attach_id) = @_;
- my $dbh = Bugzilla->dbh;
- my ($exists) = $dbh->selectrow_array("SELECT 1 FROM attach_data WHERE id = ?",
- undef, $attach_id);
- return !!$exists;
-}
-
-1;
+++ /dev/null
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-
-package Bugzilla::Attachment::FileSystem;
-
-use 5.10.1;
-use strict;
-use warnings;
-
-use Bugzilla::Constants qw(bz_locations);
-
-sub new {
- return bless({}, shift);
-}
-
-sub store {
- my ($self, $attach_id, $data) = @_;
- my $path = _local_path($attach_id);
- mkdir($path, 0770) unless -d $path;
- open(my $fh, '>', _local_file($attach_id));
- binmode($fh);
- print $fh $data;
- close($fh);
-}
-
-sub retrieve {
- my ($self, $attach_id) = @_;
- if (open(my $fh, '<', _local_file($attach_id))) {
- local $/;
- binmode($fh);
- my $data = <$fh>;
- close($fh);
- return $data;
- }
- return undef;
-}
-
-sub remove {
- my ($self, $attach_id) = @_;
- unlink(_local_file($attach_id));
-}
-
-sub exists {
- my ($self, $attach_id) = @_;
- return -e _local_file($attach_id);
-}
-
-sub _local_path {
- my ($attach_id) = @_;
- my $hash = sprintf('group.%03d', $attach_id % 1000);
- return bz_locations()->{attachdir} . '/' . $hash;
-}
-
-sub _local_file {
- my ($attach_id) = @_;
- return _local_path($attach_id) . '/attachment.' . $attach_id;
-}
-
-1;
+++ /dev/null
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-
-package Bugzilla::Attachment::S3;
-
-use 5.10.1;
-use strict;
-use warnings;
-
-use Bugzilla::Error;
-use Bugzilla::S3;
-
-sub new {
- my $s3 = Bugzilla::S3->new({
- aws_access_key_id => Bugzilla->params->{aws_access_key_id},
- aws_secret_access_key => Bugzilla->params->{aws_secret_access_key},
- secure => 1,
- });
- return
- bless({s3 => $s3, bucket => $s3->bucket(Bugzilla->params->{s3_bucket}),},
- shift);
-}
-
-sub store {
- my ($self, $attach_id, $data) = @_;
- unless ($self->{bucket}->add_key($attach_id, $data)) {
- warn "Failed to add attachment $attach_id to S3: "
- . $self->{bucket}->errstr . "\n";
- ThrowCodeError('s3_add_failed',
- {attach_id => $attach_id, reason => $self->{bucket}->errstr});
- }
-}
-
-sub retrieve {
- my ($self, $attach_id) = @_;
- my $response = $self->{bucket}->get_key($attach_id);
- if (!$response) {
- warn "Failed to retrieve attachment $attach_id from S3: "
- . $self->{bucket}->errstr . "\n";
- ThrowCodeError('s3_get_failed',
- {attach_id => $attach_id, reason => $self->{bucket}->errstr});
- }
- return $response->{value};
-}
-
-sub remove {
- my ($self, $attach_id) = @_;
- $self->{bucket}->delete_key($attach_id)
- or warn "Failed to remove attachment $attach_id from S3: "
- . $self->{bucket}->errstr . "\n";
-}
-
-sub exists {
- my ($self, $attach_id) = @_;
- return !!$self->{bucket}->head_key($attach_id);
-}
-
-1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Attachment::Storage::Base;
+
+use 5.10.1;
+use Moo::Role;
+
+use Type::Utils qw(class_type);
+
+requires qw(set_data get_data remove_data data_exists data_type);
+
+has 'attachment' => (
+ is => 'ro',
+ required => 1,
+ isa => class_type({class => 'Bugzilla::Attachment'})
+);
+
+sub set_class {
+ my ($self) = @_;
+ Bugzilla->dbh->do(
+ "REPLACE INTO attachment_storage_class (id, storage_class) VALUES (?, ?)",
+ undef, $self->attachment->id, $self->data_type);
+ return $self;
+}
+
+sub remove_class {
+ my ($self) = @_;
+ Bugzilla->dbh->do("DELETE FROM attachment_storage_class WHERE id = ?",
+ undef, $self->attachment->id);
+ return $self;
+}
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Attachment::Storage::Database;
+
+use 5.10.1;
+use Moo;
+
+with 'Bugzilla::Attachment::Storage::Base';
+
+sub data_type { return 'database'; }
+
+sub set_data {
+ my ($self, $data) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $sth
+ = $dbh->prepare(
+ "REPLACE INTO attach_data (id, thedata) VALUES (?, ?)"
+ );
+ $sth->bind_param(1, $self->attachment->id);
+ $sth->bind_param(2, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
+ return $self;
+}
+
+sub get_data {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($data)
+ = $dbh->selectrow_array("SELECT thedata FROM attach_data WHERE id = ?",
+ undef, $self->attachment->id);
+ return $data;
+}
+
+sub remove_data {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("DELETE FROM attach_data WHERE id = ?", undef, $self->attachment->id);
+ return $self;
+}
+
+sub data_exists {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($exists) = $dbh->selectrow_array("SELECT 1 FROM attach_data WHERE id = ?",
+ undef, $self->attachment->id);
+ return !!$exists;
+}
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Attachment::Storage::FileSystem;
+
+use 5.10.1;
+use Moo;
+
+use Bugzilla::Constants qw(bz_locations);
+
+with 'Bugzilla::Attachment::Storage::Base';
+
+sub data_type { return 'filesystem'; }
+
+sub set_data {
+ my ($self, $data) = @_;
+ my $path = $self->_local_path();
+ mkdir $path, 0770 unless -d $path;
+ open my $fh, '>', $self->_local_file();
+ binmode $fh;
+ print $fh $data;
+ close $fh;
+ return $self;
+}
+
+sub get_data {
+ my ($self) = @_;
+ if (open my $fh, '<', $self->_local_file()) {
+ local $/;
+ binmode $fh;
+ my $data = <$fh>;
+ close $fh;
+ return $data;
+ }
+}
+
+sub remove_data {
+ my ($self) = @_;
+ unlink $self->_local_file();
+ return $self;
+}
+
+sub data_exists {
+ my ($self) = @_;
+ return -e $self->_local_file();
+}
+
+sub _local_path {
+ my ($self) = @_;
+ my $hash = sprintf 'group.%03d', $self->attachment->id % 1000;
+ return bz_locations()->{attachdir} . '/' . $hash;
+}
+
+sub _local_file {
+ my ($self) = @_;
+ return $self->_local_path() . '/attachment.' . $self->attachment->id;
+}
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Attachment::Storage::S3;
+
+use 5.10.1;
+use Moo;
+
+use Bugzilla::Error;
+use Bugzilla::S3;
+
+with 'Bugzilla::Attachment::Storage::Base';
+
+has 's3' => (is => 'lazy');
+has 'bucket' => (is => 'lazy');
+
+sub _build_s3 {
+ my $self = shift;
+ $self->{s3} ||= Bugzilla::S3->new({
+ aws_access_key_id => Bugzilla->params->{aws_access_key_id},
+ aws_secret_access_key => Bugzilla->params->{aws_secret_access_key},
+ secure => 1,
+ });
+ return $self->{s3};
+}
+
+sub _build_bucket {
+ my $self = shift;
+ $self->{bucket} ||= $self->s3->bucket(Bugzilla->params->{s3_bucket});
+ return $self->bucket;
+}
+
+sub data_type { return 's3'; }
+
+sub set_data {
+ my ($self, $data) = @_;
+ my $attach_id = $self->attachment->id;
+
+ # If the attachment is larger than attachment_s3_minsize,
+ # we instead store it in the database.
+ if (Bugzilla->params->{attachment_s3_minsize}
+ && $self->attachment->datasize < Bugzilla->params->{attachment_s3_minsize})
+ {
+ require Bugzilla::Attachment::Storage::Database;
+ return Bugzilla::Attachment::Storage::Database->new({attachment => $self->attachment})
+ ->set_data($data);
+ }
+
+ unless ($self->bucket->add_key($attach_id, $data)) {
+ warn "Failed to add attachment $attach_id to S3: "
+ . $self->bucket->errstr . "\n";
+ ThrowCodeError('s3_add_failed',
+ {attach_id => $attach_id, reason => $self->bucket->errstr});
+ }
+
+ return $self;
+}
+
+sub get_data {
+ my ($self) = @_;
+ my $attach_id = $self->attachment->id;
+ my $response = $self->bucket->get_key($attach_id);
+ if (!$response) {
+ warn "Failed to retrieve attachment $attach_id from S3: "
+ . $self->bucket->errstr . "\n";
+ ThrowCodeError('s3_get_failed',
+ {attach_id => $attach_id, reason => $self->bucket->errstr});
+ }
+ return $response->{value};
+}
+
+sub remove_data {
+ my ($self) = @_;
+ my $attach_id = $self->attachment->id;
+ $self->bucket->delete_key($attach_id)
+ or warn "Failed to remove attachment $attach_id from S3: "
+ . $self->bucket->errstr . "\n";
+ return $self;
+}
+
+sub data_exists {
+ my ($self) = @_;
+ return !!$self->bucket->head_key($self->attachment->id);
+}
+
+1;
default => 'database',
checker => \&check_storage
},
+ {
+ name => 'attachment_s3_minsize',
+ type => 't',
+ default => '20000',
+ checker => \&check_numeric
+ },
{name => 's3_bucket', type => 't', default => '',},
{name => 'aws_access_key_id', type => 't', default => '',},
{name => 'aws_secret_access_key', type => 't', default => '',},
REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
},
attach_id => {
- TYPE => 'INT3',
+ TYPE => 'INT5',
REFERENCES =>
{TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
},
attachments => {
FIELDS => [
- attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ attach_id => {TYPE => 'BIGSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
bug_id => {
TYPE => 'INT3',
NOTNULL => 1,
attachments_ispatch_idx => ['ispatch'],
],
},
+
attach_data => {
FIELDS => [
id => {
- TYPE => 'INT3',
+ TYPE => 'INT5',
NOTNULL => 1,
PRIMARYKEY => 1,
REFERENCES =>
],
},
+ attachment_storage_class => {
+ FIELDS => [
+ id => {
+ TYPE => 'INT5',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ storage_class => {TYPE => 'varchar(64)', NOTNULL => 1},
+ extra_data => {TYPE => 'MEDIUMTEXT'}
+ ],
+ },
+
duplicates => {
FIELDS => [
dupe_of => {
REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
},
attach_id => {
- TYPE => 'INT3',
+ TYPE => 'INT5',
REFERENCES =>
{TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
},
user_agent => {TYPE => 'TINYTEXT', NOTNULL => 1},
timestamp => {TYPE => 'DATETIME', NOTNULL => 1},
bug_id => {TYPE => 'INT3', NOTNULL => 0},
- attach_id => {TYPE => 'INT4', NOTNULL => 0},
+ attach_id => {TYPE => 'INT5', NOTNULL => 0},
request_url => {TYPE => 'TINYTEXT', NOTNULL => 1},
method => {TYPE => 'TINYTEXT', NOTNULL => 1},
action => {TYPE => 'varchar(20)', NOTNULL => 1},
INT2 => 'smallint',
INT3 => 'mediumint',
INT4 => 'integer',
+ INT5 => 'bigint(20)',
SMALLSERIAL => 'smallint auto_increment',
MEDIUMSERIAL => 'mediumint auto_increment',
INTSERIAL => 'integer auto_increment',
+ BIGSERIAL => 'bigint(20) auto_increment',
TINYTEXT => 'tinytext',
MEDIUMTEXT => 'mediumtext',
INT2 => 'integer',
INT3 => 'integer',
INT4 => 'integer',
+ INT5 => 'integer',
SMALLSERIAL => 'integer',
MEDIUMSERIAL => 'integer',
INTSERIAL => 'integer',
+ BIGSERIAL => 'integer',
TINYTEXT => 'varchar(255)',
MEDIUMTEXT => 'varchar(4000)',
INT2 => 'integer',
INT3 => 'integer',
INT4 => 'integer',
+ INT5 => 'integer',
SMALLSERIAL => 'serial unique',
MEDIUMSERIAL => 'serial unique',
INTSERIAL => 'serial unique',
+ BIGSERIAL => 'serial unique',
TINYTEXT => 'varchar(255)',
MEDIUMTEXT => 'text',
INT2 => 'integer',
INT3 => 'integer',
INT4 => 'integer',
+ INT5 => 'integer',
SMALLSERIAL => 'SERIAL',
MEDIUMSERIAL => 'SERIAL',
INTSERIAL => 'SERIAL',
+ BIGSERIAL => 'SERIAL',
TINYTEXT => 'text',
MEDIUMTEXT => 'text',
# Bug 1612290 - dkl@mozilla.com
$dbh->bz_add_column('profiles', 'bounce_count', {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0});
+ # Bug 1588221 - dkl@mozilla.com
+ $dbh->bz_alter_column('bugs_activity', 'attach_id', {TYPE => 'INT5'});
+ $dbh->bz_alter_column('attachments', 'attach_id',
+ {TYPE => 'BIGSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_alter_column('attach_data', 'id',
+ {TYPE => 'INT5', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_alter_column('flags', 'attach_id', {TYPE => 'INT5'});
+ $dbh->bz_alter_column('user_request_log', 'attach_id', {TYPE => 'INT5', NOTNULL => 0});
+ _populate_attachment_storage_class();
+
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
{TYPE => 'DATETIME', NOTNULL => 1});
}
+sub _populate_attachment_storage_class {
+ my $dbh = Bugzilla->dbh;
+
+ # Return if we have already made these changes
+ my $count = $dbh->selectrow_array('SELECT COUNT(id) FROM attachment_storage_class');
+ if (!$count) {
+ $dbh->do(
+ "INSERT INTO attachment_storage_class (id, storage_class) SELECT attachments.id, 'database' FROM attachments ORDER BY attachments.id"
+ );
+ }
+}
+
+
1;
__END__
},
attachment_id => {
- TYPE => 'INT3',
+ TYPE => 'INT5',
REFERENCES =>
{TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
},
$field->set_in_new_bugmail(1);
$field->update();
}
+
+ # Bug 1588221 - dkl@mozilla.com
+ $dbh->bz_alter_column('flag_state_activity', 'attachment_id', {TYPE => 'INT5'});
}
sub install_filesystem {
next unless $mem->{data_len};
next unless check_attachment($attachment, $mem->{bug_id}, $mem->{data_len});
- Bugzilla::Attachment::current_storage()->store($attachment->id, $mem->{data});
+ $attachment->current_storage->set_data($mem->{data})->set_class();
}
}
elsif ($cmd eq 'check') {
while (my $mem = $archive->read_member) {
warn "checking $mem->{attach_id}\n";
- my $attachment = Bugzilla::Attachment->new($mem->{attach_id});
+ my $attachment
+ = Bugzilla::Attachment->new({id => $mem->{attach_id}, cache => 1});
die "bad attachment\n"
unless check_attachment($attachment, $mem->{bug_id}, $mem->{data_len});
$remove_ok{$mem->{attach_id}} = 1;
chomp $attach_id;
if ($remove_ok{$attach_id}) {
warn "removing $attach_id\n";
- Bugzilla::Attachment::current_storage()->remove($attach_id);
+ my $attachment = Bugzilla::Attachment->new({id => $attach_id, cache => 1});
+ $attachment->current_storage->remove()->remove_class();
}
else {
warn "Unable to remove $attach_id, as it did not occur in the archive.\n";
my @storage_names = Bugzilla::Attachment->get_storage_names();
my %options;
-GetOptions(\%options, 'mirror=s@{2}', 'copy=s@{2}', 'delete=s') or exit(1);
-unless ($options{mirror} || $options{copy} || $options{delete}) {
+GetOptions(\%options, 'migrate=s@{2}', 'mirror=s@{2}', 'copy=s@{2}', 'delete=s') or exit 1;
+unless ($options{migrate} || $options{mirror} || $options{copy} || $options{delete}) {
die <<EOF;
Syntax:
+ migrate-attachments.pl --migrate source destination
migrate-attachments.pl --mirror source destination
migrate-attachments.pl --copy source destination
migrate-attachments.pl --delete source
+'migrate'
+ Migrates all attachments from the specified source to the destination.
+ Attachments which already exist at the destination will be overwritten.
+ This also sets the storage class so that the system loads the data from the
+ new destination.
+
+ e.g. migrate-attachments.pl --migrate database s3
+
'mirror'
Copies all attachments from the specified source to the destination.
Attachments which already exist at the destination will not be copied
my $dbh = Bugzilla->dbh;
+if ($options{migrate}) {
+ if ($options{migrate}->[0] eq $options{migrate}->[1]) {
+ die "Source and destination must be different\n";
+ }
+ my ($source, $dest) = @{$options{migrate}};
+
+ # Do not migrate from database to s3 if minsize is set
+ my $where;
+ if ($source eq 'database' && $dest eq 's3' && Bugzilla->params->{attachment_s3_minsize}) {
+ $where = 'WHERE attach_size > ' . int Bugzilla->params->{attachment_s3_minsize};
+ }
+
+ my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM attachments $where");
+ confirm(sprintf
+ 'Migrate %s attachments from %s to %s?', $total, @{$options{migrate}});
+
+ my $sth
+ = $dbh->prepare("SELECT attach_id FROM attachments $where ORDER BY attach_id DESC");
+ $sth->execute();
+ my ($count, $migrated) = (0, 0);
+ while (my ($attach_id) = $sth->fetchrow_array()) {
+ indicate_progress({total => $total, current => ++$count});
+
+ my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1});
+
+ # skip deleted attachments
+ next if $attachment->datasize == 0;
+
+ # migrate the attachment
+ if (my $data = $attachment->current_storage($source)->get_data()) {
+ $attachment->current_storage($dest)->set_data($data)->set_class();
+ $attachment->current_storage($source)->remove_data();
+ $migrated++;
+ }
+ }
+ print "\n";
+ print "Attachments migrated: $migrated\n";
+}
+
if ($options{mirror}) {
if ($options{mirror}->[0] eq $options{mirror}->[1]) {
die "Source and destination must be different\n";
}
- my ($source, $dest) = map { storage($_) } @{$options{mirror}};
+ my ($source, $dest) = @{$options{mirror}};
my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM attachments");
- confirm(sprintf(
- 'Mirror %s attachments from %s to %s?', $total, @{$options{mirror}}));
+ confirm(sprintf
+ 'Mirror %s attachments from %s to %s?', $total, @{$options{mirror}});
- my $sth = $dbh->prepare(
- "SELECT attach_id, attach_size FROM attachments ORDER BY attach_id DESC");
+ my $sth
+ = $dbh->prepare("SELECT attach_id FROM attachments ORDER BY attach_id DESC");
$sth->execute();
my ($count, $deleted, $stored) = (0, 0, 0);
- while (my ($attach_id, $attach_size) = $sth->fetchrow_array()) {
+ while (my ($attach_id) = $sth->fetchrow_array()) {
indicate_progress({total => $total, current => ++$count});
+ my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1});
+
# remove deleted attachments
- if ($attach_size == 0 && $dest->exists($attach_id)) {
- $dest->remove($attach_id);
+ if ($attachment->datasize == 0 && $attachment->current_storage($dest)->data_exists()) {
+ $attachment->current_storage($dest)->remove_data();
$deleted++;
}
# store attachments that don't already exist
- elsif ($attach_size != 0 && !$dest->exists($attach_id)) {
- if (my $data = $source->retrieve($attach_id)) {
- $dest->store($attach_id, $data);
+ elsif ($attachment->datasize != 0 && !$attachment->current_storage($dest)->data_exists())
+ {
+ if (my $data = $attachment->current_storage($source)->get_data()) {
+ $attachment->current_storage($dest)->set_data($data);
$stored++;
}
}
if ($options{copy}->[0] eq $options{copy}->[1]) {
die "Source and destination must be different\n";
}
- my ($source, $dest) = map { storage($_) } @{$options{copy}};
+ my ($source, $dest) = @{$options{copy}};
my ($total)
= $dbh->selectrow_array(
"SELECT COUNT(*) FROM attachments WHERE attach_size != 0");
- confirm(sprintf(
- 'Copy %s attachments from %s to %s?', $total, @{$options{copy}}));
+ confirm(sprintf
+ 'Copy %s attachments from %s to %s?', $total, @{$options{copy}});
my $sth
= $dbh->prepare(
- "SELECT attach_id, attach_size FROM attachments WHERE attach_size != 0 ORDER BY attach_id DESC"
+ "SELECT attach_id FROM attachments WHERE attach_size != 0 ORDER BY attach_id DESC"
);
$sth->execute();
my ($count, $stored) = (0, 0);
- while (my ($attach_id, $attach_size) = $sth->fetchrow_array()) {
+ while (my ($attach_id) = $sth->fetchrow_array()) {
indicate_progress({total => $total, current => ++$count});
+ my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1});
+
# store attachments that don't already exist
- if (!$dest->exists($attach_id)) {
- if (my $data = $source->retrieve($attach_id)) {
- $dest->store($attach_id, $data);
+ if (!$attachment->current_storage($dest)->data_exists()) {
+ if (my $data = $attachment->current_storage($source)->get_data()) {
+ $attachment->current_storage($dest)->set_data($data);
$stored++;
}
}
}
elsif ($options{delete}) {
- my $storage = storage($options{delete});
+ my $storage = $options{delete};
my ($total)
= $dbh->selectrow_array(
"SELECT COUNT(*) FROM attachments WHERE attach_size != 0");
- confirm(sprintf('DELETE %s attachments from %s?', $total, $options{delete}));
+ confirm(sprintf 'DELETE %s attachments from %s?', $total, $options{delete});
my $sth
= $dbh->prepare(
my ($count, $deleted) = (0, 0);
while (my ($attach_id) = $sth->fetchrow_array()) {
indicate_progress({total => $total, current => ++$count});
- if ($storage->exists($attach_id)) {
- $storage->remove($attach_id);
+
+ my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1});
+
+ if ($attachment->current_storage($storage)->data_exists()) {
+ $attachment->current_storage($storage)->remove_data();
$deleted++;
}
}
print "Attachments deleted: $deleted\n";
}
-sub storage {
- my ($name) = @_;
- my $storage = Bugzilla::Attachment::get_storage_by_name($name)
- or die "Invalid attachment location: $name\n";
- return $storage;
-}
-
sub confirm {
my ($prompt) = @_;
print $prompt, "\n\nPress <Ctrl-C> to stop or <Enter> to continue..\n";
- getc();
+ getc;
}
"must use <tt>scripts/migrate-attachments</tt> to migrate existing " _
"attachments.",
+ attachment_s3_minsize =>
+ "When attachment_storage is set to S3, only attachments larger than this " _
+ "value will be uploaded to S3 for performance reasons. Smaller attachments " _
+ "will be stored in the database."
+
}
%]