| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package File::CleanupTask; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 4 |  |  | 4 |  | 67293 | use strict; | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 199 |  | 
| 4 | 4 |  |  | 4 |  | 24 | use warnings; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 178 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 4 |  |  | 4 |  | 22 | use Cwd            qw/realpath getcwd chdir/; | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 478 |  | 
| 7 | 4 |  |  | 4 |  | 27 | use File::Path     qw/mkpath rmtree/; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 283 |  | 
| 8 | 4 |  |  | 4 |  | 28 | use File::Basename qw/fileparse/; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 343 |  | 
| 9 | 4 |  |  | 4 |  | 26 | use File::Spec     qw/catpath splitpath/; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 123 |  | 
| 10 | 4 |  |  | 4 |  | 2387 | use Config::Simple; | 
|  | 4 |  |  |  |  | 84741 |  | 
|  | 4 |  |  |  |  | 43 |  | 
| 11 | 4 |  |  | 4 |  | 3192 | use File::Which    qw/which/; | 
|  | 4 |  |  |  |  | 4013 |  | 
|  | 4 |  |  |  |  | 375 |  | 
| 12 | 4 |  |  | 4 |  | 3372 | use Getopt::Long; | 
|  | 4 |  |  |  |  | 47395 |  | 
|  | 4 |  |  |  |  | 28 |  | 
| 13 | 4 |  |  | 4 |  | 763 | use File::Find; | 
|  | 4 |  |  |  |  | 5 |  | 
|  | 4 |  |  |  |  | 220 |  | 
| 14 | 4 |  |  | 4 |  | 2612 | use File::Copy; | 
|  | 4 |  |  |  |  | 9430 |  | 
|  | 4 |  |  |  |  | 334 |  | 
| 15 | 4 |  |  | 4 |  | 2681 | use IPC::Run3      qw/run3/; | 
|  | 4 |  |  |  |  | 85321 |  | 
|  | 4 |  |  |  |  | 380 |  | 
| 16 | 4 |  |  | 4 |  | 2623 | use Sort::Key      qw/nkeysort/; | 
|  | 4 |  |  |  |  | 12587 |  | 
|  | 4 |  |  |  |  | 346 |  | 
| 17 | 4 |  |  | 4 |  | 53 | use Config; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 22017 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | =head1 NAME | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | File::CleanupTask - Delete or back up files using a task-based configuration | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | =head1 VERSION | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | Version 0.11 | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =cut | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | our $VERSION = '0.11'; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | use File::CleanupTask; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | my $cleanup = File::Cleanup->new({ | 
| 37 |  |  |  |  |  |  | conf => "/path/to/tasks_file.tasks", | 
| 38 |  |  |  |  |  |  | taskname => "TASK_LABEL_IN_TASKFILE", | 
| 39 |  |  |  |  |  |  | }); | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | $cleanup->run(); | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | Once run() is called, the cleanup operation 'TASK_LABEL_IN_TASKFILE' specified | 
| 44 |  |  |  |  |  |  | in tasks_file.tasks is performed. | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | =head2 CONFIGURATION FORMAT | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | A .tasks file is a text file in which one or more cleanup tasks are specified. | 
| 50 |  |  |  |  |  |  | Each task has a label and a list of options specified as shown in the following | 
| 51 |  |  |  |  |  |  | example: | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | [TASK_LABEL_IN_TASKFILE] | 
| 54 |  |  |  |  |  |  | path                = '/home/savio/results/' | 
| 55 |  |  |  |  |  |  | backup_path         = '/home/savio/old_results/' | 
| 56 |  |  |  |  |  |  | backup_gzip             = 1 | 
| 57 |  |  |  |  |  |  | max_days                = 3 | 
| 58 |  |  |  |  |  |  | recursive               = 1 | 
| 59 |  |  |  |  |  |  | prune_empty_directories = 1 | 
| 60 |  |  |  |  |  |  | keep_if_linked_in       = '/home/savio/results/' | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | [ANOTHER_LABEL] | 
| 63 |  |  |  |  |  |  | path = 'C:\\this\\is\\a\\windows\\path' | 
| 64 |  |  |  |  |  |  | ... | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | In this case, [TASK_LABEL_IN_TASKFILE] is the name of the cleanup task to be | 
| 68 |  |  |  |  |  |  | executed. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | The following options can be specified under a task label: | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | =head3 path | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | The path to the directory containing the files to be deleted or removed. | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | Note that for MS Windows the backslashes in a path should be escaped and single | 
| 77 |  |  |  |  |  |  | quotes are strictly needed when specifying a path name (see the example above). | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | =head3 backup_path | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | If specified, will cause files to be moved in the specified directory instead | 
| 82 |  |  |  |  |  |  | of being deleted. If backup_path doesn't exist, it will be created.  Symlinks | 
| 83 |  |  |  |  |  |  | are not backed up. The files are backed up at the toplevel of backup_path in a | 
| 84 |  |  |  |  |  |  | .gz (or .tgz, depending on backup_gzip) archive, which preserves pathnames of | 
| 85 |  |  |  |  |  |  | the archived files. | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | =head3 backup_gzip | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | If set to "1", will gzip the files saved in backup_path. The resulting archive | 
| 90 |  |  |  |  |  |  | will preserve the pathname of the original file, and will be relative to | 
| 91 |  |  |  |  |  |  | 'path'. | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | For example, given the following configuration: | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | [LABEL] | 
| 96 |  |  |  |  |  |  | path = /path/to/cleanup/ | 
| 97 |  |  |  |  |  |  | backup_path = /path/to/backup/ | 
| 98 |  |  |  |  |  |  | backup_gzip = 1 | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | If /path/to/cleanup/my/target/file.txt is encountered, and it's old, it will be | 
| 101 |  |  |  |  |  |  | backed up in /path/to/backup/file.txt.gz. Uncompressing file.txt.gz using | 
| 102 |  |  |  |  |  |  | /path/to/backup as current working directory will result in: | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | /path/to/backup/path/to/cleanup/my/target/file.txt | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | =head3 max_days | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | The number of maximum days within which the files in the cleanup directories | 
| 110 |  |  |  |  |  |  | are kept.  If a file is older than the specified number of days, it is queued | 
| 111 |  |  |  |  |  |  | for deletion. | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | For example, max_days = 3 will delete files older than 3 days from the cleanup | 
| 114 |  |  |  |  |  |  | directory. | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | max_days defaults to 0 if it isn't specified, meaning that all the files are to | 
| 117 |  |  |  |  |  |  | be deleted. | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | =head3 recursive | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | If set to 0, only files within "path" can be deleted/backed up. | 
| 122 |  |  |  |  |  |  | If set to 1, files located at any level within "path" can be deleted. | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | If C is enabled and C is disabled, then | 
| 125 |  |  |  |  |  |  | only empty directories that are direct children of "path" will be cleaned up. | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | By default, this takes the 0 value. | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | =head3 prune_empty_directories | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | If set to 1, empty directories will be deleted, respecting the C | 
| 132 |  |  |  |  |  |  | option. (In versions 0.09 and older, this would not respect the max_days option.) | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | By default, this takes the 0 value. | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | =head3 keep_if_linked_in | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | A pathname to a directory that may contain symlinks. If specified, it will | 
| 139 |  |  |  |  |  |  | prevent deletion of files and directories within path that are symlinked in | 
| 140 |  |  |  |  |  |  | this directory, regardless of their age. | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | This option will be ignored in MS Windows or in other operating systems that | 
| 143 |  |  |  |  |  |  | don't support symlinks. | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | =head3 do_not_delete | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | A regular expression that defines a pattern to look for. Any pathnames matching | 
| 148 |  |  |  |  |  |  | this pattern will not be erased, regardless of their age. The regular | 
| 149 |  |  |  |  |  |  | expression applies to the full pathname of the file or directory. | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | In the configuration file, it should be surrounded by forward slashes. Because | 
| 152 |  |  |  |  |  |  | the configuration file is parsed by L, you will need to escape | 
| 153 |  |  |  |  |  |  | any backslashes in the regex with a backslash. | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | =cut | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | =head3 delete_all_or_nothing_in | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | If set to 1, immediate subfolders in path will be deleted only if all the files | 
| 160 |  |  |  |  |  |  | in it are deleted. | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =head3 pattern | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | If specified, will apply any potential delete or backup action to the files | 
| 165 |  |  |  |  |  |  | that match the pattern. Any other file will be left untouched. | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | =cut | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | =head3 enable_symlinks_integrity_in_path | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | If set to 1, the symlinks inside 'path' will be deleted only if their target | 
| 172 |  |  |  |  |  |  | will be deleted. This option is disabled by default, which means that the | 
| 173 |  |  |  |  |  |  | target of symlinks within the path will not be questioned during | 
| 174 |  |  |  |  |  |  | deletion/backup, they will be just treated as regular files. | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | This option will be ignored in MS Windows or in other operating systems that | 
| 177 |  |  |  |  |  |  | don't support symlinks. | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | =cut | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | =head1 METHODS | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | =head2 new | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | Create and configure a new File::CleanupTask object. | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | The object must be initialised as follows: | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | my $cleanup = File::Cleanup->new({ | 
| 193 |  |  |  |  |  |  | conf => "/path/to/tasks_file.tasks", | 
| 194 |  |  |  |  |  |  | taskname => 'TASK_LABEL_IN_TASKFILE', | 
| 195 |  |  |  |  |  |  | }); | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =cut | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub new { | 
| 200 | 21 |  |  | 21 | 1 | 178348 | my $class  = shift; | 
| 201 | 21 |  |  |  |  | 58 | my $params = shift; | 
| 202 | 21 |  |  |  |  | 67 | my $self   = { params => $params }; | 
| 203 | 21 |  |  |  |  | 203 | $self->{config_simple} = new Config::Simple; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 21 |  |  |  |  | 889 | $self->{cmd_gzip} = File::Which::which('gzip'); | 
| 206 | 21 | 50 |  |  |  | 3768 | if (!$self->{cmd_gzip}) { | 
| 207 | 0 |  |  |  |  | 0 | $self->_warn( | 
| 208 |  |  |  |  |  |  | "No gzip executable found in your path." | 
| 209 |  |  |  |  |  |  | . " Option backup_gzip will be disabled!" | 
| 210 |  |  |  |  |  |  | ); | 
| 211 |  |  |  |  |  |  | } | 
| 212 | 21 |  |  |  |  | 117 | return bless $self, $class; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | =head2 command_line_run | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | Given the arguments specified in the command line, processes them, | 
| 218 |  |  |  |  |  |  | creates a new File::CleanupTask object, and then calls C. | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | Options include I, I, I and I. | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | =over | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =item I: just build and show the plan, nothing will be executed or deleted. | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =item I: produce more verbose output. | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | =item I: optional, will result in the execution of the specified task. | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | =item I: the path to the .tasks configuration file. | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =back | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | =cut | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | sub command_line_run { | 
| 238 | 0 |  |  | 0 | 1 | 0 | my $class     = shift; | 
| 239 | 0 |  |  |  |  | 0 | my $rh_params = {}; | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 0 | 0 |  |  |  | 0 | GetOptions( | 
| 242 |  |  |  |  |  |  | $rh_params, | 
| 243 |  |  |  |  |  |  | 'conf=s',             # The path to the task configuration file | 
| 244 |  |  |  |  |  |  | 'taskname|task=s',    # The name of the task to be executed (must be | 
| 245 |  |  |  |  |  |  | # included in the configuration) | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | 'dryrun', | 
| 248 |  |  |  |  |  |  | 'verbose', | 
| 249 |  |  |  |  |  |  | 'help', | 
| 250 |  |  |  |  |  |  | ) | 
| 251 |  |  |  |  |  |  | || $class->_usage_and_exit(); | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 0 | 0 |  |  |  | 0 | if ( $rh_params->{help} ) { | 
| 254 | 0 |  |  |  |  | 0 | $class->_usage_and_exit(); | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 0 | 0 |  |  |  | 0 | if ( !$rh_params->{conf} ) { | 
| 258 | 0 |  |  |  |  | 0 | $class->_usage_and_exit('Parameter --conf required'); | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 0 | 0 |  |  |  | 0 | if ( $rh_params->{dryrun} ) { | 
| 262 | 0 |  |  |  |  | 0 | $rh_params->{verbose} = 1; # Implicitly turn on verbose | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 0 |  |  |  |  | 0 | $class->new($rh_params)->run(); | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | =head2 run | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | Perform the cleanup | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | =cut | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | sub run { | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 20 |  |  | 20 | 1 | 1948953 | my $can_symlink = eval { symlink("",""); 1 }; | 
|  | 20 |  |  |  |  | 74 |  | 
|  | 20 |  |  |  |  | 45 |  | 
| 279 |  |  |  |  |  |  |  | 
| 280 | 20 |  |  |  |  | 46 | my $self = shift; | 
| 281 | 20 |  |  |  |  | 75 | my @compulsory_values = (qw/path max_days/); | 
| 282 | 20 |  |  |  |  | 287 | my %allowed_values = ( | 
| 283 |  |  |  |  |  |  | 'max_days'                 => '', | 
| 284 |  |  |  |  |  |  | 'recursive'                => '', | 
| 285 |  |  |  |  |  |  | 'prune_empty_directories'  => '', | 
| 286 |  |  |  |  |  |  | 'path'                     => '', | 
| 287 |  |  |  |  |  |  | 'keep_if_linked_in'        => '', | 
| 288 |  |  |  |  |  |  | 'backup_gzip'              => '', | 
| 289 |  |  |  |  |  |  | 'backup_path'              => '', | 
| 290 |  |  |  |  |  |  | 'do_not_delete'            => '', | 
| 291 |  |  |  |  |  |  | 'delete_all_or_nothing_in' => '', | 
| 292 |  |  |  |  |  |  | 'pattern'                  => '', | 
| 293 |  |  |  |  |  |  | 'enable_symlinks_integrity_in_path' => '', | 
| 294 |  |  |  |  |  |  | ); | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | ## | 
| 297 |  |  |  |  |  |  | ## Read tasks file | 
| 298 |  |  |  |  |  |  | ## | 
| 299 | 20 |  |  |  |  | 88 | my $config_file = $self->{params}{conf}; | 
| 300 | 20 | 50 |  |  |  | 444 | if ( !-e $config_file ) { | 
| 301 | 0 |  |  |  |  | 0 | $self->_usage_and_exit("Config file $config_file does not exist"); | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  |  | 
| 304 | 20 |  |  |  |  | 162 | $self->{config_simple}->read($config_file); | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 20 |  |  |  |  | 110867 | my %taskfile = $self->{config_simple}->vars(); | 
| 307 | 20 |  |  |  |  | 13319 | foreach my $line ( keys %taskfile ) { | 
| 308 | 2120 |  |  |  |  | 3712 | my ($taskname, $key) = split( /[.]/, $line ); | 
| 309 | 2120 |  |  |  |  | 2578 | my $value = $taskfile{$line}; | 
| 310 |  |  |  |  |  |  |  | 
| 311 | 2120 | 50 |  |  |  | 3571 | if (!exists($allowed_values{$key})) { | 
| 312 | 0 |  |  |  |  | 0 | $self->_usage_and_exit( | 
| 313 |  |  |  |  |  |  | "Unrecognised configuration option! '$key' was not recognised!" | 
| 314 |  |  |  |  |  |  | . " Check $self->{params}{conf} and try again.\n" | 
| 315 |  |  |  |  |  |  | ); | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  |  | 
| 318 | 2120 | 50 | 0 |  |  | 3967 | if (!$can_symlink | 
|  |  |  | 33 |  |  |  |  | 
| 319 |  |  |  |  |  |  | && ($key eq 'enable_symlinks_integrity_in_path' | 
| 320 |  |  |  |  |  |  | || $key eq 'keep_if_linked_in') ) { | 
| 321 |  |  |  |  |  |  |  | 
| 322 | 0 |  |  |  |  | 0 | $self->_warn( | 
| 323 |  |  |  |  |  |  | "The option $key specified for task $taskname will be" | 
| 324 |  |  |  |  |  |  | . " ignored, as your operating system doesn't support" | 
| 325 |  |  |  |  |  |  | . " symlinks" | 
| 326 |  |  |  |  |  |  | ); | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | } else { | 
| 329 | 2120 |  |  |  |  | 4664 | $self->{_rhh_task_configs}{$taskname}{$key} = $value; | 
| 330 |  |  |  |  |  |  | } | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | ## | 
| 335 |  |  |  |  |  |  | ## Check compulsory values are specified | 
| 336 |  |  |  |  |  |  | ## | 
| 337 | 20 |  |  |  |  | 196 | foreach my $ckey (@compulsory_values) { | 
| 338 | 40 |  |  |  |  | 59 | foreach my $taskname (keys %{$self->{_rhh_task_configs}}) { | 
|  | 40 |  |  |  |  | 203 |  | 
| 339 | 800 | 50 |  |  |  | 1603 | if (!exists $self->{_rhh_task_configs}{$taskname}{$ckey}) { | 
| 340 | 0 |  |  |  |  | 0 | $self->_usage_and_exit( | 
| 341 |  |  |  |  |  |  | "Compulsory $ckey value hasn't been specified in" | 
| 342 |  |  |  |  |  |  | . " [$taskname] task in $config_file" | 
| 343 |  |  |  |  |  |  | ); | 
| 344 |  |  |  |  |  |  | } | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | ## | 
| 349 |  |  |  |  |  |  | ## Decide which tasks to perform - run all the tasks specified | 
| 350 |  |  |  |  |  |  | ## in the configuration by default. Run a single task if it is specified in | 
| 351 |  |  |  |  |  |  | ## the --task option. | 
| 352 |  |  |  |  |  |  | ## | 
| 353 | 20 |  |  |  |  | 53 | my @a_all_tasknames = sort keys %{ $self->{_rhh_task_configs} }; | 
|  | 20 |  |  |  |  | 274 |  | 
| 354 | 20 | 50 |  |  |  | 100 | if ( $self->{params}{taskname} ) { | 
| 355 | 20 | 50 |  |  |  | 60 | if ( grep { $_ eq $self->{params}{taskname} } @a_all_tasknames ) { | 
|  | 400 |  |  |  |  | 685 |  | 
| 356 | 20 |  |  |  |  | 99 | @a_all_tasknames = ( $self->{params}{taskname} ); | 
| 357 |  |  |  |  |  |  | } | 
| 358 |  |  |  |  |  |  | else { | 
| 359 | 0 |  |  |  |  | 0 | $self->_usage_and_exit("No such task: $self->{params}{taskname}" | 
| 360 |  |  |  |  |  |  | . " in $self->{params}{conf}" | 
| 361 |  |  |  |  |  |  | ); | 
| 362 |  |  |  |  |  |  | } | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | ## | 
| 366 |  |  |  |  |  |  | ## This is set once as soonish as the cleanup starts. We want to keep files | 
| 367 |  |  |  |  |  |  | ## that are newer than max_days at script run time. If a file is deleted in | 
| 368 |  |  |  |  |  |  | ## one day, we will keep files newer than 8 days. We expect a cleanup to be | 
| 369 |  |  |  |  |  |  | ## rescheduled in case more recent files need to be deleted. | 
| 370 |  |  |  |  |  |  | ## | 
| 371 | 20 |  |  |  |  | 91 | $self->{time} = time; | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | ## | 
| 374 |  |  |  |  |  |  | ## Execute each task | 
| 375 |  |  |  |  |  |  | ## | 
| 376 | 20 |  |  |  |  | 47 | foreach my $taskname (@a_all_tasknames) { | 
| 377 | 20 |  |  |  |  | 138 | $self->run_one_task($self->{_rhh_task_configs}{$taskname}, $taskname); | 
| 378 |  |  |  |  |  |  | } | 
| 379 | 20 |  |  |  |  | 99 | $self->_info("-++ Cleanup completed ++-"); | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =head2 run_one_task | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | Run a single cleanup task given its configuration and name. The name is used as | 
| 385 |  |  |  |  |  |  | a label for possible output and is an optional parameter of this method. | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | This will scan all files and directories in path in a depth first fashion. If a | 
| 388 |  |  |  |  |  |  | file is encountered a target action is performed based on the state of that file | 
| 389 |  |  |  |  |  |  | (file or directory, symlinked, old, empty directory...). | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | =cut | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | sub run_one_task { | 
| 394 | 24 |  |  | 24 | 1 | 17293 | my $self = shift; | 
| 395 | 24 |  |  |  |  | 49 | my $rh_task_config = shift; | 
| 396 | 24 |  |  |  |  | 49 | my $taskname = shift; | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 24 | 50 |  |  |  | 88 | if ($taskname) { | 
| 399 | 24 |  |  |  |  | 172 | $self->_info( | 
| 400 |  |  |  |  |  |  | "\n" | 
| 401 |  |  |  |  |  |  | . "\n" | 
| 402 |  |  |  |  |  |  | . " ----------------------------------------------\n" | 
| 403 |  |  |  |  |  |  | . " Task -> [ $taskname ]\n" | 
| 404 |  |  |  |  |  |  | . " ----------------------------------------------\n" | 
| 405 |  |  |  |  |  |  | ); | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 24 |  |  |  |  | 53 | my $all_or_nothing_path = $rh_task_config->{delete_all_or_nothing_in}; | 
| 409 | 24 |  |  |  |  | 65 | my $path = $rh_task_config->{path}; | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | ## | 
| 412 |  |  |  |  |  |  | ## Check that path exists | 
| 413 |  |  |  |  |  |  | ## | 
| 414 | 24 | 50 |  |  |  | 576 | if (!-d $path) { | 
| 415 | 0 |  |  |  |  | 0 | $self->_info("Cannot run this task because the path '$path' doesn't"); | 
| 416 | 0 |  |  |  |  | 0 | $self->_info("exist or is not a directory. Please ignore or provide"); | 
| 417 | 0 |  |  |  |  | 0 | $self->_info("a valid 'path' in your configuration file"            ); | 
| 418 | 0 |  |  |  |  | 0 | return; | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | ## | 
| 422 |  |  |  |  |  |  | ## Check that delete_all_or_nothing_in path exists | 
| 423 |  |  |  |  |  |  | ## | 
| 424 | 24 | 50 | 66 |  |  | 100 | if ($all_or_nothing_path && !-d $all_or_nothing_path) { | 
| 425 | 0 |  |  |  |  | 0 | $self->_info("Cannot run this task because the path "); | 
| 426 | 0 |  |  |  |  | 0 | $self->_info("'$all_or_nothing_path' doesn't exist or is not a "); | 
| 427 | 0 |  |  |  |  | 0 | $self->_info("directory. Please ignore or provide a valid "); | 
| 428 | 0 |  |  |  |  | 0 | $self->_info("'delete_all_or_nothing_in' in your configuration file"); | 
| 429 | 0 |  |  |  |  | 0 | return; | 
| 430 |  |  |  |  |  |  | } | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | ## | 
| 433 |  |  |  |  |  |  | ## Check that delete_all_or_nothing is within the cleanup path | 
| 434 |  |  |  |  |  |  | ## | 
| 435 | 24 | 50 | 66 |  |  | 98 | if ($all_or_nothing_path | 
| 436 |  |  |  |  |  |  | && (index($all_or_nothing_path, $path) < 0)) { | 
| 437 |  |  |  |  |  |  |  | 
| 438 | 0 |  |  |  |  | 0 | $self->_info("Cannot run this task because the specified"); | 
| 439 | 0 |  |  |  |  | 0 | $self->_info("delete_all_or_nothing path is not a"); | 
| 440 | 0 |  |  |  |  | 0 | $self->_info("subdirectory of 'path'"); | 
| 441 | 0 |  |  |  |  | 0 | return; | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | ## | 
| 445 |  |  |  |  |  |  | ## Set the minimum time for deleting files | 
| 446 |  |  |  |  |  |  | ## | 
| 447 | 24 |  |  |  |  | 108 | my $max_days = $rh_task_config->{max_days}; | 
| 448 | 24 | 100 |  |  |  | 116 | $self->{keep_above_epoch} = $max_days | 
| 449 |  |  |  |  |  |  | ? $self->{time} - ( $max_days * 60 * 60 * 24 ) | 
| 450 |  |  |  |  |  |  | : undef; | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | ## | 
| 453 |  |  |  |  |  |  | ## Build never_delete, a list of vital files/dirs that we really don't want | 
| 454 |  |  |  |  |  |  | ## to delete. | 
| 455 |  |  |  |  |  |  | ## | 
| 456 | 24 |  |  |  |  | 46 | my $path_symlink = $rh_task_config->{keep_if_linked_in}; | 
| 457 | 24 |  |  |  |  | 73 | my $path_backup  = $rh_task_config->{backup_path}; | 
| 458 |  |  |  |  |  |  |  | 
| 459 | 24 |  |  |  |  | 46 | my @paths = (); | 
| 460 | 24 | 100 |  |  |  | 86 | push (@paths, $path_symlink) if ($path_symlink); | 
| 461 |  |  |  |  |  |  |  | 
| 462 | 24 |  |  |  |  | 130 | my $rh_never_delete = $self->_build_never_delete(\@paths); | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | ## | 
| 465 |  |  |  |  |  |  | ## Build delete_once_empty, a list of directories that should be deleted | 
| 466 |  |  |  |  |  |  | ## only if all their content is deleted | 
| 467 |  |  |  |  |  |  | ## | 
| 468 | 24 |  |  |  |  | 45 | my $rh_delete_once_empty; | 
| 469 | 24 | 100 |  |  |  | 85 | if ($all_or_nothing_path) { | 
| 470 |  |  |  |  |  |  |  | 
| 471 | 1 |  |  |  |  | 9 | $rh_delete_once_empty = | 
| 472 |  |  |  |  |  |  | $self->_build_delete_once_empty([$all_or_nothing_path]); | 
| 473 |  |  |  |  |  |  |  | 
| 474 | 1 |  |  |  |  | 8 | $self->_print_delete_once_empty($rh_delete_once_empty); | 
| 475 |  |  |  |  |  |  | } | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 24 | 100 |  |  |  | 70 | if ($path_backup) { | 
| 478 | 4 | 50 |  |  |  | 11 | if (!$self->_ensure_path($path_backup)) { | 
| 479 | 0 |  |  |  |  | 0 | $self->_info("Cannot create the backup directory!. Terminating."); | 
| 480 | 0 |  |  |  |  | 0 | return; | 
| 481 |  |  |  |  |  |  | } | 
| 482 | 4 |  |  |  |  | 22 | my $cpath_backup = $self->_path_check($path_backup); | 
| 483 | 4 |  |  |  |  | 10 | $rh_task_config->{backup_path} = $cpath_backup; | 
| 484 |  |  |  |  |  |  |  | 
| 485 | 4 |  |  |  |  | 8 | $self->_never_delete_add_path( | 
| 486 |  |  |  |  |  |  | $rh_never_delete, | 
| 487 |  |  |  |  |  |  | $self->_path_check($cpath_backup) | 
| 488 |  |  |  |  |  |  | ); | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | } | 
| 491 | 24 | 50 |  |  |  | 78 | if ($path) { | 
| 492 | 24 |  |  |  |  | 84 | my $cpath = $self->_path_check($path); | 
| 493 | 24 |  |  |  |  | 70 | $rh_task_config->{path} = $cpath; | 
| 494 | 24 |  |  |  |  | 88 | $self->_never_delete_add_path($rh_never_delete, $cpath); | 
| 495 |  |  |  |  |  |  | } | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 24 |  |  |  |  | 104 | $self->_print_never_delete($rh_never_delete); | 
| 498 |  |  |  |  |  |  |  | 
| 499 | 24 |  |  |  |  | 240 | my $ra_plan = $self->_build_plan({ | 
| 500 |  |  |  |  |  |  | never_delete => $rh_never_delete, | 
| 501 |  |  |  |  |  |  | delete_once_empty => $rh_delete_once_empty, | 
| 502 |  |  |  |  |  |  | config    => $rh_task_config, | 
| 503 |  |  |  |  |  |  | path      => $path, | 
| 504 |  |  |  |  |  |  | }); | 
| 505 |  |  |  |  |  |  |  | 
| 506 | 24 |  |  |  |  | 172 | $self->_print_plan($ra_plan); | 
| 507 |  |  |  |  |  |  |  | 
| 508 | 24 |  |  |  |  | 175 | $self->_execute_plan({ | 
| 509 |  |  |  |  |  |  | plan => $ra_plan, | 
| 510 |  |  |  |  |  |  | never_delete => $rh_never_delete, | 
| 511 |  |  |  |  |  |  | config => $rh_task_config, | 
| 512 |  |  |  |  |  |  | }); | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | } | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | =head2 verbose, dryrun | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | Accessors that will tell you if running in dryrun or verbose mode. | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | =cut | 
| 521 |  |  |  |  |  |  |  | 
| 522 | 2644 |  |  | 2644 | 1 | 8401 | sub verbose { return $_[0]->{params}{verbose}; } | 
| 523 | 1026 |  |  | 1026 | 1 | 2594 | sub dryrun  { return $_[0]->{params}{dryrun}; } | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | =for _build_delete_once_empty | 
| 526 |  |  |  |  |  |  | Builds a delete_once_empty of pathnames, each of which should be deleted only if | 
| 527 |  |  |  |  |  |  | all its files are also deleted. | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | =cut | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | sub _build_delete_once_empty { | 
| 532 | 1 |  |  | 1 |  | 3 | my $self         = shift; | 
| 533 | 1 |  |  |  |  | 2 | my $rh_paths     = shift; | 
| 534 |  |  |  |  |  |  |  | 
| 535 | 1 |  |  |  |  | 3 | my $rh_delete_once_empty = {}; | 
| 536 | 1 |  |  |  |  | 11 | my $working_directory = Cwd->getcwd(); | 
| 537 |  |  |  |  |  |  |  | 
| 538 | 1 |  |  |  |  | 3 | foreach my $p (@$rh_paths) { | 
| 539 | 1 |  |  |  |  | 5 | $p = $self->_path_check($p); | 
| 540 | 1 |  |  |  |  | 393 | foreach my $f (glob "$p/*") { | 
| 541 | 18 | 50 |  |  |  | 379 | if ( -d $f ) { | 
| 542 | 18 |  |  |  |  | 58 | $self->_delete_once_empty_add_path($rh_delete_once_empty, $f) | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  | } | 
| 545 |  |  |  |  |  |  | } | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  |  | 
| 548 | 1 |  |  |  |  | 4 | return $rh_delete_once_empty; | 
| 549 |  |  |  |  |  |  | } | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | =for _build_never_delete | 
| 552 |  |  |  |  |  |  | Builds a never_delete list of pathnames that shouldn't be deleted at any | 
| 553 |  |  |  |  |  |  | condition. | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | =cut | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | sub _build_never_delete { | 
| 558 | 24 |  |  | 24 |  | 45 | my $self         = shift; | 
| 559 | 24 |  |  |  |  | 40 | my $rh_paths     = shift; | 
| 560 |  |  |  |  |  |  |  | 
| 561 | 24 |  |  |  |  | 62 | my $rh_never_delete = {}; | 
| 562 | 24 |  |  |  |  | 257 | my $working_directory = Cwd->getcwd(); | 
| 563 |  |  |  |  |  |  |  | 
| 564 | 24 |  |  |  |  | 64 | foreach my $p (@$rh_paths) { | 
| 565 |  |  |  |  |  |  | ## | 
| 566 |  |  |  |  |  |  | ## add the directory itself | 
| 567 |  |  |  |  |  |  | ## | 
| 568 | 10 |  |  |  |  | 42 | $p = $self->_path_check($p); | 
| 569 | 10 |  |  |  |  | 46 | $self->_never_delete_add_path($rh_never_delete, $p); | 
| 570 |  |  |  |  |  |  |  | 
| 571 | 10 |  |  |  |  | 433 | Cwd::chdir($p); | 
| 572 | 10 |  |  |  |  | 3045 | foreach my $f (glob "$p/*") { | 
| 573 |  |  |  |  |  |  |  | 
| 574 | 117 | 100 |  |  |  | 1586 | if ( my $f_target = readlink($f) ) { | 
| 575 |  |  |  |  |  |  | ## | 
| 576 |  |  |  |  |  |  | ## add any symlink within the directory | 
| 577 |  |  |  |  |  |  | ## | 
| 578 | 28 |  |  |  |  | 83 | $self->_never_delete_add_path($rh_never_delete, $f); | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | ## | 
| 581 |  |  |  |  |  |  | ## add any target of the symlink shouldn't be deleted. | 
| 582 |  |  |  |  |  |  | ## | 
| 583 | 28 |  |  |  |  | 60 | $self->_never_delete_add_path($rh_never_delete, $f_target); | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | ## | 
| 586 |  |  |  |  |  |  | ## if the target is a directory, add all its children | 
| 587 |  |  |  |  |  |  | ## | 
| 588 | 28 | 100 |  |  |  | 484 | if ( -d $f_target ) { | 
| 589 | 20 | 50 |  |  |  | 59 | if ( $f_target = $self->_path_check($f_target) ) { | 
| 590 |  |  |  |  |  |  | # Any children of the target shouldn't be deleted at any | 
| 591 |  |  |  |  |  |  | # cost. | 
| 592 |  |  |  |  |  |  | find( | 
| 593 |  |  |  |  |  |  | sub { | 
| 594 | 108 |  |  | 108 |  | 318 | $self->_never_delete_add_path( | 
| 595 |  |  |  |  |  |  | $rh_never_delete, | 
| 596 |  |  |  |  |  |  | $self->_path_check($File::Find::name) | 
| 597 |  |  |  |  |  |  | ); | 
| 598 |  |  |  |  |  |  | }, | 
| 599 | 20 |  |  |  |  | 1765 | ($f_target) | 
| 600 |  |  |  |  |  |  | ); | 
| 601 |  |  |  |  |  |  | } | 
| 602 |  |  |  |  |  |  | } | 
| 603 |  |  |  |  |  |  | } | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | } | 
| 606 | 10 |  |  |  |  | 318 | Cwd::chdir($working_directory); | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  |  | 
| 610 | 24 |  |  |  |  | 69 | return $rh_never_delete; | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | =for _never_delete_add_path | 
| 614 |  |  |  |  |  |  | Adds a path to the given never_delete list. | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | =cut | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | sub _never_delete_add_path { | 
| 619 | 202 |  |  | 202 |  | 290 | my $self         = shift; | 
| 620 | 202 |  |  |  |  | 230 | my $rh_never_delete = shift; | 
| 621 | 202 |  |  |  |  | 228 | my $path         = shift; | 
| 622 |  |  |  |  |  |  |  | 
| 623 | 202 |  |  |  |  | 374 | $path = $self->_path_check($path); | 
| 624 |  |  |  |  |  |  |  | 
| 625 | 202 | 50 |  |  |  | 529 | if (!$path) { | 
| 626 | 0 |  |  |  |  | 0 | $self->_warn( | 
| 627 |  |  |  |  |  |  | "Attempt to add empty path to the never_delete list. Ignoring it." | 
| 628 |  |  |  |  |  |  | ); | 
| 629 |  |  |  |  |  |  | } | 
| 630 |  |  |  |  |  |  | else { | 
| 631 | 202 |  |  |  |  | 774 | $rh_never_delete->{paths}{$path} = 1; | 
| 632 |  |  |  |  |  |  | } | 
| 633 |  |  |  |  |  |  |  | 
| 634 | 202 |  |  |  |  | 3795 | return; | 
| 635 |  |  |  |  |  |  | } | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | =for _delete_once_empty_contains | 
| 638 |  |  |  |  |  |  | Checks if the given path is contained in the delete_once_empty | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | =cut | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | sub _delete_once_empty_contains { | 
| 643 | 35 |  |  | 35 |  | 37 | my $self         = shift; | 
| 644 | 35 |  |  |  |  | 37 | my $rh_delete_once_empty = shift; | 
| 645 | 35 |  |  |  |  | 36 | my $path         = shift; | 
| 646 |  |  |  |  |  |  |  | 
| 647 | 35 | 100 |  |  |  | 185 | return 1 if (exists $rh_delete_once_empty->{paths}{$path}); | 
| 648 |  |  |  |  |  |  |  | 
| 649 | 17 |  |  |  |  | 68 | return 0; | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | =for _delete_once_empty_add_path | 
| 653 |  |  |  |  |  |  | Adds a path to the given delete_once_empty. | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | =cut | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | sub _delete_once_empty_add_path { | 
| 658 | 18 |  |  | 18 |  | 28 | my $self = shift; | 
| 659 | 18 |  |  |  |  | 22 | my $rh_delete_once_empty = shift; | 
| 660 | 18 |  |  |  |  | 24 | my $path = shift; | 
| 661 |  |  |  |  |  |  |  | 
| 662 | 18 |  |  |  |  | 41 | $path = $self->_path_check($path); | 
| 663 | 18 | 50 |  |  |  | 69 | if (!$path) { | 
| 664 | 0 |  |  |  |  | 0 | $self->_warn( | 
| 665 |  |  |  |  |  |  | "Attempt to add empty path to the delete_once_empty. Ignoring it." | 
| 666 |  |  |  |  |  |  | ); | 
| 667 |  |  |  |  |  |  | } | 
| 668 |  |  |  |  |  |  | else { | 
| 669 |  |  |  |  |  |  | # Add the path | 
| 670 | 18 |  |  |  |  | 112 | $rh_delete_once_empty->{paths}{$path} = 1; | 
| 671 |  |  |  |  |  |  | } | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | =for _never_delete_contains | 
| 675 |  |  |  |  |  |  | Checks if the given path is contained in the never_delete. | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | =cut | 
| 678 |  |  |  |  |  |  |  | 
| 679 |  |  |  |  |  |  | sub _never_delete_contains { | 
| 680 | 1255 |  |  | 1255 |  | 1549 | my $self         = shift; | 
| 681 | 1255 |  |  |  |  | 1161 | my $rh_never_delete = shift; | 
| 682 | 1255 |  |  |  |  | 1190 | my $path         = shift; | 
| 683 |  |  |  |  |  |  |  | 
| 684 | 1255 | 100 |  |  |  | 4082 | return 1 if (exists $rh_never_delete->{paths}{$path}); | 
| 685 | 1111 |  |  |  |  | 3128 | return 0; | 
| 686 |  |  |  |  |  |  | } | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | =for _path_check | 
| 689 |  |  |  |  |  |  | Checks up the given path, and returns its absolute representation. | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | =cut | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | sub _path_check { | 
| 694 | 2473 |  |  | 2473 |  | 2726 | my $self = shift; | 
| 695 | 2473 |  |  |  |  | 2564 | my $path = shift; | 
| 696 |  |  |  |  |  |  |  | 
| 697 | 2473 | 50 |  |  |  | 4692 | if (!$path) { $self->_info("No path given to _path_check()"); return; } | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 698 |  |  |  |  |  |  |  | 
| 699 | 2473 | 100 |  |  |  | 45166 | if (-l $path) { | 
| 700 |  |  |  |  |  |  | ## | 
| 701 |  |  |  |  |  |  | ## Get the canonical path of the symlink parent and append the symlink | 
| 702 |  |  |  |  |  |  | ## filename to it. | 
| 703 |  |  |  |  |  |  | ## | 
| 704 | 29 |  |  |  |  | 646 | my ($volume,undef,$file) = File::Spec->splitpath($path); | 
| 705 | 29 |  |  |  |  | 147 | my $parent = $self->_parent_path($path); | 
| 706 | 29 |  |  |  |  | 87 | my $cparent = $self->_path_check($parent); | 
| 707 | 29 |  |  |  |  | 264 | return File::Spec->catpath($volume, $cparent, $file); | 
| 708 |  |  |  |  |  |  | } | 
| 709 |  |  |  |  |  |  |  | 
| 710 | 2444 | 100 |  |  |  | 231357 | return (-e $path) ? Cwd::realpath($path) | 
| 711 |  |  |  |  |  |  | : File::Spec->canonpath($path); | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | =begin _build_plan | 
| 715 |  |  |  |  |  |  |  | 
| 716 |  |  |  |  |  |  | Plans the actions to be executed on the files in the target path according to: | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  | - options in the configuration | 
| 719 |  |  |  |  |  |  | - the target files | 
| 720 |  |  |  |  |  |  | - the never_delete | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | All files in the never_delete list can't be deleted. | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  | =end _build_plan | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | =cut | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | sub _build_plan { | 
| 729 | 24 |  |  | 24 |  | 50 | my $self      = shift; | 
| 730 | 24 |  |  |  |  | 32 | my $rh_params = shift; | 
| 731 |  |  |  |  |  |  |  | 
| 732 | 24 |  |  |  |  | 47 | my $path         = $rh_params->{path}; | 
| 733 | 24 |  |  |  |  | 41 | my $rh_never_delete = $rh_params->{never_delete}; | 
| 734 | 24 |  |  |  |  | 46 | my $rh_delete_once_empty = $rh_params->{delete_once_empty}; | 
| 735 | 24 |  |  |  |  | 56 | my $recursive    = $rh_params->{config}{recursive}; | 
| 736 | 24 |  |  |  |  | 50 | my $prune_empty  = $rh_params->{config}{prune_empty_directories}; | 
| 737 | 24 |  |  |  |  | 62 | my $dont_del_pattern   = $rh_params->{config}{do_not_delete}; | 
| 738 |  |  |  |  |  |  |  | 
| 739 | 24 |  |  |  |  | 66 | my $symlinks_integrity = | 
| 740 |  |  |  |  |  |  | $rh_params->{config}{enable_symlinks_integrity_in_path}; | 
| 741 |  |  |  |  |  |  |  | 
| 742 | 24 |  |  |  |  | 46 | my @plan = (); # holds a list of lists: (['filename','action']). We need a | 
| 743 |  |  |  |  |  |  | # list as we need to perform these actions in order. | 
| 744 |  |  |  |  |  |  |  | 
| 745 | 24 |  |  |  |  | 44 | my %summary;   # holds the number of files to be deleted vs. the | 
| 746 |  |  |  |  |  |  | # total number of files for each directory visited. | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | my %empties;   # avoid to go into empty dirs again. | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | # If "enable_symlinks_integrity_in_path" is true, any symlink will be | 
| 751 |  |  |  |  |  |  | # postprocessed, and the plan will be built as symlinks were not existing. | 
| 752 |  |  |  |  |  |  | # | 
| 753 |  |  |  |  |  |  | # If this is the case, %sym_integrity will be an hash | 
| 754 |  |  |  |  |  |  | #    key: path to symlink target (canonical) | 
| 755 |  |  |  |  |  |  | #    value: symlink pathname  (non canonical) | 
| 756 | 0 |  |  |  |  | 0 | my %sym_integrity; | 
| 757 |  |  |  |  |  |  |  | 
| 758 | 24 | 100 |  |  |  | 60 | if ($recursive) { | 
| 759 |  |  |  |  |  |  | find( | 
| 760 |  |  |  |  |  |  | { 'bydepth' => 1, | 
| 761 |  |  |  |  |  |  |  | 
| 762 |  |  |  |  |  |  | 'preprocess' => sub { | 
| 763 | 373 |  |  | 373 |  | 1046 | my @files = @_; | 
| 764 |  |  |  |  |  |  | ## | 
| 765 |  |  |  |  |  |  | ## Prepare this directory's summary | 
| 766 |  |  |  |  |  |  | ## | 
| 767 | 373 |  |  |  |  | 926 | my $dir = $self->_path_check($File::Find::dir); | 
| 768 | 373 | 50 |  |  |  | 1321 | if (!exists $summary{$dir}) { | 
| 769 | 373 |  |  |  |  | 1631 | $summary{$dir}{'nfiles'}  = 0; | 
| 770 | 373 |  |  |  |  | 842 | $summary{$dir}{'ndelete'} = 0; | 
| 771 |  |  |  |  |  |  | } | 
| 772 | 373 |  |  |  |  | 14164 | return @files; | 
| 773 |  |  |  |  |  |  | }, | 
| 774 |  |  |  |  |  |  |  | 
| 775 |  |  |  |  |  |  | 'wanted' => sub { | 
| 776 |  |  |  |  |  |  | ## | 
| 777 |  |  |  |  |  |  | ## Update actions and collect summary | 
| 778 |  |  |  |  |  |  | ## | 
| 779 | 1259 |  |  | 1259 |  | 1584 | my $f = $File::Find::name; | 
| 780 |  |  |  |  |  |  |  | 
| 781 |  |  |  |  |  |  |  | 
| 782 | 1259 |  |  |  |  | 1127 | my $will_check_integrity; | 
| 783 | 1259 | 100 |  |  |  | 2124 | if ($symlinks_integrity) { | 
| 784 |  |  |  |  |  |  |  | 
| 785 | 87 |  |  |  |  | 218 | $will_check_integrity = | 
| 786 |  |  |  |  |  |  | $self->_postprocess_link(\%sym_integrity, $f); | 
| 787 |  |  |  |  |  |  | } | 
| 788 |  |  |  |  |  |  |  | 
| 789 | 1259 | 100 |  |  |  | 2454 | if (!$will_check_integrity) { | 
| 790 |  |  |  |  |  |  |  | 
| 791 | 1254 |  |  |  |  | 2643 | my $dir = $self->_path_check($File::Find::dir); | 
| 792 |  |  |  |  |  |  |  | 
| 793 | 1254 | 100 |  |  |  | 13579 | if (!exists $empties{$f}) { | 
| 794 |  |  |  |  |  |  |  | 
| 795 | 971 |  |  |  |  | 2637 | my @actions = | 
| 796 | 971 |  |  |  |  | 1012 | @{ $self->_plan_add_actions ( | 
| 797 |  |  |  |  |  |  | \@plan, | 
| 798 |  |  |  |  |  |  | $f, | 
| 799 |  |  |  |  |  |  | $rh_params | 
| 800 |  |  |  |  |  |  | )}; | 
| 801 |  |  |  |  |  |  |  | 
| 802 | 971 |  |  |  |  | 4807 | foreach my $action (@actions) { | 
| 803 |  |  |  |  |  |  | ## | 
| 804 |  |  |  |  |  |  | ## count deleted items | 
| 805 |  |  |  |  |  |  | ## | 
| 806 | 881 | 50 | 66 |  |  | 17063 | if ($action eq 'delete' && (-f $f || -l $f)) { | 
|  |  |  | 66 |  |  |  |  | 
| 807 | 746 |  |  |  |  | 1989 | $summary{$dir}{'ndelete'} += 1; | 
| 808 |  |  |  |  |  |  | } | 
| 809 |  |  |  |  |  |  |  | 
| 810 |  |  |  |  |  |  | ## count total items | 
| 811 | 881 |  |  |  |  | 20204 | $summary{$dir}{'nfiles'}++; | 
| 812 |  |  |  |  |  |  | } | 
| 813 |  |  |  |  |  |  | } | 
| 814 |  |  |  |  |  |  |  | 
| 815 |  |  |  |  |  |  | } | 
| 816 |  |  |  |  |  |  | }, | 
| 817 |  |  |  |  |  |  |  | 
| 818 |  |  |  |  |  |  | 'postprocess' => sub { | 
| 819 |  |  |  |  |  |  | ## | 
| 820 |  |  |  |  |  |  | ## Consider deleting a directory given the actions performed on | 
| 821 |  |  |  |  |  |  | ## the files it contains. | 
| 822 |  |  |  |  |  |  | ## | 
| 823 | 373 |  |  | 373 |  | 859 | my $dir  = $self->_path_check($File::Find::dir); | 
| 824 | 373 |  |  |  |  | 1118 | my $nf   = $summary{$dir}{'nfiles'}; | 
| 825 | 373 |  |  |  |  | 722 | my $ndel = $summary{$dir}{'ndelete'}; | 
| 826 |  |  |  |  |  |  |  | 
| 827 | 373 |  |  |  |  | 413 | my $action = 'nothing'; | 
| 828 | 373 |  |  |  |  | 403 | my $reason = 'default'; | 
| 829 |  |  |  |  |  |  |  | 
| 830 | 373 | 100 |  |  |  | 1218 | if (!$prune_empty) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 831 | 12 |  |  |  |  | 33 | ($action, $reason) = ('nothing', 'no prune empty'); | 
| 832 |  |  |  |  |  |  | } | 
| 833 |  |  |  |  |  |  | elsif ($self->_never_delete_contains($rh_never_delete, $dir)) { | 
| 834 | 43 |  |  |  |  | 102 | ($action, $reason) = ('nothing', 'never_deleted'); | 
| 835 |  |  |  |  |  |  | } | 
| 836 |  |  |  |  |  |  | elsif ($ndel < $nf) { | 
| 837 | 24 |  |  |  |  | 89 | ($action, $reason) = ( | 
| 838 |  |  |  |  |  |  | "nothing", | 
| 839 |  |  |  |  |  |  | "will contain files ($ndel/$nf deleted)" | 
| 840 |  |  |  |  |  |  | ); | 
| 841 |  |  |  |  |  |  | } | 
| 842 |  |  |  |  |  |  | else { | 
| 843 |  |  |  |  |  |  | ## | 
| 844 |  |  |  |  |  |  | ## May delete if all these conditions are met: | 
| 845 |  |  |  |  |  |  | ## - prune_empty is on | 
| 846 |  |  |  |  |  |  | ## - the directory is or will be empty (all files deleted) | 
| 847 |  |  |  |  |  |  | ## - the directory is not never_deleted | 
| 848 |  |  |  |  |  |  | ## - the directory is older than max_days old if specified | 
| 849 |  |  |  |  |  |  | ## | 
| 850 |  |  |  |  |  |  |  | 
| 851 |  |  |  |  |  |  |  | 
| 852 |  |  |  |  |  |  | # Delete only if the directory doesn't match the pattern | 
| 853 | 294 |  |  |  |  | 292 | my $matches; | 
| 854 | 294 | 100 |  |  |  | 616 | if ($dont_del_pattern) { | 
| 855 |  |  |  |  |  |  |  | 
| 856 | 84 |  |  |  |  | 235 | $dont_del_pattern = | 
| 857 |  |  |  |  |  |  | $self->_fix_pattern($dont_del_pattern); | 
| 858 |  |  |  |  |  |  |  | 
| 859 | 84 |  |  |  |  | 295 | $matches = ($dir =~ m@$dont_del_pattern@gsx) | 
| 860 |  |  |  |  |  |  | } | 
| 861 | 294 | 50 |  |  |  | 412 | if ($matches) { | 
| 862 | 0 |  |  |  |  | 0 | ($action, $reason) | 
| 863 |  |  |  |  |  |  | = ("nothing", "'do_not_delete' matched"); | 
| 864 |  |  |  |  |  |  | } | 
| 865 |  |  |  |  |  |  | else { | 
| 866 | 294 |  |  |  |  | 5222 | my $d_time = (stat($dir))[9]; # mtime | 
| 867 | 294 | 50 | 100 |  |  | 1798 | if (! defined($d_time)) { | 
|  |  | 100 |  |  |  |  |  | 
| 868 | 0 |  |  |  |  | 0 | ($action, $reason) = ('nothing', "unable to stat"); | 
| 869 |  |  |  |  |  |  | } | 
| 870 |  |  |  |  |  |  | elsif ($self->{keep_above_epoch} && | 
| 871 |  |  |  |  |  |  | $d_time >= $self->{keep_above_epoch}) { | 
| 872 |  |  |  |  |  |  |  | 
| 873 | 11 |  |  |  |  | 26 | ($action, $reason) = ('nothing', "new directory"); | 
| 874 |  |  |  |  |  |  |  | 
| 875 |  |  |  |  |  |  | } | 
| 876 |  |  |  |  |  |  | else { | 
| 877 |  |  |  |  |  |  | ## | 
| 878 |  |  |  |  |  |  | ## Delete the directory | 
| 879 |  |  |  |  |  |  | ## | 
| 880 | 283 | 100 |  |  |  | 750 | my $verb = $self->_is_folder_empty($dir) ? 'is' | 
| 881 |  |  |  |  |  |  | : 'will be'; | 
| 882 |  |  |  |  |  |  |  | 
| 883 | 283 |  |  |  |  | 1403 | ($action, $reason) | 
| 884 |  |  |  |  |  |  | = ('delete', sprintf('%s empty', $verb)); | 
| 885 |  |  |  |  |  |  |  | 
| 886 | 283 |  |  |  |  | 982 | $empties{$dir} = 1; | 
| 887 |  |  |  |  |  |  | } | 
| 888 |  |  |  |  |  |  | } | 
| 889 |  |  |  |  |  |  | } | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | ## | 
| 892 |  |  |  |  |  |  | ## Add the action to the plan | 
| 893 |  |  |  |  |  |  | ## | 
| 894 | 373 |  |  |  |  | 1978 | $self->_plan_add_action( \@plan, | 
| 895 |  |  |  |  |  |  | { action => $action, | 
| 896 |  |  |  |  |  |  | reason => $reason, | 
| 897 |  |  |  |  |  |  | f_path => $dir, | 
| 898 |  |  |  |  |  |  | } | 
| 899 |  |  |  |  |  |  | ); | 
| 900 |  |  |  |  |  |  |  | 
| 901 |  |  |  |  |  |  | ## | 
| 902 |  |  |  |  |  |  | ## Sum up what we found to the parent directory | 
| 903 |  |  |  |  |  |  | ## | 
| 904 | 373 | 50 |  |  |  | 1271 | if ( my $f_parent = $self->_parent_path($dir)) { | 
| 905 | 373 |  |  |  |  | 947 | $summary{$f_parent}{'nfiles'}  += $nf; | 
| 906 | 373 |  |  |  |  | 8699 | $summary{$f_parent}{'ndelete'} += $ndel; | 
| 907 |  |  |  |  |  |  | } | 
| 908 |  |  |  |  |  |  | } | 
| 909 |  |  |  |  |  |  | }, | 
| 910 |  |  |  |  |  |  |  | 
| 911 | 18 |  |  |  |  | 438 | ($self->_path_check($path))  # The path to visit | 
| 912 |  |  |  |  |  |  |  | 
| 913 |  |  |  |  |  |  | ); | 
| 914 |  |  |  |  |  |  | } | 
| 915 |  |  |  |  |  |  | else { | 
| 916 |  |  |  |  |  |  | ## | 
| 917 |  |  |  |  |  |  | ## Non recursive | 
| 918 |  |  |  |  |  |  | ## | 
| 919 | 6 |  |  |  |  | 15 | my $cpath = $self->_path_check($path); | 
| 920 | 6 |  |  |  |  | 573 | foreach my $f (glob "$path/*") { | 
| 921 |  |  |  |  |  |  |  | 
| 922 | 21 |  |  |  |  | 29 | my $will_check_integrity; | 
| 923 | 21 | 50 |  |  |  | 41 | if ($symlinks_integrity) { | 
| 924 | 0 |  |  |  |  | 0 | $will_check_integrity = | 
| 925 |  |  |  |  |  |  | $self->_postprocess_link(\%sym_integrity, $f); | 
| 926 |  |  |  |  |  |  | } | 
| 927 |  |  |  |  |  |  |  | 
| 928 | 21 | 50 |  |  |  | 44 | if (!$will_check_integrity) { | 
| 929 |  |  |  |  |  |  |  | 
| 930 | 21 |  |  |  |  | 48 | $f = $self->_path_check($f); | 
| 931 |  |  |  |  |  |  |  | 
| 932 |  |  |  |  |  |  | ## | 
| 933 |  |  |  |  |  |  | ## Update actions | 
| 934 |  |  |  |  |  |  | ## | 
| 935 | 21 |  |  |  |  | 70 | $self->_plan_add_actions(\@plan, $f, $rh_params); | 
| 936 |  |  |  |  |  |  |  | 
| 937 |  |  |  |  |  |  | ## | 
| 938 |  |  |  |  |  |  | ## Now check if the directory is empty | 
| 939 |  |  |  |  |  |  | ## | 
| 940 | 21 | 50 | 100 |  |  | 336 | if ( -d $f && | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 941 |  |  |  |  |  |  | $prune_empty && | 
| 942 |  |  |  |  |  |  | $self->_is_folder_empty($f) && | 
| 943 |  |  |  |  |  |  | (!$self->_never_delete_contains($rh_never_delete, $f)) && | 
| 944 |  |  |  |  |  |  | (! $self->{keep_above_epoch} || (stat($f))[9] <= $self->{keep_above_epoch})) { | 
| 945 |  |  |  |  |  |  |  | 
| 946 |  |  |  |  |  |  |  | 
| 947 | 1 |  |  |  |  | 8 | $self->_plan_add_action( \@plan, | 
| 948 |  |  |  |  |  |  | { action => 'delete', | 
| 949 |  |  |  |  |  |  | reason => 'is_empty', | 
| 950 |  |  |  |  |  |  | f_path => $f, | 
| 951 |  |  |  |  |  |  | } | 
| 952 |  |  |  |  |  |  | ); | 
| 953 |  |  |  |  |  |  | } | 
| 954 |  |  |  |  |  |  | } | 
| 955 |  |  |  |  |  |  | } | 
| 956 |  |  |  |  |  |  | } | 
| 957 |  |  |  |  |  |  |  | 
| 958 |  |  |  |  |  |  | ## | 
| 959 |  |  |  |  |  |  | ## Now should fix the plan taking internal symlinks into account | 
| 960 |  |  |  |  |  |  | ## | 
| 961 | 24 |  |  |  |  | 674 | return $self->_refine_plan( | 
| 962 |  |  |  |  |  |  | \@plan, | 
| 963 |  |  |  |  |  |  | { never_delete => $rh_never_delete, | 
| 964 |  |  |  |  |  |  | delete_once_empty => $rh_delete_once_empty, | 
| 965 |  |  |  |  |  |  | symlinks  => \%sym_integrity | 
| 966 |  |  |  |  |  |  | } | 
| 967 |  |  |  |  |  |  | ); | 
| 968 |  |  |  |  |  |  | } | 
| 969 |  |  |  |  |  |  |  | 
| 970 |  |  |  |  |  |  | =begin _plan_add_actions | 
| 971 |  |  |  |  |  |  |  | 
| 972 |  |  |  |  |  |  | Given a path to a file and the task configuration options, augment the plan | 
| 973 |  |  |  |  |  |  | with actions to take on that file. | 
| 974 |  |  |  |  |  |  |  | 
| 975 |  |  |  |  |  |  | Returns the array containing one or more actions performed. | 
| 976 |  |  |  |  |  |  |  | 
| 977 |  |  |  |  |  |  | These actions are meant to be performed in reverse sequence on the given file. | 
| 978 |  |  |  |  |  |  | An empty array_ref is returned if no action is to be performed on the given | 
| 979 |  |  |  |  |  |  | file. | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | A returned action can be one of: delete, backup. | 
| 982 |  |  |  |  |  |  |  | 
| 983 |  |  |  |  |  |  | Resulting actions are decided according to one or more of the followings: | 
| 984 |  |  |  |  |  |  |  | 
| 985 |  |  |  |  |  |  | - options in the configuration | 
| 986 |  |  |  |  |  |  | - the target files | 
| 987 |  |  |  |  |  |  | - the never_delete | 
| 988 |  |  |  |  |  |  |  | 
| 989 |  |  |  |  |  |  | This method works under the assumption that the specified file or directory | 
| 990 |  |  |  |  |  |  | exists and the user has full permissions on it. | 
| 991 |  |  |  |  |  |  |  | 
| 992 |  |  |  |  |  |  | =end _plan_add_actions | 
| 993 |  |  |  |  |  |  |  | 
| 994 |  |  |  |  |  |  | =cut | 
| 995 |  |  |  |  |  |  |  | 
| 996 |  |  |  |  |  |  | sub _plan_add_actions { | 
| 997 | 992 |  |  | 992 |  | 1076 | my $self      = shift; | 
| 998 | 992 |  |  |  |  | 967 | my $ra_plan   = shift; | 
| 999 | 992 |  |  |  |  | 1028 | my $f         = shift; | 
| 1000 | 992 |  |  |  |  | 948 | my $rh_params = shift; | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 | 992 |  |  |  |  | 1853 | my $backup_path      = $rh_params->{config}{backup_path}; | 
| 1003 | 992 |  |  |  |  | 1184 | my $dont_del_pattern = $rh_params->{config}{do_not_delete}; | 
| 1004 | 992 |  |  |  |  | 1245 | my $pattern          = $rh_params->{config}{pattern}; | 
| 1005 |  |  |  |  |  |  |  | 
| 1006 | 992 |  |  |  |  | 1499 | my @actions = (); | 
| 1007 |  |  |  |  |  |  |  | 
| 1008 | 992 |  |  |  |  | 826 | my $action; # undef = ignore (note, this is different from "nothing") | 
| 1009 |  |  |  |  |  |  | my $reason; | 
| 1010 |  |  |  |  |  |  |  | 
| 1011 |  |  |  |  |  |  |  | 
| 1012 |  |  |  |  |  |  | # deal with directories in the caller | 
| 1013 | 992 | 100 | 100 |  |  | 21091 | if (-d $f && !-l $f) { | 
| 1014 |  |  |  |  |  |  | return \@actions | 
| 1015 | 99 |  |  |  |  | 407 | } | 
| 1016 |  |  |  |  |  |  |  | 
| 1017 |  |  |  |  |  |  | ## Only deal with files/symlinks from now on | 
| 1018 |  |  |  |  |  |  | ## | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 | 893 | 100 |  |  |  | 2505 | if ($self->_never_delete_contains($rh_params->{never_delete}, $f)) { | 
| 1021 |  |  |  |  |  |  | ## | 
| 1022 |  |  |  |  |  |  | ## In never_delete | 
| 1023 |  |  |  |  |  |  | ## | 
| 1024 | 101 |  |  |  |  | 181 | ($action, $reason) = ('nothing', 'in never_delete'); | 
| 1025 |  |  |  |  |  |  | } | 
| 1026 |  |  |  |  |  |  | else { | 
| 1027 |  |  |  |  |  |  | ## | 
| 1028 |  |  |  |  |  |  | ## Decide if the file must be considered | 
| 1029 |  |  |  |  |  |  | ## | 
| 1030 | 792 |  |  |  |  | 838 | my $file_must_be_considered = 1; # default: yes (i.e., may delete it) | 
| 1031 | 792 | 100 |  |  |  | 1252 | if ($pattern) { | 
| 1032 | 50 |  |  |  |  | 121 | $pattern = $self->_fix_pattern($pattern); | 
| 1033 | 50 |  |  |  |  | 309 | $file_must_be_considered = ($f =~ m@$pattern@gsx); | 
| 1034 |  |  |  |  |  |  | } | 
| 1035 |  |  |  |  |  |  |  | 
| 1036 |  |  |  |  |  |  | ## | 
| 1037 |  |  |  |  |  |  | ## Decide if the file must be kept | 
| 1038 |  |  |  |  |  |  | ## | 
| 1039 | 792 |  |  |  |  | 757 | my $file_must_be_kept;           # default: no (i.e., may delete it) | 
| 1040 | 792 | 100 |  |  |  | 1237 | if ($dont_del_pattern) { | 
| 1041 | 43 |  |  |  |  | 91 | $dont_del_pattern = $self->_fix_pattern($dont_del_pattern); | 
| 1042 | 43 |  |  |  |  | 324 | $file_must_be_kept = ($f =~ m@$dont_del_pattern@gsx); | 
| 1043 |  |  |  |  |  |  | } | 
| 1044 |  |  |  |  |  |  |  | 
| 1045 |  |  |  |  |  |  | ## | 
| 1046 |  |  |  |  |  |  | ## Take decisions | 
| 1047 |  |  |  |  |  |  | ## | 
| 1048 | 792 | 100 |  |  |  | 1228 | if (!$file_must_be_considered) { | 
| 1049 | 11 |  |  |  |  | 25 | ($action, $reason) = ('nothing', "'pattern' did not match"); | 
| 1050 |  |  |  |  |  |  | } | 
| 1051 |  |  |  |  |  |  | else { | 
| 1052 | 781 | 100 |  |  |  | 1023 | if ($file_must_be_kept) { | 
| 1053 | 20 |  |  |  |  | 56 | ($action, $reason) = ('nothing', "'do_not_delete' matched"); | 
| 1054 |  |  |  |  |  |  | } | 
| 1055 |  |  |  |  |  |  | else { | 
| 1056 |  |  |  |  |  |  | ## | 
| 1057 |  |  |  |  |  |  | ## Perform an action on the file (delete/backup) according to | 
| 1058 |  |  |  |  |  |  | ## the given criteria (max_days for now) | 
| 1059 |  |  |  |  |  |  | ## | 
| 1060 |  |  |  |  |  |  |  | 
| 1061 |  |  |  |  |  |  | ## Make sure we get the time from the symlink rather than the | 
| 1062 |  |  |  |  |  |  | ## linked file (if $f is a symlink) | 
| 1063 | 761 |  |  |  |  | 633 | my $f_time; | 
| 1064 | 761 | 100 | 66 |  |  | 23718 | if ($Config{d_lstat} && -l $f) { | 
| 1065 | 4 |  |  |  |  | 62 | $f_time = (lstat($f))[9]; | 
| 1066 |  |  |  |  |  |  | } else { | 
| 1067 | 757 |  |  |  |  | 28156 | $f_time = (stat($f))[9]; | 
| 1068 |  |  |  |  |  |  | } | 
| 1069 | 761 | 50 | 100 |  |  | 4311 | if ( !defined($f_time) ) { | 
|  |  | 100 |  |  |  |  |  | 
| 1070 | 0 |  |  |  |  | 0 | ($action, $reason) = ('nothing', "unable to stat"); | 
| 1071 |  |  |  |  |  |  | } | 
| 1072 |  |  |  |  |  |  | elsif ( $self->{keep_above_epoch} | 
| 1073 |  |  |  |  |  |  | && $f_time >= $self->{keep_above_epoch} ) { | 
| 1074 |  |  |  |  |  |  |  | 
| 1075 | 4 |  |  |  |  | 12 | ($action, $reason) = ('nothing', "new file"); | 
| 1076 |  |  |  |  |  |  |  | 
| 1077 |  |  |  |  |  |  | } | 
| 1078 |  |  |  |  |  |  | else { | 
| 1079 |  |  |  |  |  |  | ## | 
| 1080 |  |  |  |  |  |  | ## This is an old file | 
| 1081 |  |  |  |  |  |  | ## | 
| 1082 | 757 | 100 |  |  |  | 1182 | if ($backup_path) { | 
| 1083 | 9 |  |  |  |  | 16 | ($action, $reason) = ('backup', 'old file'); | 
| 1084 |  |  |  |  |  |  | } | 
| 1085 |  |  |  |  |  |  | else { | 
| 1086 | 748 |  |  |  |  | 1589 | ($action, $reason) = ('delete', 'old file'); | 
| 1087 |  |  |  |  |  |  | } | 
| 1088 |  |  |  |  |  |  | } | 
| 1089 |  |  |  |  |  |  | } | 
| 1090 |  |  |  |  |  |  | } | 
| 1091 |  |  |  |  |  |  | } | 
| 1092 |  |  |  |  |  |  |  | 
| 1093 | 893 | 50 |  |  |  | 1621 | if ($action) { | 
| 1094 | 893 |  |  |  |  | 1700 | push (@actions, $action); | 
| 1095 | 893 |  |  |  |  | 3829 | $self->_plan_add_action( $ra_plan , | 
| 1096 |  |  |  |  |  |  | { action => $action, | 
| 1097 |  |  |  |  |  |  | reason => $reason, | 
| 1098 |  |  |  |  |  |  | f_path => $f | 
| 1099 |  |  |  |  |  |  | } | 
| 1100 |  |  |  |  |  |  | ); | 
| 1101 |  |  |  |  |  |  | } | 
| 1102 |  |  |  |  |  |  |  | 
| 1103 | 893 |  |  |  |  | 3850 | return \@actions; | 
| 1104 |  |  |  |  |  |  | } | 
| 1105 |  |  |  |  |  |  |  | 
| 1106 |  |  |  |  |  |  | =for _plan_add_action | 
| 1107 |  |  |  |  |  |  | Adds the given action to the plan. | 
| 1108 |  |  |  |  |  |  |  | 
| 1109 |  |  |  |  |  |  | =cut | 
| 1110 |  |  |  |  |  |  |  | 
| 1111 |  |  |  |  |  |  | sub _plan_add_action { | 
| 1112 | 3847 |  |  | 3847 |  | 3924 | my $self      = shift; | 
| 1113 | 3847 |  |  |  |  | 3450 | my $ra_plan   = shift; | 
| 1114 | 3847 |  |  |  |  | 3086 | my $rh_action = shift; | 
| 1115 | 3847 |  |  |  |  | 3112 | my $add_to_top= shift; | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 |  |  |  |  |  |  | # perl 5.8.9 compatibility | 
| 1118 | 3847 | 100 |  |  |  | 5622 | $add_to_top = defined $add_to_top ? $add_to_top | 
| 1119 |  |  |  |  |  |  | : 0; | 
| 1120 |  |  |  |  |  |  |  | 
| 1121 | 3847 | 100 |  |  |  | 5147 | if ($add_to_top) { | 
| 1122 | 1230 |  |  |  |  | 6676 | unshift (@$ra_plan, | 
| 1123 |  |  |  |  |  |  | [ $rh_action->{reason}, | 
| 1124 |  |  |  |  |  |  | $rh_action->{f_path}, | 
| 1125 |  |  |  |  |  |  | $rh_action->{action} | 
| 1126 |  |  |  |  |  |  | ] | 
| 1127 |  |  |  |  |  |  | ); | 
| 1128 |  |  |  |  |  |  | } | 
| 1129 |  |  |  |  |  |  | else { | 
| 1130 | 2617 |  |  |  |  | 9534 | push (@$ra_plan, | 
| 1131 |  |  |  |  |  |  | [ $rh_action->{reason}, | 
| 1132 |  |  |  |  |  |  | $rh_action->{f_path}, | 
| 1133 |  |  |  |  |  |  | $rh_action->{action} | 
| 1134 |  |  |  |  |  |  | ] | 
| 1135 |  |  |  |  |  |  | ); | 
| 1136 |  |  |  |  |  |  | } | 
| 1137 |  |  |  |  |  |  | } | 
| 1138 |  |  |  |  |  |  |  | 
| 1139 |  |  |  |  |  |  | =for _is_folder_empty | 
| 1140 |  |  |  |  |  |  | Returns 1 if the given folder is empty. | 
| 1141 |  |  |  |  |  |  |  | 
| 1142 |  |  |  |  |  |  | =cut | 
| 1143 |  |  |  |  |  |  |  | 
| 1144 |  |  |  |  |  |  | sub _is_folder_empty { | 
| 1145 | 288 |  |  | 288 |  | 347 | my $self    = shift; | 
| 1146 | 288 |  |  |  |  | 347 | my $dirname = shift; | 
| 1147 | 288 | 50 |  |  |  | 18778 | opendir(my $dh, $dirname) or die "Not a directory"; | 
| 1148 | 288 | 100 |  |  |  | 2500 | return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) == 0; | 
|  | 1423 |  |  |  |  | 7641 |  | 
| 1149 |  |  |  |  |  |  | } | 
| 1150 |  |  |  |  |  |  |  | 
| 1151 |  |  |  |  |  |  | =for _execute_plan | 
| 1152 |  |  |  |  |  |  | Execute a plan based on the given task options. Blacklist is passed to make | 
| 1153 |  |  |  |  |  |  | sure once again that no unwanted files or directories are deleted. | 
| 1154 |  |  |  |  |  |  |  | 
| 1155 |  |  |  |  |  |  | =cut | 
| 1156 |  |  |  |  |  |  |  | 
| 1157 |  |  |  |  |  |  | sub _execute_plan { | 
| 1158 | 24 |  |  | 24 |  | 41 | my $self      = shift; | 
| 1159 | 24 |  |  |  |  | 32 | my $rh_params = shift; | 
| 1160 |  |  |  |  |  |  |  | 
| 1161 | 24 |  |  |  |  | 49 | my $rh_never_delete = $rh_params->{never_delete}; | 
| 1162 | 24 |  |  |  |  | 58 | my $rh_config   = $rh_params->{config}; | 
| 1163 | 24 |  |  |  |  | 53 | my $ra_plan     = $rh_params->{plan}; | 
| 1164 |  |  |  |  |  |  |  | 
| 1165 | 24 |  |  |  |  | 44 | my $backup_path = $rh_config->{backup_path}; | 
| 1166 | 24 |  |  |  |  | 51 | my $backup_gzip = $rh_config->{backup_gzip}; | 
| 1167 | 24 |  |  |  |  | 41 | my $path        = $rh_config->{path}; | 
| 1168 |  |  |  |  |  |  |  | 
| 1169 | 24 |  |  |  |  | 234 | my $working_directory = Cwd->getcwd(); | 
| 1170 |  |  |  |  |  |  |  | 
| 1171 | 24 |  |  |  |  | 941 | Cwd::chdir($path);                     # Needed for backup | 
| 1172 |  |  |  |  |  |  |  | 
| 1173 | 24 |  |  |  |  | 117 | while ( my $ra_plan_item = pop @$ra_plan ) { | 
| 1174 | 1270 |  |  |  |  | 3192 | my ($desc, $f, $action) = @$ra_plan_item; | 
| 1175 |  |  |  |  |  |  |  | 
| 1176 | 1270 | 100 |  |  |  | 2814 | if ($action eq 'delete') { | 
|  |  | 100 |  |  |  |  |  | 
| 1177 |  |  |  |  |  |  | ## | 
| 1178 |  |  |  |  |  |  | ## Delete here | 
| 1179 |  |  |  |  |  |  | ## | 
| 1180 | 1014 | 50 |  |  |  | 2562 | if ($self->dryrun) { | 
| 1181 | 0 |  |  |  |  | 0 | $self->_info("-- dryrun [rmtree] --> $f"); | 
| 1182 |  |  |  |  |  |  | } | 
| 1183 |  |  |  |  |  |  | else { | 
| 1184 | 1014 |  |  |  |  | 3012 | $self->_info("Deleting $f"); | 
| 1185 | 1014 |  |  |  |  | 209876 | File::Path::rmtree($f); | 
| 1186 |  |  |  |  |  |  | } | 
| 1187 |  |  |  |  |  |  | } | 
| 1188 |  |  |  |  |  |  | elsif ($action eq 'backup') { | 
| 1189 |  |  |  |  |  |  | ## | 
| 1190 |  |  |  |  |  |  | ## Do backup as requested. Ensure: | 
| 1191 |  |  |  |  |  |  | ## | 
| 1192 |  |  |  |  |  |  | ## - from is the path to a file | 
| 1193 |  |  |  |  |  |  | ## -  to is the path to a directory of the form | 
| 1194 |  |  |  |  |  |  | ##    "//" | 
| 1195 |  |  |  |  |  |  | ## | 
| 1196 | 9 |  |  |  |  | 1156 | my $from = File::Spec->abs2rel( $f, $path ); | 
| 1197 | 9 |  |  |  |  | 328 | my $from_filename = File::Basename::fileparse($f); | 
| 1198 | 9 |  |  |  |  | 39 | my $to   = sprintf("%s/%s", $backup_path, $from); | 
| 1199 |  |  |  |  |  |  |  | 
| 1200 | 9 |  |  |  |  | 334 | $to =~ s/$from_filename//; | 
| 1201 |  |  |  |  |  |  |  | 
| 1202 | 9 |  |  |  |  | 26 | $from =~ s#/+#/#g;         # clean multi-slashes | 
| 1203 | 9 |  |  |  |  | 192 | $to   =~ s#/+#/#g;         # | 
| 1204 |  |  |  |  |  |  |  | 
| 1205 | 9 | 50 |  |  |  | 46 | if ( $self->_ensure_path($to) ) { | 
| 1206 |  |  |  |  |  |  | ## | 
| 1207 |  |  |  |  |  |  | ## Target path now exists - now the target is expected to be a | 
| 1208 |  |  |  |  |  |  | ## filename with .gz extension. | 
| 1209 |  |  |  |  |  |  | ## | 
| 1210 | 9 | 100 | 66 |  |  | 56 | if ( $backup_gzip && $self->{cmd_gzip} ) { | 
| 1211 |  |  |  |  |  |  | ## | 
| 1212 |  |  |  |  |  |  | ## Gzip in case | 
| 1213 |  |  |  |  |  |  | ## | 
| 1214 | 7 | 100 | 66 |  |  | 120 | if ( $from | 
|  |  |  | 100 |  |  |  |  | 
| 1215 |  |  |  |  |  |  | && ($from !~ /[.](gz|tgz)$/i) # do not re-gzip | 
| 1216 |  |  |  |  |  |  | && (!readlink($from))         # do not gzip symlinks | 
| 1217 |  |  |  |  |  |  | ){ | 
| 1218 | 3 |  |  |  |  | 13 | $self->_info("Gzipping $from"); | 
| 1219 | 3 |  |  |  |  | 10 | my $ra_cmd = [$self->{cmd_gzip}, '--force', $from ]; | 
| 1220 |  |  |  |  |  |  |  | 
| 1221 | 3 |  |  |  |  | 9 | my $cmd_txt = join(" ", @$ra_cmd); | 
| 1222 | 3 | 50 |  |  |  | 8 | if ($self->dryrun) { | 
| 1223 | 0 |  |  |  |  | 0 | $self->_info("-- dryrun [gzip cmd] --> $cmd_txt"); | 
| 1224 |  |  |  |  |  |  | } | 
| 1225 |  |  |  |  |  |  | else { | 
| 1226 | 3 |  |  |  |  | 10 | $self->_info("Running $cmd_txt"); | 
| 1227 | 3 |  |  |  |  | 16 | run3($ra_cmd); | 
| 1228 |  |  |  |  |  |  | } | 
| 1229 | 3 |  |  |  |  | 13616 | $from .= '.gz'; | 
| 1230 |  |  |  |  |  |  | } | 
| 1231 |  |  |  |  |  |  | else { | 
| 1232 | 4 |  |  |  |  | 29 | $self->_info("$from appears to be already gzipped"); | 
| 1233 |  |  |  |  |  |  | } | 
| 1234 |  |  |  |  |  |  | } | 
| 1235 |  |  |  |  |  |  |  | 
| 1236 |  |  |  |  |  |  | # | 
| 1237 |  |  |  |  |  |  | # Move from -> to | 
| 1238 |  |  |  |  |  |  | # | 
| 1239 | 9 |  |  |  |  | 47 | my $to_file = sprintf("%s/%s", $backup_path, $from); | 
| 1240 | 9 | 50 |  |  |  | 42 | if ($self->dryrun) { | 
| 1241 | 0 |  |  |  |  | 0 | $self->_info("-- dryrun [mv] $from --> $to_file"); | 
| 1242 |  |  |  |  |  |  | } | 
| 1243 |  |  |  |  |  |  | else { | 
| 1244 | 9 |  |  |  |  | 50 | $self->_info("mv $from to $to_file"); | 
| 1245 | 9 | 50 |  |  |  | 78 | if (!move( $from, $to_file ) ){ | 
| 1246 | 0 |  |  |  |  | 0 | $self->_warn("Unable to move. Dying..."); | 
| 1247 | 0 |  |  |  |  | 0 | die sprintf("Unable to move $from to $to_file: %s", $!); | 
| 1248 |  |  |  |  |  |  | } | 
| 1249 |  |  |  |  |  |  | } | 
| 1250 |  |  |  |  |  |  | } | 
| 1251 |  |  |  |  |  |  | } | 
| 1252 |  |  |  |  |  |  | } | 
| 1253 |  |  |  |  |  |  |  | 
| 1254 | 24 |  |  |  |  | 1697 | Cwd::chdir($working_directory); | 
| 1255 |  |  |  |  |  |  | } | 
| 1256 |  |  |  |  |  |  |  | 
| 1257 |  |  |  |  |  |  | sub _ensure_path { | 
| 1258 | 13 |  |  | 13 |  | 18 | my $self = shift; | 
| 1259 | 13 |  |  |  |  | 21 | my $path = shift; | 
| 1260 |  |  |  |  |  |  |  | 
| 1261 | 13 | 100 | 66 |  |  | 378 | if ( !-e $path || !-d $path ) { | 
| 1262 | 3 |  |  |  |  | 10 | $self->_info("[making path] $path"); | 
| 1263 | 3 |  |  |  |  | 4 | eval { File::Path::mkpath($path) }; | 
|  | 3 |  |  |  |  | 310 |  | 
| 1264 | 3 | 50 |  |  |  | 12 | $self->_warn("Unable to create $path: $@") if ($@); | 
| 1265 |  |  |  |  |  |  | } | 
| 1266 |  |  |  |  |  |  |  | 
| 1267 | 13 | 50 | 33 |  |  | 292 | if ( !-e $path || !-d $path ) { | 
| 1268 | 0 |  |  |  |  | 0 | $self->_warn("Path wasn't found after trying to create it."); | 
| 1269 | 0 |  |  |  |  | 0 | return 0; | 
| 1270 |  |  |  |  |  |  | } | 
| 1271 | 13 |  |  |  |  | 48 | return 1; | 
| 1272 |  |  |  |  |  |  | } | 
| 1273 |  |  |  |  |  |  |  | 
| 1274 |  |  |  |  |  |  | =begin _refine_plan | 
| 1275 |  |  |  |  |  |  |  | 
| 1276 |  |  |  |  |  |  | Takes into account symlinks in the current plan. | 
| 1277 |  |  |  |  |  |  |  | 
| 1278 |  |  |  |  |  |  | The refinement is done in the following way: | 
| 1279 |  |  |  |  |  |  |  | 
| 1280 |  |  |  |  |  |  | 1) Go through the plan, and look for symlink targets. | 
| 1281 |  |  |  |  |  |  |  | 
| 1282 |  |  |  |  |  |  | 2) Mark any symlink with as the action of it's target if it's in the cleanup | 
| 1283 |  |  |  |  |  |  | directory: keep the symlink if its target is kept, delete otherwise (broken | 
| 1284 |  |  |  |  |  |  | symlinks, or pointing outside the cleanup, target is being backupped...). | 
| 1285 |  |  |  |  |  |  | While deciding this, build an hashref of | 
| 1286 |  |  |  |  |  |  | { symlink_parent (canonical) => symlink_path (non_canonical) }. | 
| 1287 |  |  |  |  |  |  |  | 
| 1288 |  |  |  |  |  |  | 3) Add the symlink to the plan in the correct position. | 
| 1289 |  |  |  |  |  |  | To do this, build another 'refined' plan. | 
| 1290 |  |  |  |  |  |  | - go hrough the pathnames (visits parents first) in the plan, pop each item. | 
| 1291 |  |  |  |  |  |  | - if the parent of a marked symlink is found, do the following: | 
| 1292 |  |  |  |  |  |  | * mark it as 'delete' if the symlink is going to be deleted. | 
| 1293 |  |  |  |  |  |  | or mark it as 'nothing' if the symlink is not going to be deleted. | 
| 1294 |  |  |  |  |  |  | * push the parent in the refined plan. | 
| 1295 |  |  |  |  |  |  | * push the symlink in the refined plan. | 
| 1296 |  |  |  |  |  |  |  | 
| 1297 |  |  |  |  |  |  | 4) Fix the plan to have consistent state (bubble up states between pairs of | 
| 1298 |  |  |  |  |  |  | directories) | 
| 1299 |  |  |  |  |  |  |  | 
| 1300 |  |  |  |  |  |  | Return the refined plan. | 
| 1301 |  |  |  |  |  |  |  | 
| 1302 |  |  |  |  |  |  | =end _refine_plan | 
| 1303 |  |  |  |  |  |  |  | 
| 1304 |  |  |  |  |  |  | =cut | 
| 1305 |  |  |  |  |  |  |  | 
| 1306 |  |  |  |  |  |  | sub _refine_plan { | 
| 1307 | 24 |  |  | 24 |  | 50 | my $self        = shift; | 
| 1308 | 24 |  |  |  |  | 38 | my $ra_plan     = shift; | 
| 1309 | 24 |  |  |  |  | 28 | my $rh_params   = shift; | 
| 1310 |  |  |  |  |  |  |  | 
| 1311 | 24 |  |  |  |  | 65 | my $rh_never_delete = $rh_params->{never_delete}; | 
| 1312 | 24 |  |  |  |  | 41 | my $rh_delete_once_empty = $rh_params->{delete_once_empty}; | 
| 1313 |  |  |  |  |  |  |  | 
| 1314 |  |  |  |  |  |  | # this is: | 
| 1315 |  |  |  |  |  |  | #  { symlink_target   (canonical) => | 
| 1316 |  |  |  |  |  |  | #    [ symlink_path (non canonical) ] | 
| 1317 |  |  |  |  |  |  | #  } | 
| 1318 | 24 |  |  |  |  | 63 | my $rh_symlinks  = $rh_params->{symlinks}; | 
| 1319 |  |  |  |  |  |  |  | 
| 1320 |  |  |  |  |  |  | ## | 
| 1321 |  |  |  |  |  |  | ## Symlinks to delete and keep | 
| 1322 |  |  |  |  |  |  | ## | 
| 1323 | 24 |  |  |  |  | 35 | my %symlinks_marked; # this is: | 
| 1324 |  |  |  |  |  |  | # { symlink_parent (canonical) => [ | 
| 1325 |  |  |  |  |  |  | #    { symlink_path => symlink_path (non canonical), | 
| 1326 |  |  |  |  |  |  | #      action       => 'delete' | 
| 1327 |  |  |  |  |  |  | #    } | 
| 1328 |  |  |  |  |  |  | #   ],... | 
| 1329 |  |  |  |  |  |  | # } | 
| 1330 |  |  |  |  |  |  |  | 
| 1331 | 24 |  |  |  |  | 81 | foreach my $ra_item (@{$ra_plan}) {                 # 1 | 
|  | 24 |  |  |  |  | 64 |  | 
| 1332 | 1267 |  |  |  |  | 1724 | my ($reason, $f, $action) = @$ra_item; | 
| 1333 |  |  |  |  |  |  |  | 
| 1334 | 1267 | 100 |  |  |  | 2216 | if (exists $rh_symlinks->{$f}) { | 
| 1335 |  |  |  |  |  |  | # 2 - Keep the symlink if its target is kept, delete otherwise | 
| 1336 | 2 |  |  |  |  | 4 | foreach my $sym_path (@{$rh_symlinks->{$f}}) { | 
|  | 2 |  |  |  |  | 8 |  | 
| 1337 |  |  |  |  |  |  |  | 
| 1338 | 3 |  |  |  |  | 12 | my $sym_cparent = $self->_path_check( | 
| 1339 |  |  |  |  |  |  | $self->_parent_path($sym_path) | 
| 1340 |  |  |  |  |  |  | ); | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 | 3 | 50 |  |  |  | 22 | my $sym_action  = ($action eq 'nothing') ? 'nothing' : 'delete'; | 
| 1343 |  |  |  |  |  |  |  | 
| 1344 |  |  |  |  |  |  | # two symlinks may be in the same directory, | 
| 1345 | 3 | 50 |  |  |  | 14 | if (!exists $symlinks_marked{$sym_cparent}) { | 
| 1346 | 3 |  |  |  |  | 13 | $symlinks_marked{$sym_cparent} = []; | 
| 1347 |  |  |  |  |  |  | } | 
| 1348 |  |  |  |  |  |  |  | 
| 1349 | 3 |  |  |  |  | 5 | push( @{$symlinks_marked{$sym_cparent}}, | 
|  | 3 |  |  |  |  | 26 |  | 
| 1350 |  |  |  |  |  |  | { symlink_path => $sym_path, | 
| 1351 |  |  |  |  |  |  | action       => $sym_action | 
| 1352 |  |  |  |  |  |  | } | 
| 1353 |  |  |  |  |  |  | ); | 
| 1354 |  |  |  |  |  |  | } | 
| 1355 |  |  |  |  |  |  | } | 
| 1356 |  |  |  |  |  |  | } | 
| 1357 |  |  |  |  |  |  |  | 
| 1358 |  |  |  |  |  |  | # 3 | 
| 1359 | 24 |  |  |  |  | 67 | my $rh_undelete_dirs = {}; | 
| 1360 | 24 |  |  |  |  | 42 | my $ra_refined_plan = []; | 
| 1361 | 24 |  |  |  |  | 34 | while ( my $ra_item = pop @{$ra_plan} ) { | 
|  | 1291 |  |  |  |  | 2329 |  | 
| 1362 | 1267 |  |  |  |  | 1586 | my ($reason, $f, $action) = @$ra_item; | 
| 1363 | 1267 | 100 |  |  |  | 1749 | if (!exists $symlinks_marked{$f} ) { | 
| 1364 |  |  |  |  |  |  | # just re-add it | 
| 1365 | 1264 |  |  |  |  | 2721 | $self->_plan_add_action( $ra_refined_plan, | 
| 1366 |  |  |  |  |  |  | { action => $action, | 
| 1367 |  |  |  |  |  |  | reason => $reason, | 
| 1368 |  |  |  |  |  |  | f_path => $f, | 
| 1369 |  |  |  |  |  |  | } | 
| 1370 |  |  |  |  |  |  | ); | 
| 1371 |  |  |  |  |  |  | } | 
| 1372 |  |  |  |  |  |  | else { | 
| 1373 |  |  |  |  |  |  | # fix the action of a symlink parent - keep the parent if at least | 
| 1374 |  |  |  |  |  |  | # one symlink in it is kept. | 
| 1375 | 3 |  |  |  |  | 16 | my @sym_nothing = | 
| 1376 | 3 |  |  |  |  | 5 | grep { $_->{action} eq 'nothing' } @{$symlinks_marked{$f}}; | 
|  | 3 |  |  |  |  | 12 |  | 
| 1377 |  |  |  |  |  |  |  | 
| 1378 | 3 |  |  |  |  | 6 | my $f_action; | 
| 1379 |  |  |  |  |  |  | my $f_reason; | 
| 1380 | 3 | 50 |  |  |  | 8 | if (scalar @sym_nothing) { # at least one symlink to be kept | 
| 1381 | 3 |  |  |  |  | 6 | $f_action = 'nothing'; | 
| 1382 | 3 |  |  |  |  | 6 | $f_reason = 'refined (1+ symlink kept in it)'; | 
| 1383 |  |  |  |  |  |  |  | 
| 1384 |  |  |  |  |  |  | # Propagate to the parent | 
| 1385 | 3 |  |  |  |  | 9 | my $f_parent = $self->_parent_path($f); | 
| 1386 | 3 | 50 |  |  |  | 20 | $rh_undelete_dirs->{ $f_parent } = 1 if $f_parent; | 
| 1387 |  |  |  |  |  |  | } | 
| 1388 |  |  |  |  |  |  | else { | 
| 1389 | 0 |  |  |  |  | 0 | $f_action = $action; | 
| 1390 | 0 |  |  |  |  | 0 | $f_reason = 'refined (all symlinks will be deleted)'; | 
| 1391 |  |  |  |  |  |  | } | 
| 1392 |  |  |  |  |  |  | # Add the symlink parent with the updated action | 
| 1393 | 3 |  |  |  |  | 21 | $self->_plan_add_action( $ra_refined_plan, | 
| 1394 |  |  |  |  |  |  | { action => $f_action, | 
| 1395 |  |  |  |  |  |  | reason => $f_reason, | 
| 1396 |  |  |  |  |  |  | f_path => $f, | 
| 1397 |  |  |  |  |  |  | } | 
| 1398 |  |  |  |  |  |  | ); | 
| 1399 |  |  |  |  |  |  |  | 
| 1400 |  |  |  |  |  |  | # Add the action on each symlink's path | 
| 1401 | 3 |  |  |  |  | 7 | foreach my $rh_item (@{$symlinks_marked{$f}}) { | 
|  | 3 |  |  |  |  | 13 |  | 
| 1402 | 3 |  |  |  |  | 19 | $self->_plan_add_action( $ra_refined_plan, | 
| 1403 |  |  |  |  |  |  | { action => $rh_item->{action}, | 
| 1404 |  |  |  |  |  |  | reason => 'refined', | 
| 1405 |  |  |  |  |  |  | f_path => $rh_item->{symlink_path}, | 
| 1406 |  |  |  |  |  |  | } | 
| 1407 |  |  |  |  |  |  | ); | 
| 1408 |  |  |  |  |  |  | } | 
| 1409 |  |  |  |  |  |  | } | 
| 1410 |  |  |  |  |  |  | } | 
| 1411 |  |  |  |  |  |  |  | 
| 1412 |  |  |  |  |  |  | # 4 - fix inconsistent directory state (and reverse the plan again) | 
| 1413 |  |  |  |  |  |  | # | 
| 1414 | 24 |  |  |  |  | 37 | my @refined_plan_fixed; | 
| 1415 | 24 | 100 |  |  |  | 55 | my $add_to_head = ($rh_delete_once_empty) ? 0 : 1; | 
| 1416 | 24 |  |  |  |  | 79 | while ( my $ra_item = pop @$ra_refined_plan ) { | 
| 1417 | 1270 |  |  |  |  | 1988 | my ($reason, $f, $action) = @$ra_item; | 
| 1418 | 1270 | 100 | 100 |  |  | 28193 | if (-d $f && !-l $f) { | 
| 1419 |  |  |  |  |  |  | ## | 
| 1420 |  |  |  |  |  |  | ## Directory | 
| 1421 |  |  |  |  |  |  | ## | 
| 1422 | 374 | 100 |  |  |  | 781 | if ($rh_undelete_dirs->{$f}) { | 
| 1423 | 16 |  |  |  |  | 25 | $action = 'nothing'; | 
| 1424 | 16 |  |  |  |  | 36 | $reason = "bubbled (was: $reason)"; | 
| 1425 |  |  |  |  |  |  |  | 
| 1426 |  |  |  |  |  |  | # also propagate to the parent | 
| 1427 | 16 |  |  |  |  | 42 | my $f_parent = $self->_parent_path($f); | 
| 1428 | 16 | 50 |  |  |  | 62 | $rh_undelete_dirs->{$f_parent} = 1 if $f_parent; | 
| 1429 |  |  |  |  |  |  | } | 
| 1430 |  |  |  |  |  |  | } | 
| 1431 |  |  |  |  |  |  | ## | 
| 1432 |  |  |  |  |  |  | ## Add current item to the list | 
| 1433 |  |  |  |  |  |  | ## | 
| 1434 | 1270 |  |  |  |  | 4742 | $self->_plan_add_action( \@refined_plan_fixed, | 
| 1435 |  |  |  |  |  |  | { action => $action, | 
| 1436 |  |  |  |  |  |  | reason => $reason, | 
| 1437 |  |  |  |  |  |  | f_path => $f | 
| 1438 |  |  |  |  |  |  | } | 
| 1439 |  |  |  |  |  |  | , $add_to_head | 
| 1440 |  |  |  |  |  |  | ); | 
| 1441 |  |  |  |  |  |  | } | 
| 1442 |  |  |  |  |  |  |  | 
| 1443 | 24 | 100 |  |  |  | 580 | return \@refined_plan_fixed if (!$rh_delete_once_empty); | 
| 1444 |  |  |  |  |  |  |  | 
| 1445 | 1 |  |  |  |  | 4 | my @final_plan; | 
| 1446 |  |  |  |  |  |  | my $propagate_action; | 
| 1447 | 1 |  |  |  |  | 9 | while ( my $ra_item = pop @refined_plan_fixed ) { | 
| 1448 | 40 |  |  |  |  | 79 | my ($reason, $f, $action) = @$ra_item; | 
| 1449 |  |  |  |  |  |  | ## | 
| 1450 |  |  |  |  |  |  | ## Check if we have to stop any previous propagation at this round. | 
| 1451 |  |  |  |  |  |  | ## | 
| 1452 | 40 | 100 |  |  |  | 65 | if ($propagate_action) { | 
| 1453 |  |  |  |  |  |  |  | 
| 1454 | 9 | 100 |  |  |  | 27 | $propagate_action = (index($f, $propagate_action) == 0) | 
| 1455 |  |  |  |  |  |  | ? $propagate_action | 
| 1456 |  |  |  |  |  |  | : 0 ; | 
| 1457 |  |  |  |  |  |  |  | 
| 1458 |  |  |  |  |  |  | } | 
| 1459 |  |  |  |  |  |  |  | 
| 1460 |  |  |  |  |  |  | ## | 
| 1461 |  |  |  |  |  |  | ## See if we should propagate the 'nothing' action to any children | 
| 1462 |  |  |  |  |  |  | ## | 
| 1463 | 40 | 100 | 100 |  |  | 117 | if (!$propagate_action              # we are not propagating... | 
|  |  |  | 100 |  |  |  |  | 
| 1464 |  |  |  |  |  |  | && $self->_delete_once_empty_contains(   # toplevel directory found | 
| 1465 |  |  |  |  |  |  | $rh_delete_once_empty, | 
| 1466 |  |  |  |  |  |  | $f | 
| 1467 |  |  |  |  |  |  | ) | 
| 1468 |  |  |  |  |  |  | && $action eq 'nothing'  ) {    # ... which we don't want to delete | 
| 1469 |  |  |  |  |  |  |  | 
| 1470 | 4 |  |  |  |  | 8 | $propagate_action = $f;         # propagate until /^/ | 
| 1471 |  |  |  |  |  |  | # matches $f from this round | 
| 1472 |  |  |  |  |  |  | } | 
| 1473 |  |  |  |  |  |  |  | 
| 1474 | 40 | 100 | 100 |  |  | 106 | if ($propagate_action | 
| 1475 |  |  |  |  |  |  | && $f ne $propagate_action ) {  # aesthetics only | 
| 1476 |  |  |  |  |  |  |  | 
| 1477 | 5 |  |  |  |  | 7 | $reason = 'all or none'; | 
| 1478 | 5 |  |  |  |  | 7 | $action = 'nothing'; | 
| 1479 |  |  |  |  |  |  | } | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 | 40 |  |  |  |  | 154 | $self->_plan_add_action( \@final_plan, | 
| 1482 |  |  |  |  |  |  | { action => $action, | 
| 1483 |  |  |  |  |  |  | reason => $reason, | 
| 1484 |  |  |  |  |  |  | f_path => $f | 
| 1485 |  |  |  |  |  |  | } | 
| 1486 |  |  |  |  |  |  | ); | 
| 1487 |  |  |  |  |  |  | } | 
| 1488 |  |  |  |  |  |  |  | 
| 1489 | 1 |  |  |  |  | 50 | return \@final_plan; | 
| 1490 |  |  |  |  |  |  | } | 
| 1491 |  |  |  |  |  |  |  | 
| 1492 |  |  |  |  |  |  | =for _parent_path | 
| 1493 |  |  |  |  |  |  | Get the parent path of a given path. This method only accesses the disk if the | 
| 1494 |  |  |  |  |  |  | f_path is found to have no parent directory (i.e., just the relative file name | 
| 1495 |  |  |  |  |  |  | has been specified).  In this case, we check that the current working directory | 
| 1496 |  |  |  |  |  |  | contains the given file. If yes, we return the current working directory as the | 
| 1497 |  |  |  |  |  |  | parent of the specified file. If not, we return undef. | 
| 1498 |  |  |  |  |  |  |  | 
| 1499 |  |  |  |  |  |  | =cut | 
| 1500 |  |  |  |  |  |  |  | 
| 1501 |  |  |  |  |  |  | sub _parent_path { | 
| 1502 | 424 |  |  | 424 |  | 540 | my $self   = shift; | 
| 1503 | 424 |  |  |  |  | 505 | my $f_path = shift; | 
| 1504 |  |  |  |  |  |  |  | 
| 1505 | 424 | 50 |  |  |  | 799 | if (!$f_path) { | 
| 1506 | 0 |  |  |  |  | 0 | $self->_warn("No path was given to _parent_path()"); | 
| 1507 | 0 |  |  |  |  | 0 | return undef; | 
| 1508 |  |  |  |  |  |  | } | 
| 1509 |  |  |  |  |  |  |  | 
| 1510 | 424 |  |  |  |  | 7479 | my ($volume, $directories, $file) = File::Spec->splitpath($f_path); | 
| 1511 |  |  |  |  |  |  |  | 
| 1512 |  |  |  |  |  |  | ## | 
| 1513 |  |  |  |  |  |  | ## Try to reconstruct the full pathname of the parent of a relative $f_path | 
| 1514 |  |  |  |  |  |  | ## | 
| 1515 | 424 | 50 |  |  |  | 1083 | if (!$directories) { | 
| 1516 | 0 |  |  |  |  | 0 | my $cwd = Cwd->getcwd(); | 
| 1517 | 0 | 0 |  |  |  | 0 | if (-e File::Spec->catpath($volume, $cwd, $file)) { | 
| 1518 | 0 |  |  |  |  | 0 | $self->_info("Returning $cwd as the parent path for $file"); | 
| 1519 | 0 |  |  |  |  | 0 | return $cwd; | 
| 1520 |  |  |  |  |  |  | } | 
| 1521 |  |  |  |  |  |  | else { | 
| 1522 | 0 |  |  |  |  | 0 | $self->_warn("The relative pathname $f_path was given to" | 
| 1523 |  |  |  |  |  |  | . "_parent_path(), but such target doesn't exist in the current" | 
| 1524 |  |  |  |  |  |  | . "working directory ($cwd)." | 
| 1525 |  |  |  |  |  |  | ); | 
| 1526 | 0 |  |  |  |  | 0 | return undef; | 
| 1527 |  |  |  |  |  |  | } | 
| 1528 |  |  |  |  |  |  | } | 
| 1529 |  |  |  |  |  |  |  | 
| 1530 | 424 |  |  |  |  | 2602 | my $f_parent = File::Spec->catpath($volume, $directories, ''); | 
| 1531 | 424 |  |  |  |  | 1863 | $f_parent =~ s#/$##g; | 
| 1532 |  |  |  |  |  |  |  | 
| 1533 | 424 |  |  |  |  | 1297 | return $f_parent; | 
| 1534 |  |  |  |  |  |  | } | 
| 1535 |  |  |  |  |  |  |  | 
| 1536 |  |  |  |  |  |  |  | 
| 1537 |  |  |  |  |  |  | =begin _postprocess_link | 
| 1538 |  |  |  |  |  |  |  | 
| 1539 |  |  |  |  |  |  | Given a path to a symlink and a hash reference, keep the symlink target as a | 
| 1540 |  |  |  |  |  |  | key of the hash reference (canonical path), and the path to the symlink (non | 
| 1541 |  |  |  |  |  |  | canonical) as the corresponding value. Because multiple symlinks can point to | 
| 1542 |  |  |  |  |  |  | the same target, the value of this hashref is an arrayref of symlinks paths. | 
| 1543 |  |  |  |  |  |  |  | 
| 1544 |  |  |  |  |  |  | Returns true on success, or false if a path to something else than a symlink is | 
| 1545 |  |  |  |  |  |  | passed to this method. | 
| 1546 |  |  |  |  |  |  |  | 
| 1547 |  |  |  |  |  |  | =end _postprocess_link | 
| 1548 |  |  |  |  |  |  |  | 
| 1549 |  |  |  |  |  |  | =cut | 
| 1550 |  |  |  |  |  |  |  | 
| 1551 |  |  |  |  |  |  | sub _postprocess_link { | 
| 1552 | 87 |  |  | 87 |  | 89 | my $self        = shift; | 
| 1553 | 87 |  |  |  |  | 76 | my $rh_symlinks = shift; | 
| 1554 | 87 |  |  |  |  | 98 | my $sym_path    = shift; | 
| 1555 |  |  |  |  |  |  |  | 
| 1556 | 87 | 100 |  |  |  | 1102 | if (my $sym_target = readlink($sym_path)) { # check if this is a symlink | 
| 1557 | 5 |  |  |  |  | 18 | my $sym_target_cpath = $self->_path_check($sym_target); | 
| 1558 | 5 | 100 |  |  |  | 23 | if (!exists $rh_symlinks->{$sym_target_cpath}) { | 
| 1559 | 3 |  |  |  |  | 11 | $rh_symlinks->{$sym_target_cpath} = []; | 
| 1560 |  |  |  |  |  |  | } | 
| 1561 | 5 |  |  |  |  | 7 | push (@{$rh_symlinks->{$sym_target_cpath}}, $sym_path); | 
|  | 5 |  |  |  |  | 16 |  | 
| 1562 |  |  |  |  |  |  |  | 
| 1563 | 5 |  |  |  |  | 17 | return 1; | 
| 1564 |  |  |  |  |  |  | } | 
| 1565 |  |  |  |  |  |  |  | 
| 1566 |  |  |  |  |  |  | # $sym_path is not a path to a symlink | 
| 1567 | 82 |  |  |  |  | 163 | return 0; | 
| 1568 |  |  |  |  |  |  | } | 
| 1569 |  |  |  |  |  |  |  | 
| 1570 |  |  |  |  |  |  | =begin _fix_pattern | 
| 1571 |  |  |  |  |  |  |  | 
| 1572 |  |  |  |  |  |  | Refine a pattern passed from the configuration. | 
| 1573 |  |  |  |  |  |  |  | 
| 1574 |  |  |  |  |  |  | Currently applyes the following transformation: | 
| 1575 |  |  |  |  |  |  | - Remove any "/" in case the user has specified a pattern in the form of | 
| 1576 |  |  |  |  |  |  | /pattern/. | 
| 1577 |  |  |  |  |  |  |  | 
| 1578 |  |  |  |  |  |  | =end _fix_pattern | 
| 1579 |  |  |  |  |  |  |  | 
| 1580 |  |  |  |  |  |  | =cut | 
| 1581 |  |  |  |  |  |  |  | 
| 1582 |  |  |  |  |  |  | sub _fix_pattern { | 
| 1583 | 177 |  |  | 177 |  | 183 | my $self    = shift; | 
| 1584 | 177 |  |  |  |  | 200 | my $pattern = shift; | 
| 1585 |  |  |  |  |  |  |  | 
| 1586 | 177 | 100 |  |  |  | 685 | if ($pattern =~ m{^/(.*)/$}) { | 
| 1587 | 50 |  |  |  |  | 165 | $pattern = $1; | 
| 1588 |  |  |  |  |  |  | } | 
| 1589 | 177 |  |  |  |  | 315 | return $pattern; | 
| 1590 |  |  |  |  |  |  | } | 
| 1591 |  |  |  |  |  |  |  | 
| 1592 |  |  |  |  |  |  |  | 
| 1593 |  |  |  |  |  |  | sub _print_never_delete { | 
| 1594 | 24 |  |  | 24 |  | 50 | my $self = shift; | 
| 1595 | 24 |  |  |  |  | 40 | my $rh_never_delete = shift; | 
| 1596 | 24 | 50 |  |  |  | 102 | if ( !scalar keys %$rh_never_delete ) { | 
| 1597 | 0 |  |  |  |  | 0 | $self->_info ("- - - [ NO NEVER DELETE FILES] - - -"); | 
| 1598 |  |  |  |  |  |  | } | 
| 1599 |  |  |  |  |  |  | else { | 
| 1600 | 24 |  |  |  |  | 90 | $self->_info ("- - - [ NEVER DELETE ] - - -"); | 
| 1601 | 24 |  |  |  |  | 37 | foreach my $path (keys %{$rh_never_delete->{paths}}) { | 
|  | 24 |  |  |  |  | 140 |  | 
| 1602 | 178 |  |  |  |  | 489 | $self->_info (sprintf("* %s", $path)); | 
| 1603 |  |  |  |  |  |  | } | 
| 1604 | 24 |  |  |  |  | 73 | $self->_info (""); | 
| 1605 |  |  |  |  |  |  | } | 
| 1606 |  |  |  |  |  |  | } | 
| 1607 |  |  |  |  |  |  |  | 
| 1608 |  |  |  |  |  |  | sub _print_delete_once_empty { | 
| 1609 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 1610 | 1 |  |  |  |  | 3 | my $rh_delete_once_empty = shift; | 
| 1611 | 1 | 50 |  |  |  | 7 | if ( !scalar keys %$rh_delete_once_empty ) { | 
| 1612 | 0 |  |  |  |  | 0 | $self->_info ("- - - [ NO DELETE ONCE EMPTY ] - - -"); | 
| 1613 |  |  |  |  |  |  | } | 
| 1614 |  |  |  |  |  |  | else { | 
| 1615 | 1 |  |  |  |  | 6 | $self->_info ("- - - [ DELETE ONCE EMPTY ] - - -"); | 
| 1616 | 1 |  |  |  |  | 3 | foreach my $path (keys %{$rh_delete_once_empty->{paths}}) { | 
|  | 1 |  |  |  |  | 11 |  | 
| 1617 | 18 |  |  |  |  | 49 | $self->_info (sprintf("* %s", $path)); | 
| 1618 |  |  |  |  |  |  | } | 
| 1619 | 1 |  |  |  |  | 6 | $self->_info (""); | 
| 1620 |  |  |  |  |  |  | } | 
| 1621 |  |  |  |  |  |  | } | 
| 1622 |  |  |  |  |  |  | sub _print_plan { | 
| 1623 | 24 |  |  | 24 |  | 55 | my $self    = shift; | 
| 1624 | 24 |  |  |  |  | 36 | my $ra_plan = shift; | 
| 1625 |  |  |  |  |  |  |  | 
| 1626 | 24 |  |  |  |  | 57 | my $i = 1 + scalar @$ra_plan; | 
| 1627 |  |  |  |  |  |  |  | 
| 1628 | 24 | 50 | 33 |  |  | 176 | if ( !$ra_plan || !scalar @$ra_plan ) { | 
| 1629 | 0 |  |  |  |  | 0 | $self->_info ("- - - [ EMPTY PLAN ] - - -"); | 
| 1630 |  |  |  |  |  |  | } | 
| 1631 |  |  |  |  |  |  | else { | 
| 1632 | 24 |  |  |  |  | 78 | $self->_info ("- - - [ PLAN ] - - -"); | 
| 1633 | 24 |  |  |  |  | 68 | foreach my $ra_plan_item (@$ra_plan) { | 
| 1634 | 1270 |  |  |  |  | 928 | $i--; | 
| 1635 |  |  |  |  |  |  |  | 
| 1636 | 1270 |  |  |  |  | 1647 | my ($reason, $f, $action) = @$ra_plan_item; | 
| 1637 | 1270 |  |  |  |  | 2743 | $self->_info( | 
| 1638 |  |  |  |  |  |  | sprintf("%2d) [%7s] %14s - %s", $i, $action, $reason, $f) | 
| 1639 |  |  |  |  |  |  | ); | 
| 1640 |  |  |  |  |  |  | } | 
| 1641 |  |  |  |  |  |  | } | 
| 1642 | 24 |  |  |  |  | 75 | $self->_info (""); | 
| 1643 |  |  |  |  |  |  | } | 
| 1644 |  |  |  |  |  |  |  | 
| 1645 |  |  |  |  |  |  | sub _info { | 
| 1646 | 2644 |  |  | 2644 |  | 2388 | my $self    = shift; | 
| 1647 | 2644 |  |  |  |  | 2290 | my $message = shift; | 
| 1648 | 2644 | 50 |  |  |  | 3386 | print " [INFO] $message\n" if $self->verbose; | 
| 1649 |  |  |  |  |  |  | } | 
| 1650 |  |  |  |  |  |  |  | 
| 1651 |  |  |  |  |  |  | sub _warn { | 
| 1652 | 0 |  |  | 0 |  |  | my $self    = shift; | 
| 1653 | 0 |  |  |  |  |  | my $message = shift; | 
| 1654 | 0 |  |  |  |  |  | warn " [WARN] $message"; | 
| 1655 |  |  |  |  |  |  | } | 
| 1656 |  |  |  |  |  |  |  | 
| 1657 |  |  |  |  |  |  | sub _usage_and_exit { | 
| 1658 | 0 |  |  | 0 |  |  | my $self    = shift; | 
| 1659 | 0 |  |  |  |  |  | my $message = shift; | 
| 1660 |  |  |  |  |  |  |  | 
| 1661 | 0 |  |  |  |  |  | print <<"END"; | 
| 1662 |  |  |  |  |  |  | $0 | 
| 1663 |  |  |  |  |  |  | required: | 
| 1664 |  |  |  |  |  |  | --conf      a tasks configuration file | 
| 1665 |  |  |  |  |  |  | --taskname  a task from within the tasks file | 
| 1666 |  |  |  |  |  |  |  | 
| 1667 |  |  |  |  |  |  | optional: | 
| 1668 |  |  |  |  |  |  | --dryrun    output plan and then exit | 
| 1669 |  |  |  |  |  |  | --verbose   make some noise! | 
| 1670 |  |  |  |  |  |  | --help      show this message | 
| 1671 |  |  |  |  |  |  |  | 
| 1672 |  |  |  |  |  |  | For more information and documentation for how to write task config files see | 
| 1673 |  |  |  |  |  |  | 'perldoc File::CleanupTask'. | 
| 1674 |  |  |  |  |  |  |  | 
| 1675 |  |  |  |  |  |  | END | 
| 1676 | 0 | 0 |  |  |  |  | if ($message) { | 
| 1677 | 0 |  |  |  |  |  | die( $message . "\n" ); | 
| 1678 |  |  |  |  |  |  | } | 
| 1679 |  |  |  |  |  |  | else { | 
| 1680 | 0 |  |  |  |  |  | exit; | 
| 1681 |  |  |  |  |  |  | } | 
| 1682 |  |  |  |  |  |  | } | 
| 1683 |  |  |  |  |  |  |  | 
| 1684 |  |  |  |  |  |  | =head1 AUTHOR | 
| 1685 |  |  |  |  |  |  |  | 
| 1686 |  |  |  |  |  |  | Savio Dimatteo, C<<  >> | 
| 1687 |  |  |  |  |  |  |  | 
| 1688 |  |  |  |  |  |  | =head1 BUGS | 
| 1689 |  |  |  |  |  |  |  | 
| 1690 |  |  |  |  |  |  | Please report any bugs or feature requests to C, or through | 
| 1691 |  |  |  |  |  |  | the web interface at L.  I will be notified, and then you'll | 
| 1692 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 1693 |  |  |  |  |  |  |  | 
| 1694 |  |  |  |  |  |  |  | 
| 1695 |  |  |  |  |  |  |  | 
| 1696 |  |  |  |  |  |  |  | 
| 1697 |  |  |  |  |  |  | =head1 SUPPORT | 
| 1698 |  |  |  |  |  |  |  | 
| 1699 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 1700 |  |  |  |  |  |  |  | 
| 1701 |  |  |  |  |  |  | perldoc File::CleanupTask | 
| 1702 |  |  |  |  |  |  |  | 
| 1703 |  |  |  |  |  |  |  | 
| 1704 |  |  |  |  |  |  | You can also look for information at: | 
| 1705 |  |  |  |  |  |  |  | 
| 1706 |  |  |  |  |  |  | =over 4 | 
| 1707 |  |  |  |  |  |  |  | 
| 1708 |  |  |  |  |  |  | =item * RT: CPAN's request tracker (report bugs here) | 
| 1709 |  |  |  |  |  |  |  | 
| 1710 |  |  |  |  |  |  | L | 
| 1711 |  |  |  |  |  |  |  | 
| 1712 |  |  |  |  |  |  | =item * AnnoCPAN: Annotated CPAN documentation | 
| 1713 |  |  |  |  |  |  |  | 
| 1714 |  |  |  |  |  |  | L | 
| 1715 |  |  |  |  |  |  |  | 
| 1716 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 1717 |  |  |  |  |  |  |  | 
| 1718 |  |  |  |  |  |  | L | 
| 1719 |  |  |  |  |  |  |  | 
| 1720 |  |  |  |  |  |  | =item * Search CPAN | 
| 1721 |  |  |  |  |  |  |  | 
| 1722 |  |  |  |  |  |  | L | 
| 1723 |  |  |  |  |  |  |  | 
| 1724 |  |  |  |  |  |  | =back | 
| 1725 |  |  |  |  |  |  |  | 
| 1726 |  |  |  |  |  |  |  | 
| 1727 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 1728 |  |  |  |  |  |  |  | 
| 1729 |  |  |  |  |  |  | Thanks Alex for devising the original format of a .tasks file and offering me | 
| 1730 |  |  |  |  |  |  | the opportunity to publish this work on CPAN. | 
| 1731 |  |  |  |  |  |  |  | 
| 1732 |  |  |  |  |  |  | Thanks Mike for your feedback about canonical paths detection. | 
| 1733 |  |  |  |  |  |  |  | 
| 1734 |  |  |  |  |  |  | Thanks David for reviewing the code. | 
| 1735 |  |  |  |  |  |  |  | 
| 1736 |  |  |  |  |  |  | Thanks #london.pm for helping me choosing the name of this module. | 
| 1737 |  |  |  |  |  |  |  | 
| 1738 |  |  |  |  |  |  |  | 
| 1739 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 1740 |  |  |  |  |  |  |  | 
| 1741 |  |  |  |  |  |  | Copyright 2012 Savio Dimatteo. | 
| 1742 |  |  |  |  |  |  |  | 
| 1743 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 1744 |  |  |  |  |  |  | under the terms of either: the GNU General Public License as published | 
| 1745 |  |  |  |  |  |  | by the Free Software Foundation; or the Artistic License. | 
| 1746 |  |  |  |  |  |  |  | 
| 1747 |  |  |  |  |  |  | See http://dev.perl.org/licenses/ for more information. | 
| 1748 |  |  |  |  |  |  |  | 
| 1749 |  |  |  |  |  |  |  | 
| 1750 |  |  |  |  |  |  | =cut | 
| 1751 |  |  |  |  |  |  |  | 
| 1752 |  |  |  |  |  |  | 1; # End of File::CleanupTask |