1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::VirtualFS;
10
11use strict;
12use warnings;
13
14our @ObjectDependencies = (
15    'Kernel::Config',
16    'Kernel::System::DB',
17    'Kernel::System::Log',
18    'Kernel::System::Main',
19);
20
21=head1 NAME
22
23Kernel::System::VirtualFS - virtual filesystem lib
24
25=head1 DESCRIPTION
26
27All virtual filesystem functions.
28
29=head1 PUBLIC INTERFACE
30
31=head2 new()
32
33Don't use the constructor directly, use the ObjectManager instead:
34
35    my $VirtualFSObject = $Kernel::OM->Get('Kernel::System::VirtualFS');
36
37=cut
38
39sub new {
40    my ( $Type, %Param ) = @_;
41
42    # allocate new hash for object
43    my $Self = {};
44    bless( $Self, $Type );
45
46    # load backend
47    $Self->{BackendDefault} = $Kernel::OM->Get('Kernel::Config')->Get('VirtualFS::Backend')
48        || 'Kernel::System::VirtualFS::DB';
49
50    if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Self->{BackendDefault} ) ) {
51        return;
52    }
53
54    $Self->{Backend}->{ $Self->{BackendDefault} } = $Self->{BackendDefault}->new();
55
56    return $Self;
57}
58
59=head2 Read()
60
61read a file from virtual file system
62
63    my %File = $VirtualFSObject->Read(
64        Filename => '/Object/some/name.txt',
65        Mode     => 'utf8',
66
67        # optional
68        DisableWarnings => 1,
69    );
70
71returns
72
73    my %File = (
74        Content  => $ContentSCALAR,
75
76        # preferences data
77        Preferences => {
78
79            # generated automatically
80            FilesizeRaw        => 12345,
81
82            # optional
83            ContentType        => 'text/plain',
84            ContentID          => '<some_id@example.com>',
85            ContentAlternative => 1,
86            SomeCustomParams   => 'with our own value',
87        },
88    );
89
90=cut
91
92sub Read {
93    my ( $Self, %Param ) = @_;
94
95    # check needed stuff
96    for (qw(Filename Mode)) {
97        if ( !$Param{$_} ) {
98            $Kernel::OM->Get('Kernel::System::Log')->Log(
99                Priority => 'error',
100                Message  => "Need $_!"
101            );
102            return;
103        }
104    }
105
106    # lookup
107    my ( $FileID, $BackendKey, $Backend ) = $Self->_FileLookup( $Param{Filename} );
108    if ( !$BackendKey ) {
109        if ( !$Param{DisableWarnings} ) {
110            $Kernel::OM->Get('Kernel::System::Log')->Log(
111                Priority => 'error',
112                Message  => "No such file '$Param{Filename}'!",
113            );
114        }
115        return;
116    }
117
118    # get database object
119    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
120
121    # get preferences
122    my %Preferences;
123    return if !$DBObject->Prepare(
124        SQL => 'SELECT preferences_key, preferences_value FROM '
125            . 'virtual_fs_preferences WHERE virtual_fs_id = ?',
126        Bind => [ \$FileID ],
127    );
128
129    while ( my @Row = $DBObject->FetchrowArray() ) {
130        $Preferences{ $Row[0] } = $Row[1];
131    }
132
133    # load backend (if not default)
134    if ( !$Self->{Backend}->{$Backend} ) {
135
136        return if !$Kernel::OM->Get('Kernel::System::Main')->Require($Backend);
137
138        $Self->{Backend}->{$Backend} = $Backend->new();
139
140        return if !$Self->{Backend}->{$Backend};
141    }
142
143    # get file
144    my $Content = $Self->{Backend}->{$Backend}->Read(
145        %Param,
146        BackendKey => $BackendKey,
147    );
148    return if !$Content;
149
150    return (
151        Preferences => \%Preferences,
152        Content     => $Content,
153    );
154}
155
156=head2 Write()
157
158write a file to virtual file system
159
160    my $Success = $VirtualFSObject->Write(
161        Content  => \$Content,
162        Filename => '/Object/SomeFileName.txt',
163        Mode     => 'binary'            # (binary|utf8)
164
165        # optional, preferences data
166        Preferences => {
167            ContentType        => 'text/plain',
168            ContentID          => '<some_id@example.com>',
169            ContentAlternative => 1,
170            SomeCustomParams   => 'with our own value',
171        },
172    );
173
174=cut
175
176sub Write {
177    my ( $Self, %Param ) = @_;
178
179    # check needed stuff
180    for (qw(Filename Content Mode)) {
181        if ( !$Param{$_} ) {
182            $Kernel::OM->Get('Kernel::System::Log')->Log(
183                Priority => 'error',
184                Message  => "Need $_!"
185            );
186            return;
187        }
188    }
189
190    # lookup
191    my ($FileID) = $Self->_FileLookup( $Param{Filename} );
192    if ($FileID) {
193        $Kernel::OM->Get('Kernel::System::Log')->Log(
194            Priority => 'error',
195            Message  => "File already exists '$Param{Filename}'!",
196        );
197        return;
198    }
199
200    # get database object
201    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
202
203    # insert
204    return if !$DBObject->Do(
205        SQL => 'INSERT INTO virtual_fs (filename, backend_key, backend, create_time)'
206            . ' VALUES ( ?, \'TMP\', ?, current_timestamp)',
207        Bind => [ \$Param{Filename}, \$Self->{BackendDefault} ],
208    );
209
210    ($FileID) = $Self->_FileLookup( $Param{Filename} );
211
212    if ( !$FileID ) {
213        $Kernel::OM->Get('Kernel::System::Log')->Log(
214            Priority => 'error',
215            Message  => "Unable to store '$Param{Filename}'!",
216        );
217        return;
218    }
219
220    # size calculation
221    $Param{Preferences}->{FilesizeRaw} = bytes::length( ${ $Param{Content} } );
222
223    # insert preferences
224    for my $Key ( sort keys %{ $Param{Preferences} } ) {
225        return if !$DBObject->Do(
226            SQL => 'INSERT INTO virtual_fs_preferences '
227                . '(virtual_fs_id, preferences_key, preferences_value) VALUES ( ?, ?, ?)',
228            Bind => [ \$FileID, \$Key, \$Param{Preferences}->{$Key} ],
229        );
230    }
231
232    # store file
233    my $BackendKey = $Self->{Backend}->{ $Self->{BackendDefault} }->Write(%Param);
234    return if !$BackendKey;
235
236    # update backend key
237    return if !$DBObject->Do(
238        SQL  => 'UPDATE virtual_fs SET backend_key = ? WHERE id = ?',
239        Bind => [ \$BackendKey, \$FileID ],
240    );
241
242    return 1;
243}
244
245=head2 Delete()
246
247delete a file from virtual file system
248
249    my $Success = $VirtualFSObject->Delete(
250        Filename => '/Object/SomeFileName.txt',
251
252        # optional
253        DisableWarnings => 1,
254    );
255
256=cut
257
258sub Delete {
259    my ( $Self, %Param ) = @_;
260
261    # check needed stuff
262    for (qw(Filename)) {
263        if ( !$Param{$_} ) {
264            $Kernel::OM->Get('Kernel::System::Log')->Log(
265                Priority => 'error',
266                Message  => "Need $_!"
267            );
268            return;
269        }
270    }
271
272    # lookup
273    my ( $FileID, $BackendKey, $Backend ) = $Self->_FileLookup( $Param{Filename} );
274    if ( !$FileID ) {
275        if ( !$Param{DisableWarnings} ) {
276            $Kernel::OM->Get('Kernel::System::Log')->Log(
277                Priority => 'error',
278                Message  => "No such file '$Param{Filename}'!",
279            );
280        }
281        return;
282    }
283
284    # load backend (if not default)
285    if ( !$Self->{Backend}->{$Backend} ) {
286
287        return if !$Kernel::OM->Get('Kernel::System::Main')->Require($Backend);
288
289        $Self->{Backend}->{$Backend} = $Backend->new();
290
291        return if !$Self->{Backend}->{$Backend};
292    }
293
294    # get database object
295    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
296
297    # delete preferences
298    return if !$DBObject->Do(
299        SQL  => 'DELETE FROM virtual_fs_preferences WHERE virtual_fs_id = ?',
300        Bind => [ \$FileID ],
301    );
302
303    # delete
304    return if !$DBObject->Do(
305        SQL  => 'DELETE FROM virtual_fs WHERE id = ?',
306        Bind => [ \$FileID ],
307    );
308
309    # delete file
310    return $Self->{Backend}->{$Backend}->Delete(
311        %Param,
312        BackendKey => $BackendKey,
313    );
314}
315
316=head2 Find()
317
318find files in virtual file system
319
320only for file name
321
322    my @List = $VirtualFSObject->Find(
323        Filename => '/Object/some_what/*.txt',
324    );
325
326only for preferences
327
328    my @List = $VirtualFSObject->Find(
329        Preferences => {
330            ContentType => 'text/plain',
331        },
332    );
333
334for file name and for preferences
335
336    my @List = $VirtualFSObject->Find(
337        Filename    => '/Object/some_what/*.txt',
338        Preferences => {
339            ContentType => 'text/plain',
340        },
341    );
342
343Returns:
344
345    my @List = (
346      '/Object/some/file.txt',
347      '/Object/my.pdf',
348    );
349
350=cut
351
352sub Find {
353    my ( $Self, %Param ) = @_;
354
355    # check needed stuff
356    if ( !$Param{Filename} && !$Param{Preferences} ) {
357        $Kernel::OM->Get('Kernel::System::Log')->Log(
358            Priority => 'error',
359            Message  => 'Need Filename or/and Preferences!',
360        );
361        return;
362    }
363
364    # get database object
365    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
366
367    # get like escape string needed for some databases (e.g. oracle)
368    my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
369
370    # prepare file name search
371    my $SQLResult = 'vfs.filename';
372    my $SQLTable  = 'virtual_fs vfs ';
373    my $SQLWhere  = '';
374    my @SQLBind;
375    if ( $Param{Filename} ) {
376        my $Like = $Param{Filename};
377        $Like =~ s/\*/%/g;
378        $Like = $DBObject->Quote( $Like, 'Like' );
379        $SQLWhere .= "vfs.filename LIKE '$Like' $LikeEscapeString";
380    }
381
382    # prepare preferences search
383    if ( $Param{Preferences} ) {
384        $SQLResult = 'vfs.filename, vfsp.preferences_key, vfsp.preferences_value';
385        $SQLTable .= ', virtual_fs_preferences vfsp';
386        if ($SQLWhere) {
387            $SQLWhere .= ' AND ';
388        }
389        $SQLWhere .= 'vfs.id = vfsp.virtual_fs_id ';
390        my $SQL = '';
391        for my $Key ( sort keys %{ $Param{Preferences} } ) {
392            if ($SQL) {
393                $SQL .= ' OR ';
394            }
395            $SQL .= '(vfsp.preferences_key = ? AND ';
396            push @SQLBind, \$Key;
397
398            my $Value = $Param{Preferences}->{$Key};
399            if ( $Value =~ /(\*|\%)/ ) {
400                $Value =~ s/\*/%/g;
401                $Value = $DBObject->Quote( $Value, 'Like' );
402                $SQL .= "vfsp.preferences_value LIKE '$Value' $LikeEscapeString";
403            }
404            else {
405                $SQL .= 'vfsp.preferences_value = ?';
406                push @SQLBind, \$Value;
407            }
408            $SQL .= ')';
409        }
410
411        $SQLWhere .= " AND ($SQL)";
412    }
413
414    # search
415    return if !$DBObject->Prepare(
416        SQL  => "SELECT $SQLResult FROM $SQLTable WHERE $SQLWhere",
417        Bind => \@SQLBind,
418    );
419    my @List;
420    my %Result;
421    while ( my @Row = $DBObject->FetchrowArray() ) {
422        if ( $Param{Preferences} ) {
423            for my $Key ( sort keys %{ $Param{Preferences} } ) {
424                $Result{ $Row[0] }->{ $Row[1] } = $Row[2];
425            }
426        }
427        else {
428            push @List, $Row[0];
429        }
430    }
431
432    # check preferences search
433    if ( $Param{Preferences} ) {
434        FILE:
435        for my $File ( sort keys %Result ) {
436            for my $Key ( sort keys %{ $Param{Preferences} } ) {
437                my $DB    = $Result{$File}->{$Key};
438                my $Given = $Param{Preferences}->{$Key};
439                next FILE if defined $DB  && !defined $Given;
440                next FILE if !defined $DB && defined $Given;
441                if ( $Given =~ /\*/ ) {
442                    $Given           =~ s/\*/.\*/g;
443                    $Given           =~ s/\//\\\//g;
444                    next FILE if $DB !~ /$Given/;
445                }
446                else {
447                    next FILE if $DB ne $Given;
448                }
449            }
450            push @List, $File;
451        }
452    }
453
454    # return result
455    return @List;
456}
457
458=begin Internal:
459
460returns internal meta information, unique file id, where and with what arguments the
461file is stored
462
463    my ( $FileID, $BackendKey, $Backend ) = $Self->_FileLookup( '/Object/SomeFile.txt' );
464
465=cut
466
467sub _FileLookup {
468    my ( $Self, $Filename ) = @_;
469
470    # get database object
471    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
472
473    # lookup
474    return if !$DBObject->Prepare(
475        SQL  => 'SELECT id, backend_key, backend FROM virtual_fs WHERE filename = ?',
476        Bind => [ \$Filename ],
477    );
478
479    my $FileID;
480    my $BackendKey;
481    my $Backend;
482    while ( my @Row = $DBObject->FetchrowArray() ) {
483        $FileID     = $Row[0];
484        $BackendKey = $Row[1];
485        $Backend    = $Row[2];
486    }
487
488    return ( $FileID, $BackendKey, $Backend );
489}
490
491=end Internal:
492
493=cut
494
4951;
496
497=head1 TERMS AND CONDITIONS
498
499This software is part of the OTRS project (L<https://otrs.org/>).
500
501This software comes with ABSOLUTELY NO WARRANTY. For details, see
502the enclosed file COPYING for license information (GPL). If you
503did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
504
505=cut
506