1package MogileFS::FID;
2use strict;
3use warnings;
4use Carp qw(croak);
5use MogileFS::ReplicationRequest qw(rr_upgrade);
6use MogileFS::Server;
7use overload '""' => \&as_string;
8
9BEGIN {
10    my $testing = $ENV{TESTING} ? 1 : 0;
11    eval "sub TESTING () { $testing }";
12}
13
14sub new {
15    my ($class, $fidid) = @_;
16    croak("Invalid fidid") unless $fidid;
17    return bless {
18        fidid    => $fidid,
19        dmid     => undef,
20        dkey     => undef,
21        length   => undef,
22        classid  => undef,
23        devcount => undef,
24        _loaded  => 0,
25        _devids  => undef,   # undef, or pre-loaded arrayref devid list
26    }, $class;
27}
28
29sub as_string {
30    my $self = shift;
31    "FID[f=$self->{fidid}]";
32}
33
34# mutates/blesses given row.
35sub new_from_db_row {
36    my ($class, $row) = @_;
37    # TODO: sanity check provided row more?
38    $row->{fidid}   = delete $row->{fid} or die "Missing 'fid' column";
39    $row->{_loaded} = 1;
40    return bless $row, $class;
41}
42
43# quick port of old API.  perhaps not ideal.
44sub new_from_dmid_and_key {
45    my ($class, $dmid, $key) = @_;
46    my $row = Mgd::get_store()->read_store->file_row_from_dmid_key($dmid, $key)
47        or return undef;
48    return $class->new_from_db_row($row);
49}
50
51# given a bunch of ::FID objects, populates their devids en-masse
52# (for the fsck worker, which doesn't want to do many database
53# round-trips)
54sub mass_load_devids {
55    my ($class, @fids) = @_;
56    my $sto = Mgd::get_store();
57    my $locs = $sto->fid_devids_multiple(map { $_->id } @fids);
58    my @ret;
59    foreach my $fid (@fids) {
60        $fid->{_devids} = $locs->{$fid->id} || [];
61    }
62}
63# --------------------------------------------------------------------------
64
65sub exists {
66    my $self = shift;
67    $self->_tryload;
68    return $self->{_loaded};
69}
70
71sub classid {
72    my $self = shift;
73    $self->_load;
74    return $self->{classid};
75}
76
77sub dmid {
78    my $self = shift;
79    $self->_load;
80    return $self->{dmid};
81}
82
83sub length {
84    my $self = shift;
85    $self->_load;
86    return $self->{length};
87}
88
89sub devcount {
90    my $self = shift;
91    $self->_load;
92    return $self->{devcount};
93}
94
95sub id { $_[0]{fidid} }
96
97# force loading, or die.
98sub _load {
99    return 1 if $_[0]{_loaded};
100    my $self = shift;
101    croak("FID\#$self->fidid} doesn't exist") unless $self->_tryload;
102}
103
104# return 1 if loaded, or 0 if not exist
105sub _tryload {
106    return 1 if $_[0]{_loaded};
107    my $self = shift;
108    my $row = Mgd::get_store()->file_row_from_fidid($self->{fidid})
109        or return 0;
110    $self->{$_} = $row->{$_} foreach qw(dmid dkey length classid devcount);
111    $self->{_loaded} = 1;
112    return 1;
113}
114
115sub update_devcount {
116    my ($self, %opts) = @_;
117
118    my $no_lock = delete $opts{no_lock};
119    croak "Bogus options" if %opts;
120
121    return 1 if MogileFS::Config->server_setting_cached('skip_devcount');
122
123    my $fidid = $self->{fidid};
124
125    my $sto = Mgd::get_store();
126    if ($no_lock) {
127        return $sto->update_devcount($fidid);
128    } else {
129        return $sto->update_devcount_atomic($fidid);
130    }
131}
132
133sub update_class {
134    my ($self, %opts) = @_;
135
136    my $classid = delete $opts{classid};
137    croak "Bogus options" if %opts;
138
139    my $sto = Mgd::get_store();
140    return $sto->update_classid($self->{fidid}, $classid);
141}
142
143sub enqueue_for_replication {
144    my ($self, %opts) = @_;
145    my $in       = delete $opts{in};
146    my $from_dev = delete $opts{from_device};  # devid or Device object
147    croak("Unknown options to enqueue_for_replication") if %opts;
148    my $from_devid = (ref $from_dev ? $from_dev->id : $from_dev) || undef;
149    # Still schedule for the future, but don't delay long
150    $in = 1 if (TESTING && $in);
151    Mgd::get_store()->enqueue_for_replication($self->id, $from_devid, $in);
152}
153
154sub delete {
155    my $fid = shift;
156    my $sto = Mgd::get_store();
157    my $memc = MogileFS::Config->memcache_client;
158    if ($memc) {
159        $fid->_tryload;
160    }
161    $sto->delete_fidid($fid->id);
162    if ($memc && $fid->{_loaded}) {
163        $memc->delete("mogfid:$fid->{dmid}:$fid->{dkey}");
164    }
165}
166
167# returns 1 on success, 0 on duplicate key error, dies on exception
168sub rename {
169    my ($fid, $to_key) = @_;
170    my $sto = Mgd::get_store();
171    return $sto->rename_file($fid->id, $to_key);
172}
173
174# returns array of devids that this fid is on
175# NOTE: TODO: by default, this doesn't cache.  callers might be surprised from
176#   having an old version later on.  before caching is added, auditing needs
177#   to be done.
178sub devids {
179    my $self = shift;
180
181    # if it was mass-loaded and stored in _devids arrayref, use
182    # that instead of going to db...
183    return @{$self->{_devids}} if $self->{_devids};
184
185    # else get it from the database
186    return Mgd::get_store()->read_store->fid_devids($self->id);
187}
188
189sub devs {
190    my $self = shift;
191    return map { Mgd::device_factory()->get_by_id($_) } $self->devids;
192}
193
194sub devfids {
195    my $self = shift;
196    return map { MogileFS::DevFID->new($_, $self) } $self->devids;
197}
198
199
200# return FID's class
201sub class {
202    my $self = shift;
203    return Mgd::class_factory()->get_by_id($self->dmid, $self->classid);
204}
205
206# Get reloaded the next time we're bothered.
207sub want_reload {
208    my $self = shift;
209    $self->{_loaded} = 0;
210}
211
212# returns bool: if fid's presumed-to-be-on devids meet the file class'
213# replication policy rules.  dies on failure to load class, world
214# info, etc.
215sub devids_meet_policy {
216    my $self = shift;
217    my $cls  = $self->class;
218
219    my $polobj = $cls->repl_policy_obj;
220
221    my $alldev = Mgd::device_factory()->map_by_id
222        or die "No global device map";
223
224    my @devs = $self->devs;
225
226    my %rep_args = (
227                    fid       => $self->id,
228                    on_devs   => [@devs],
229                    all_devs  => $alldev,
230                    failed    => {},
231                    min       => $cls->mindevcount,
232                    );
233    my $rr = rr_upgrade($polobj->replicate_to(%rep_args));
234    return $rr->is_happy && ! $rr->too_happy;
235}
236
237sub fsck_log {
238    my ($self, $code, $dev) = @_;
239    Mgd::get_store()->fsck_log(
240                               code  => $code,
241                               fid   => $self->id,
242                               devid => ($dev ? $dev->id : undef),
243                               );
244
245}
246
247sub forget_cached_devids {
248    my $self = shift;
249    $self->{_devids} = undef;
250}
251
252# returns MogileFS::DevFID object, after noting in the db that this fid is on this DB.
253# it trusts you that it is, and that you've verified it.
254sub note_on_device {
255    my ($fid, $dev) = @_;
256    my $dfid = MogileFS::DevFID->new($dev, $fid);
257    $dfid->add_to_db;
258    $fid->forget_cached_devids;
259    return $dfid;
260}
261
262sub forget_about_device {
263    my ($fid, $dev) = @_;
264    $dev->forget_about($fid);
265    $fid->forget_cached_devids;
266    return 1;
267}
268
269# return an FID's checksum object, undef if it's missing
270sub checksum {
271    my $self = shift;
272    my $row = Mgd::get_store()->get_checksum($self->{fidid}) or return undef;
273
274    MogileFS::Checksum->new($row);
275}
276
2771;
278
279__END__
280
281=head1 NAME
282
283MogileFS::FID - represents a unique, immutable version of a file
284
285=head1 ABOUT
286
287This class represents a "fid", or "file id", which is a unique
288revision of a file.  If you upload a file with the same key
289("filename") a dozen times, each one has a unique "fid".  Fids are
290immutable, and are what are replicated around the MogileFS farm.
291