|
Lines 35-43
use base qw(Koha::Object);
Link Here
|
| 35 |
|
35 |
|
| 36 |
Koha::File::Transport - Base class for file transport handling |
36 |
Koha::File::Transport - Base class for file transport handling |
| 37 |
|
37 |
|
|
|
38 |
=head1 SYNOPSIS |
| 39 |
|
| 40 |
use Koha::File::Transports; |
| 41 |
|
| 42 |
# Create/retrieve a transport (polymorphic - returns SFTP/FTP/Local subclass) |
| 43 |
my $transport = Koha::File::Transports->find($id); |
| 44 |
|
| 45 |
# SIMPLIFIED API (recommended) - connection and directory management are automatic |
| 46 |
# Connections are established on-demand and directories are managed automatically |
| 47 |
|
| 48 |
# Example 1: Upload to custom path (connection happens automatically) |
| 49 |
$transport->upload_file('/local/file.txt', 'remote.txt', { path => '/custom/dir/' }); |
| 50 |
|
| 51 |
# Example 2: Download using configured download_directory (auto-managed) |
| 52 |
$transport->download_file('remote.txt', '/local/file.txt'); |
| 53 |
|
| 54 |
# Example 3: List files in custom directory (one-shot operation) |
| 55 |
my $files = $transport->list_files({ path => '/some/directory/' }); |
| 56 |
|
| 57 |
# TRADITIONAL API - manual connection and directory management |
| 58 |
# Useful when you need fine-grained control or want to perform multiple |
| 59 |
# operations in the same directory without repeating the path |
| 60 |
|
| 61 |
$transport->connect(); # Optional - will auto-connect if omitted |
| 62 |
$transport->change_directory('/work/dir/'); # Sets working directory |
| 63 |
$transport->upload_file('/local/1.txt', '1.txt'); |
| 64 |
$transport->upload_file('/local/2.txt', '2.txt'); # Uses same directory |
| 65 |
my $files = $transport->list_files(); # Lists /work/dir/ |
| 66 |
$transport->rename_file('1.txt', '1_old.txt'); |
| 67 |
$transport->download_file('2.txt', '/local/2.txt'); |
| 68 |
$transport->disconnect(); # Optional - cleaned up automatically |
| 69 |
|
| 70 |
# HYBRID APPROACH - mixing both APIs |
| 71 |
# Once you explicitly set a directory, auto-management is disabled |
| 72 |
|
| 73 |
$transport->change_directory('/work/dir/'); # Explicit directory change |
| 74 |
$transport->upload_file('/local/file.txt', 'file.txt'); # Uses /work/dir/ |
| 75 |
$transport->list_files(); # Still uses /work/dir/ |
| 76 |
# The configured upload_directory/download_directory won't be used anymore |
| 77 |
|
| 38 |
=head1 DESCRIPTION |
78 |
=head1 DESCRIPTION |
| 39 |
|
79 |
|
| 40 |
Base class providing common functionality for FTP/SFTP file transport. |
80 |
Base class providing common functionality for FTP/SFTP/Local file transport. |
|
|
81 |
|
| 82 |
This class supports two distinct usage patterns: |
| 83 |
|
| 84 |
=head2 Simplified API (Auto-Managing) |
| 85 |
|
| 86 |
The simplified API automatically manages connections and directories: |
| 87 |
|
| 88 |
=over 4 |
| 89 |
|
| 90 |
=item * B<Automatic Connection Management> |
| 91 |
|
| 92 |
You never need to call connect() or disconnect(). Connections are established |
| 93 |
on-demand when you call file operation methods and are automatically cleaned |
| 94 |
up when the object is destroyed. |
| 95 |
|
| 96 |
=item * B<Flexible Directory Management> |
| 97 |
|
| 98 |
Each file operation (upload_file, download_file, list_files) accepts an optional |
| 99 |
options hashref with a 'path' key to specify a custom directory for that operation: |
| 100 |
|
| 101 |
$transport->upload_file($local, $remote, { path => '/custom/dir/' }); |
| 102 |
|
| 103 |
If no path is provided, the transport uses its configured upload_directory |
| 104 |
(for uploads) or download_directory (for downloads/listings). |
| 105 |
|
| 106 |
=item * B<No State Maintained> |
| 107 |
|
| 108 |
Each operation is independent. The transport doesn't remember which directory |
| 109 |
you used in previous operations, making the API stateless and safe for |
| 110 |
concurrent usage patterns. |
| 111 |
|
| 112 |
=back |
| 113 |
|
| 114 |
=head2 Traditional API (Explicit Control) |
| 115 |
|
| 116 |
The traditional API provides manual control over connection and directory state: |
| 117 |
|
| 118 |
=over 4 |
| 119 |
|
| 120 |
=item * B<Explicit Connection Control> |
| 121 |
|
| 122 |
While connect() and disconnect() are still available and can be called explicitly, |
| 123 |
they are entirely optional. The simplified API manages connections automatically. |
| 124 |
|
| 125 |
=item * B<Stateful Directory Management> |
| 126 |
|
| 127 |
Once you call change_directory() explicitly, the transport switches to "manual mode" |
| 128 |
and remembers your working directory. Subsequent operations will use this directory |
| 129 |
and automatic directory management is disabled: |
| 130 |
|
| 131 |
$transport->change_directory('/work/'); |
| 132 |
$transport->upload_file($local, $remote); # Uses /work/, not upload_directory |
| 133 |
$transport->list_files(); # Lists /work/, not download_directory |
| 134 |
|
| 135 |
=item * B<Session-Based Operations> |
| 136 |
|
| 137 |
This is useful when you need to perform multiple operations in the same directory |
| 138 |
without repeating the path parameter each time. |
| 139 |
|
| 140 |
=back |
| 141 |
|
| 142 |
=head2 How It Works |
| 143 |
|
| 144 |
The implementation uses a C<_user_set_directory> flag to track which mode is active: |
| 145 |
|
| 146 |
=over 4 |
| 147 |
|
| 148 |
=item * When you call change_directory() explicitly, the flag is set to true |
| 149 |
|
| 150 |
=item * When the flag is true, auto-directory management is disabled |
| 151 |
|
| 152 |
=item * When the flag is false (default), operations use their path option or fall back to configured directories |
| 153 |
|
| 154 |
=item * The flag is reset to false when a new connection is established |
| 155 |
|
| 156 |
=back |
| 157 |
|
| 158 |
This design allows you to mix both approaches in the same codebase, choosing |
| 159 |
the right pattern for each use case. |
| 41 |
|
160 |
|
| 42 |
=cut |
161 |
=cut |
| 43 |
|
162 |
|
|
Lines 92-97
sub store {
Link Here
|
| 92 |
return $self; |
211 |
return $self; |
| 93 |
} |
212 |
} |
| 94 |
|
213 |
|
|
|
214 |
=head3 _ensure_connected |
| 215 |
|
| 216 |
$transport->_ensure_connected(); |
| 217 |
|
| 218 |
Internal method that ensures a connection exists, connecting if needed. |
| 219 |
Returns true if connected, false if connection failed. |
| 220 |
|
| 221 |
=cut |
| 222 |
|
| 223 |
sub _ensure_connected { |
| 224 |
my ($self) = @_; |
| 225 |
|
| 226 |
# Check if already connected (transport-specific) |
| 227 |
return 1 if $self->_is_connected(); |
| 228 |
|
| 229 |
# Attempt to connect (which will reset directory state) |
| 230 |
return $self->connect(); |
| 231 |
} |
| 232 |
|
| 233 |
=head3 _is_connected |
| 234 |
|
| 235 |
my $connected = $transport->_is_connected(); |
| 236 |
|
| 237 |
Internal method to check if transport is currently connected. |
| 238 |
Must be implemented by subclasses. |
| 239 |
|
| 240 |
=cut |
| 241 |
|
| 242 |
sub _is_connected { |
| 243 |
my ($self) = @_; |
| 244 |
die "Subclass must implement _is_connected"; |
| 245 |
} |
| 246 |
|
| 247 |
=head3 _auto_change_directory |
| 248 |
|
| 249 |
$transport->_auto_change_directory($dir_type, $custom_path); |
| 250 |
|
| 251 |
Internal method that automatically changes to the appropriate directory. |
| 252 |
$dir_type is 'upload' or 'download', $custom_path is optional override. |
| 253 |
|
| 254 |
=cut |
| 255 |
|
| 256 |
sub _auto_change_directory { |
| 257 |
my ( $self, $dir_type, $custom_path ) = @_; |
| 258 |
|
| 259 |
my $target_dir; |
| 260 |
if ($custom_path) { |
| 261 |
$target_dir = $custom_path; |
| 262 |
} elsif ( $dir_type eq 'upload' ) { |
| 263 |
$target_dir = $self->upload_directory; |
| 264 |
} elsif ( $dir_type eq 'download' ) { |
| 265 |
$target_dir = $self->download_directory; |
| 266 |
} |
| 267 |
|
| 268 |
return 1 unless $target_dir; # No directory to change to |
| 269 |
|
| 270 |
# Call the internal _change_directory directly to avoid setting the flag |
| 271 |
# Only explicit user calls to change_directory() should set the flag |
| 272 |
return $self->_change_directory($target_dir); |
| 273 |
} |
| 274 |
|
| 95 |
=head3 plain_text_password |
275 |
=head3 plain_text_password |
| 96 |
|
276 |
|
| 97 |
my $password = $server->plain_text_password; |
277 |
my $password = $server->plain_text_password; |
|
Lines 188-254
Interface methods that must be implemented by subclasses
Link Here
|
| 188 |
|
368 |
|
| 189 |
=head3 connect |
369 |
=head3 connect |
| 190 |
|
370 |
|
| 191 |
$transport->connect(); |
371 |
my $success = $transport->connect(); |
|
|
372 |
|
| 373 |
Establishes a connection to the remote server. |
| 192 |
|
374 |
|
| 193 |
Method for connecting the current transport to the file server |
375 |
B<Note:> Calling this method is entirely optional. All file operations |
|
|
376 |
(upload_file, download_file, list_files, etc.) automatically establish |
| 377 |
a connection if one doesn't already exist. You only need to call this |
| 378 |
explicitly if you want to verify connectivity before performing operations. |
| 379 |
|
| 380 |
B<Returns:> True on success, undef on failure. Check object_messages() for details. |
| 194 |
|
381 |
|
| 195 |
=cut |
382 |
=cut |
| 196 |
|
383 |
|
| 197 |
sub connect { |
384 |
sub connect { |
| 198 |
my ($self) = @_; |
385 |
my ($self) = @_; |
| 199 |
die "Subclass must implement connect"; |
386 |
|
|
|
387 |
# Reset directory state on new connection |
| 388 |
$self->{_user_set_directory} = 0; |
| 389 |
|
| 390 |
return $self->_connect(); |
| 391 |
} |
| 392 |
|
| 393 |
=head3 _connect |
| 394 |
|
| 395 |
my $success = $transport->_connect(); |
| 396 |
|
| 397 |
Internal method that performs the protocol-specific connection operation. |
| 398 |
Must be implemented by subclasses. Called by connect() after resetting |
| 399 |
directory state. |
| 400 |
|
| 401 |
=cut |
| 402 |
|
| 403 |
sub _connect { |
| 404 |
my ($self) = @_; |
| 405 |
die "Subclass must implement _connect"; |
| 200 |
} |
406 |
} |
| 201 |
|
407 |
|
| 202 |
=head3 upload_file |
408 |
=head3 upload_file |
| 203 |
|
409 |
|
| 204 |
$transport->upload_file($file); |
410 |
# Signature: |
|
|
411 |
my $success = $transport->upload_file($local_file, $remote_file, \%options); |
| 412 |
|
| 413 |
Uploads a file to the remote server. Automatically establishes a connection if needed. |
| 414 |
|
| 415 |
B<Parameters:> |
| 416 |
|
| 417 |
=over 4 |
| 418 |
|
| 419 |
=item * C<$local_file> - Path to local file to upload (required) |
| 420 |
|
| 421 |
=item * C<$remote_file> - Remote filename (not a path, just the filename) (required) |
| 422 |
|
| 423 |
=item * C<\%options> - Optional hashref with keys: |
| 424 |
|
| 425 |
=over 4 |
| 426 |
|
| 427 |
=item * C<path> - Directory path to upload to. If provided, uses this directory |
| 428 |
for this operation only (simplified API). If omitted, behavior depends on whether |
| 429 |
change_directory() has been called explicitly (see DESCRIPTION). |
| 430 |
|
| 431 |
=back |
| 432 |
|
| 433 |
=back |
| 434 |
|
| 435 |
B<Usage Patterns:> |
| 436 |
|
| 437 |
# Pattern 1: Simplified API with custom path |
| 438 |
$transport->upload_file('/tmp/data.csv', 'data.csv', { path => '/uploads/' }); |
| 439 |
|
| 440 |
# Pattern 2: Simplified API with configured upload_directory |
| 441 |
$transport->upload_file('/tmp/data.csv', 'data.csv'); |
| 205 |
|
442 |
|
| 206 |
Method for uploading a file to the current file server |
443 |
# Pattern 3: Traditional API with explicit directory |
|
|
444 |
$transport->change_directory('/uploads/'); |
| 445 |
$transport->upload_file('/tmp/data.csv', 'data.csv'); |
| 446 |
|
| 447 |
B<Returns:> True on success, undef on failure. Check object_messages() for details. |
| 207 |
|
448 |
|
| 208 |
=cut |
449 |
=cut |
| 209 |
|
450 |
|
| 210 |
sub upload_file { |
451 |
sub upload_file { |
| 211 |
my ( $self, $local_file, $remote_file ) = @_; |
452 |
my ( $self, $local_file, $remote_file, $options ) = @_; |
| 212 |
die "Subclass must implement upload_file"; |
453 |
|
|
|
454 |
return unless $self->_ensure_connected(); |
| 455 |
|
| 456 |
# Only auto-change directory if: |
| 457 |
# 1. Options provided with custom path (simplified API), OR |
| 458 |
# 2. No explicit directory set by user AND default upload_directory exists (traditional API) |
| 459 |
if ( $options && $options->{path} ) { |
| 460 |
|
| 461 |
# Simplified API - use custom path |
| 462 |
return unless $self->_auto_change_directory( 'upload', $options->{path} ); |
| 463 |
} elsif ( !$self->{_user_set_directory} ) { |
| 464 |
|
| 465 |
# Traditional API - use default directory only if user hasn't set one |
| 466 |
return unless $self->_auto_change_directory( 'upload', undef ); |
| 467 |
} |
| 468 |
|
| 469 |
return $self->_upload_file( $local_file, $remote_file ); |
| 213 |
} |
470 |
} |
| 214 |
|
471 |
|
| 215 |
=head3 download_file |
472 |
=head3 _upload_file |
| 216 |
|
473 |
|
| 217 |
$transport->download_file($file); |
474 |
$transport->_upload_file($local_file, $remote_file); |
| 218 |
|
475 |
|
| 219 |
Method for downloading a file from the current file server |
476 |
Internal method that performs the protocol-specific upload operation. |
|
|
477 |
Must be implemented by subclasses. Called by upload_file after connection |
| 478 |
and directory management. |
| 220 |
|
479 |
|
| 221 |
=cut |
480 |
=cut |
| 222 |
|
481 |
|
| 223 |
sub download_file { |
482 |
sub _upload_file { |
| 224 |
my ( $self, $remote_file, $local_file ) = @_; |
483 |
my ($self) = @_; |
| 225 |
die "Subclass must implement download_file"; |
484 |
die "Subclass must implement _upload_file"; |
| 226 |
} |
485 |
} |
| 227 |
|
486 |
|
| 228 |
=head3 change_directory |
487 |
=head3 download_file |
|
|
488 |
|
| 489 |
# Signature: |
| 490 |
my $success = $transport->download_file($remote_file, $local_file, \%options); |
| 229 |
|
491 |
|
| 230 |
my $files = $transport->change_directory($path); |
492 |
Downloads a file from the remote server. Automatically establishes a connection if needed. |
| 231 |
|
493 |
|
| 232 |
Method for changing the current directory on the connected file server |
494 |
B<Parameters:> |
|
|
495 |
|
| 496 |
=over 4 |
| 497 |
|
| 498 |
=item * C<$remote_file> - Remote filename (not a path, just the filename) (required) |
| 499 |
|
| 500 |
=item * C<$local_file> - Path where the downloaded file should be saved (required) |
| 501 |
|
| 502 |
=item * C<\%options> - Optional hashref with keys: |
| 503 |
|
| 504 |
=over 4 |
| 505 |
|
| 506 |
=item * C<path> - Directory path to download from. If provided, uses this directory |
| 507 |
for this operation only (simplified API). If omitted, behavior depends on whether |
| 508 |
change_directory() has been called explicitly (see DESCRIPTION). |
| 509 |
|
| 510 |
=back |
| 511 |
|
| 512 |
=back |
| 513 |
|
| 514 |
B<Usage Patterns:> |
| 515 |
|
| 516 |
# Pattern 1: Simplified API with custom path |
| 517 |
$transport->download_file('data.csv', '/tmp/data.csv', { path => '/downloads/' }); |
| 518 |
|
| 519 |
# Pattern 2: Simplified API with configured download_directory |
| 520 |
$transport->download_file('data.csv', '/tmp/data.csv'); |
| 521 |
|
| 522 |
# Pattern 3: Traditional API with explicit directory |
| 523 |
$transport->change_directory('/downloads/'); |
| 524 |
$transport->download_file('data.csv', '/tmp/data.csv'); |
| 525 |
|
| 526 |
B<Returns:> True on success, undef on failure. Check object_messages() for details. |
| 233 |
|
527 |
|
| 234 |
=cut |
528 |
=cut |
| 235 |
|
529 |
|
| 236 |
sub change_directory { |
530 |
sub download_file { |
| 237 |
my ( $self, $path ) = @_; |
531 |
my ( $self, $remote_file, $local_file, $options ) = @_; |
| 238 |
die "Subclass must implement change_directory"; |
532 |
|
|
|
533 |
return unless $self->_ensure_connected(); |
| 534 |
|
| 535 |
# Only auto-change directory if: |
| 536 |
# 1. Options provided with custom path (simplified API), OR |
| 537 |
# 2. No explicit directory set by user AND default download_directory exists (traditional API) |
| 538 |
if ( $options && $options->{path} ) { |
| 539 |
|
| 540 |
# Simplified API - use custom path |
| 541 |
return unless $self->_auto_change_directory( 'download', $options->{path} ); |
| 542 |
} elsif ( !$self->{_user_set_directory} ) { |
| 543 |
|
| 544 |
# Traditional API - use default directory only if user hasn't set one |
| 545 |
return unless $self->_auto_change_directory( 'download', undef ); |
| 546 |
} |
| 547 |
|
| 548 |
return $self->_download_file( $remote_file, $local_file ); |
| 239 |
} |
549 |
} |
| 240 |
|
550 |
|
| 241 |
=head3 list_files |
551 |
=head3 _download_file |
| 242 |
|
552 |
|
| 243 |
my $files = $transport->list_files($path); |
553 |
$transport->_download_file($remote_file, $local_file); |
| 244 |
|
554 |
|
| 245 |
Method for listing files in the current directory of the connected file server |
555 |
Internal method that performs the protocol-specific download operation. |
|
|
556 |
Must be implemented by subclasses. Called by download_file after connection |
| 557 |
and directory management. |
| 246 |
|
558 |
|
| 247 |
=cut |
559 |
=cut |
| 248 |
|
560 |
|
| 249 |
sub list_files { |
561 |
sub _download_file { |
| 250 |
my ( $self, $path ) = @_; |
562 |
my ($self) = @_; |
| 251 |
die "Subclass must implement list_files"; |
563 |
die "Subclass must implement _download_file"; |
| 252 |
} |
564 |
} |
| 253 |
|
565 |
|
| 254 |
=head3 rename_file |
566 |
=head3 rename_file |
|
Lines 261-280
Method for renaming a file on the current file server
Link Here
|
| 261 |
|
573 |
|
| 262 |
sub rename_file { |
574 |
sub rename_file { |
| 263 |
my ( $self, $old_name, $new_name ) = @_; |
575 |
my ( $self, $old_name, $new_name ) = @_; |
| 264 |
die "Subclass must implement rename_file"; |
576 |
|
|
|
577 |
return unless $self->_ensure_connected(); |
| 578 |
|
| 579 |
return $self->_rename_file( $old_name, $new_name ); |
| 580 |
} |
| 581 |
|
| 582 |
=head3 _rename_file |
| 583 |
|
| 584 |
$transport->_rename_file($old_name, $new_name); |
| 585 |
|
| 586 |
Internal method that performs the protocol-specific file rename operation. |
| 587 |
Must be implemented by subclasses. Called by rename_file after connection |
| 588 |
verification. |
| 589 |
|
| 590 |
=cut |
| 591 |
|
| 592 |
sub _rename_file { |
| 593 |
my ($self) = @_; |
| 594 |
die "Subclass must implement _rename_file"; |
| 595 |
} |
| 596 |
|
| 597 |
=head3 list_files |
| 598 |
|
| 599 |
# Signature: |
| 600 |
my $files = $transport->list_files(\%options); |
| 601 |
|
| 602 |
Lists files in a directory on the remote server. Automatically establishes a connection if needed. |
| 603 |
|
| 604 |
B<Parameters:> |
| 605 |
|
| 606 |
=over 4 |
| 607 |
|
| 608 |
=item * C<\%options> - Optional hashref with keys: |
| 609 |
|
| 610 |
=over 4 |
| 611 |
|
| 612 |
=item * C<path> - Directory path to list files from. If provided, uses this directory |
| 613 |
for this operation only (simplified API). If omitted, behavior depends on whether |
| 614 |
change_directory() has been called explicitly (see DESCRIPTION). |
| 615 |
|
| 616 |
=back |
| 617 |
|
| 618 |
=back |
| 619 |
|
| 620 |
B<Usage Patterns:> |
| 621 |
|
| 622 |
# Pattern 1: Simplified API with custom path |
| 623 |
my $files = $transport->list_files({ path => '/incoming/' }); |
| 624 |
|
| 625 |
# Pattern 2: Simplified API with configured download_directory |
| 626 |
my $files = $transport->list_files(); |
| 627 |
|
| 628 |
# Pattern 3: Traditional API with explicit directory |
| 629 |
$transport->change_directory('/incoming/'); |
| 630 |
my $files = $transport->list_files(); |
| 631 |
|
| 632 |
B<Returns:> Arrayref of hashrefs on success, undef on failure. Each hashref contains |
| 633 |
file metadata (filename, size, permissions, etc.). The exact structure varies by |
| 634 |
transport type but always includes a 'filename' key. |
| 635 |
|
| 636 |
=cut |
| 637 |
|
| 638 |
sub list_files { |
| 639 |
my ( $self, $options ) = @_; |
| 640 |
|
| 641 |
return unless $self->_ensure_connected(); |
| 642 |
|
| 643 |
# Only auto-change directory if: |
| 644 |
# 1. Options provided with custom path (simplified API), OR |
| 645 |
# 2. No explicit directory set by user AND default download_directory exists (traditional API) |
| 646 |
if ( $options && $options->{path} ) { |
| 647 |
|
| 648 |
# Simplified API - use custom path |
| 649 |
return unless $self->_auto_change_directory( 'download', $options->{path} ); |
| 650 |
} elsif ( !$self->{_user_set_directory} ) { |
| 651 |
|
| 652 |
# Traditional API - use default directory only if user hasn't set one |
| 653 |
return unless $self->_auto_change_directory( 'download', undef ); |
| 654 |
} |
| 655 |
|
| 656 |
return $self->_list_files(); |
| 657 |
} |
| 658 |
|
| 659 |
=head3 _list_files |
| 660 |
|
| 661 |
my $files = $transport->_list_files(); |
| 662 |
|
| 663 |
Internal method that performs the protocol-specific file listing operation. |
| 664 |
Must be implemented by subclasses. Called by list_files after connection |
| 665 |
and directory management. |
| 666 |
|
| 667 |
=cut |
| 668 |
|
| 669 |
sub _list_files { |
| 670 |
my ($self) = @_; |
| 671 |
die "Subclass must implement _list_files"; |
| 265 |
} |
672 |
} |
| 266 |
|
673 |
|
| 267 |
=head3 disconnect |
674 |
=head3 disconnect |
| 268 |
|
675 |
|
| 269 |
$transport->disconnect(); |
676 |
$transport->disconnect(); |
| 270 |
|
677 |
|
| 271 |
Method for disconnecting from the current file server |
678 |
Closes the connection to the remote server. |
|
|
679 |
|
| 680 |
B<Note:> Calling this method is entirely optional. Connections are automatically |
| 681 |
cleaned up when the transport object is destroyed (goes out of scope). You only |
| 682 |
need to call this explicitly if you want to free resources before the object |
| 683 |
is destroyed, such as in long-running processes. |
| 684 |
|
| 685 |
B<Returns:> True on success, undef on failure. |
| 272 |
|
686 |
|
| 273 |
=cut |
687 |
=cut |
| 274 |
|
688 |
|
| 275 |
sub disconnect { |
689 |
sub disconnect { |
| 276 |
my ($self) = @_; |
690 |
my ($self) = @_; |
| 277 |
die "Subclass must implement disconnect"; |
691 |
|
|
|
692 |
# Reset directory state when disconnecting |
| 693 |
$self->{_user_set_directory} = 0; |
| 694 |
|
| 695 |
return $self->_disconnect(); |
| 696 |
} |
| 697 |
|
| 698 |
=head3 _disconnect |
| 699 |
|
| 700 |
$transport->_disconnect(); |
| 701 |
|
| 702 |
Internal method that performs the protocol-specific disconnection operation. |
| 703 |
Must be implemented by subclasses. Called by disconnect() after resetting |
| 704 |
directory state. |
| 705 |
|
| 706 |
=cut |
| 707 |
|
| 708 |
sub _disconnect { |
| 709 |
my ($self) = @_; |
| 710 |
die "Subclass must implement _disconnect"; |
| 711 |
} |
| 712 |
|
| 713 |
=head3 change_directory |
| 714 |
|
| 715 |
my $success = $transport->change_directory($path); |
| 716 |
|
| 717 |
Changes the current working directory on the remote server. |
| 718 |
|
| 719 |
B<Important:> Calling this method explicitly switches the transport to "manual mode" |
| 720 |
and disables automatic directory management. After calling this method, all subsequent |
| 721 |
file operations will use this directory (or relative paths from it) until you call |
| 722 |
change_directory() again or create a new connection. |
| 723 |
|
| 724 |
B<Parameters:> |
| 725 |
|
| 726 |
=over 4 |
| 727 |
|
| 728 |
=item * C<$path> - Directory path to change to (required) |
| 729 |
|
| 730 |
=back |
| 731 |
|
| 732 |
B<Example:> |
| 733 |
|
| 734 |
# After calling change_directory explicitly: |
| 735 |
$transport->change_directory('/work/dir/'); |
| 736 |
$transport->upload_file($local, $remote); # Uses /work/dir/, not upload_directory |
| 737 |
$transport->list_files(); # Lists /work/dir/, not download_directory |
| 738 |
|
| 739 |
B<Returns:> True on success, undef on failure. Check object_messages() for details. |
| 740 |
|
| 741 |
=cut |
| 742 |
|
| 743 |
sub change_directory { |
| 744 |
my ( $self, $path ) = @_; |
| 745 |
|
| 746 |
# Mark that user has explicitly set a directory |
| 747 |
# This prevents auto-directory management from interfering |
| 748 |
$self->{_user_set_directory} = 1; |
| 749 |
|
| 750 |
return $self->_change_directory($path); |
| 751 |
} |
| 752 |
|
| 753 |
=head3 _change_directory |
| 754 |
|
| 755 |
my $success = $transport->_change_directory($path); |
| 756 |
|
| 757 |
Internal method that performs the protocol-specific directory change operation. |
| 758 |
Must be implemented by subclasses. Called by change_directory() after setting |
| 759 |
the _user_set_directory flag. |
| 760 |
|
| 761 |
=cut |
| 762 |
|
| 763 |
sub _change_directory { |
| 764 |
my ($self) = @_; |
| 765 |
die "Subclass must implement _change_directory"; |
| 278 |
} |
766 |
} |
| 279 |
|
767 |
|
| 280 |
=head3 _post_store_trigger |
768 |
=head3 _post_store_trigger |