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