summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Attachment.pm155
-rw-r--r--Bugzilla/Attachment/Database.pm59
-rw-r--r--Bugzilla/Attachment/FileSystem.pm61
-rw-r--r--Bugzilla/Config/Attachment.pm17
4 files changed, 164 insertions, 128 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 95e6b2977..07fafb11e 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -333,27 +333,7 @@ the content of the attachment
sub data {
my $self = shift;
- return $self->{data} if exists $self->{data};
-
- # First try to get the attachment data from the database.
- ($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
- FROM attach_data
- WHERE id = ?",
- undef,
- $self->id);
-
- # If there's no attachment data in the database, the attachment is stored
- # in a local file, so retrieve it from there.
- if (length($self->{data}) == 0) {
- if (open(AH, '<', $self->_get_local_filename())) {
- local $/;
- binmode AH;
- $self->{data} = <AH>;
- close(AH);
- }
- }
-
- return $self->{data};
+ return $self->{data} //= current_storage()->retrieve($self->id);
}
=over
@@ -372,60 +352,6 @@ sub datasize {
=over
-=item C<linecount>
-
-the number of lines of the attachment content
-
-=back
-
-=cut
-
-# linecount allows for getting the number of lines of an attachment
-# from the database directly if the data has not yet been loaded for
-# performance reasons.
-
-sub linecount {
- my ($self) = @_;
-
- return $self->{linecount} if exists $self->{linecount};
-
- # Limit this to just text/* attachments as this could
- # cause strange results for binary attachments.
- return if $self->contenttype !~ /^text\//;
-
- # If the data has already been loaded, we can just determine
- # line count from the data directly.
- if ($self->{data}) {
- $self->{linecount} = $self->{data} =~ tr/\n/\n/;
- }
- else {
- $self->{linecount} =
- int(Bugzilla->dbh->selectrow_array("
- SELECT LENGTH(attach_data.thedata)-LENGTH(REPLACE(attach_data.thedata,'\n',''))/LENGTH('\n')
- FROM attach_data WHERE id = ?", undef, $self->id));
-
- }
-
- # If we still do not have a linecount either the attachment
- # is stored in a local file or has been deleted. If the former,
- # we call self->data to force a load from the filesystem and
- # then do a split on newlines and count again.
- unless ($self->{linecount}) {
- $self->{linecount} = $self->data =~ tr/\n/\n/;
- }
-
- return $self->{linecount};
-}
-
-sub _get_local_filename {
- my $self = shift;
- my $hash = ($self->id % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
-}
-
-=over
-
=item C<flags>
flags that have been set on the attachment
@@ -543,13 +469,10 @@ sub _check_data {
$params->{attach_size} || ThrowUserError('zero_length_file');
# Make sure the attachment does not exceed the maximum permitted size.
- my $max_size = max(Bugzilla->params->{'maxlocalattachment'} * 1048576,
- Bugzilla->params->{'maxattachmentsize'} * 1024);
-
- if ($params->{attach_size} > $max_size) {
- my $vars = { filesize => sprintf("%.0f", $params->{attach_size}/1024) };
- ThrowUserError('file_too_large', $vars);
+ if ($params->{attach_size} > Bugzilla->params->{'maxattachmentsize'} * 1024) {
+ ThrowUserError('file_too_large', { filesize => sprintf("%.0f", $params->{attach_size}/1024) });
}
+
return $data;
}
@@ -804,46 +727,18 @@ sub create {
my $data = delete $params->{data};
my $attachment = $class->insert_create_data($params);
- my $attachid = $attachment->id;
+ $attachment->{bug} = $bug;
- # The file is too large to be stored in the DB, so we store it locally.
- if ($params->{attach_size} > Bugzilla->params->{'maxattachmentsize'} * 1024) {
- my $attachdir = bz_locations()->{'attachdir'};
- my $hash = ($attachid % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- mkdir "$attachdir/$hash", 0770;
- chmod 0770, "$attachdir/$hash";
- if (ref $data) {
- copy($data, "$attachdir/$hash/attachment.$attachid");
- close $data;
- }
- else {
- open(AH, '>', "$attachdir/$hash/attachment.$attachid");
- binmode AH;
- print AH $data;
- close AH;
- }
- $data = ''; # Will be stored in the DB.
- }
- # If we have a filehandle, we need its content to store it in the DB.
- elsif (ref $data) {
+ # store attachment data
+ if (ref($data)) {
local $/;
- # Store the content in a temp variable while we close the FH.
my $tmp = <$data>;
- close $data;
+ close($data);
$data = $tmp;
}
+ current_storage()->store($attachment->id, $data);
- my $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
-
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
-
- $attachment->{bug} = $bug;
-
- # Return the new attachment object.
+ # Return the new attachment object
return $attachment;
}
@@ -924,10 +819,10 @@ sub remove_from_db {
'SELECT id FROM flags WHERE attach_id = ?', undef, $self->id);
$dbh->do('DELETE FROM flags WHERE ' . $dbh->sql_in('id', $flag_ids))
if @$flag_ids;
- $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
$dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?, attach_size = ?
WHERE attach_id = ?', undef, ('text/plain', 0, 1, 0, $self->id));
$dbh->bz_commit_transaction();
+ current_storage->remove($self->id);
# As we don't call SUPER->remove_from_db we need to manually clear
# memcached here.
@@ -989,5 +884,33 @@ sub get_content_type {
return $content_type;
}
+sub current_storage {
+ return state $storage //= get_storage_by_name(Bugzilla->params->{attachment_storage});
+}
+
+sub get_storage_names {
+ require Bugzilla::Config::Attachment;
+ foreach my $param (Bugzilla::Config::Attachment->get_param_list) {
+ next unless $param->{name} eq 'attachment_storage';
+ return @{ $param->{choices} };
+ }
+ return [];
+}
+
+sub get_storage_by_name {
+ my ($name) = @_;
+ # all options for attachment_storage need to be handled here
+ if ($name eq 'database') {
+ require Bugzilla::Attachment::Database;
+ return Bugzilla::Attachment::Database->new();
+ }
+ elsif ($name eq 'filesystem') {
+ require Bugzilla::Attachment::FileSystem;
+ return Bugzilla::Attachment::FileSystem->new();
+ }
+ else {
+ return undef;
+ }
+}
1;
diff --git a/Bugzilla/Attachment/Database.pm b/Bugzilla/Attachment/Database.pm
new file mode 100644
index 000000000..24617cacc
--- /dev/null
+++ b/Bugzilla/Attachment/Database.pm
@@ -0,0 +1,59 @@
+# 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 strict;
+use warnings;
+
+use Bugzilla::Util qw(trick_taint);
+
+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, ?)");
+ trick_taint($data);
+ $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;
diff --git a/Bugzilla/Attachment/FileSystem.pm b/Bugzilla/Attachment/FileSystem.pm
new file mode 100644
index 000000000..675184c49
--- /dev/null
+++ b/Bugzilla/Attachment/FileSystem.pm
@@ -0,0 +1,61 @@
+# 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 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;
diff --git a/Bugzilla/Config/Attachment.pm b/Bugzilla/Config/Attachment.pm
index e6e3b7f3d..cf87f4c89 100644
--- a/Bugzilla/Config/Attachment.pm
+++ b/Bugzilla/Config/Attachment.pm
@@ -66,19 +66,12 @@ sub get_param_list {
checker => \&check_maxattachmentsize
},
- # The maximum size (in bytes) for patches and non-patch attachments.
- # The default limit is 1000KB, which is 24KB less than mysql's default
- # maximum packet size (which determines how much data can be sent in a
- # single mysql packet and thus how much data can be inserted into the
- # database) to provide breathing space for the data in other fields of
- # the attachment record as well as any mysql packet overhead (I don't
- # know of any, but I suspect there may be some.)
-
{
- name => 'maxlocalattachment',
- type => 't',
- default => '0',
- checker => \&check_numeric
+ name => 'attachment_storage',
+ type => 's',
+ choices => ['database', 'filesystem'],
+ default => 'database',
+ checker => \&check_multi
} );
return @param_list;
}