gboudreau/Greyhole

View on GitHub
includes/Tasks/RenameTask.php

Summary

Maintainability
F
3 days
Test Coverage
<?php
/*
Copyright 2009-2020 Guillaume Boudreau

This file is part of Greyhole.

Greyhole is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Greyhole is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Greyhole.  If not, see <http://www.gnu.org/licenses/>.
*/

class RenameTask extends AbstractTask {

    private $fix_symlinks_scanned_dirs = [];

    public function execute() {
        $this->fix_symlinks_scanned_dirs = [];

        $share = $this->share;
        $full_path = $this->full_path;
        $target_full_path = $this->additional_info;
        $task_id = $this->id;

        $landing_zone = get_share_landing_zone($share);
        if (!$landing_zone) {
            return TRUE;
        }

        if ($this->should_ignore_file()) {
            return TRUE;
        }

        if (is_dir("$landing_zone/$target_full_path") || Metastores::dir_exists_in_metastores($share, $full_path)) {
            Log::info("Directory renamed: $landing_zone/$full_path -> $landing_zone/$target_full_path");

            foreach (Config::storagePoolDrives() as $sp_drive) {
                if (!StoragePool::is_pool_drive($sp_drive)) {
                    continue;
                }
                list($original_path, ) = explode_full_path(get_share_landing_zone($share) . "/$target_full_path");

                if (is_dir("$sp_drive/$share/$full_path")) {
                    // Make sure the parent directory of target_full_path exists, before we try moving something there...
                    list($path, ) = explode_full_path("$sp_drive/$share/$target_full_path");
                    gh_mkdir($path, $original_path);

                    gh_rename("$sp_drive/$share/$full_path", "$sp_drive/$share/$target_full_path");

                    // Make sure all the copies of this folder have the right owner & permissions
                    $dir_permissions = StorageFile::get_file_permissions("$landing_zone/$target_full_path");
                    chown("$sp_drive/$share/$target_full_path", $dir_permissions->fileowner);
                    chgrp("$sp_drive/$share/$target_full_path", $dir_permissions->filegroup);
                    chmod("$sp_drive/$share/$target_full_path", $dir_permissions->fileperms);

                    Log::debug("  Directory moved: $sp_drive/$share/$full_path -> $sp_drive/$share/$target_full_path");
                }

                list($path, ) = explode_full_path("$sp_drive/" . Metastores::METASTORE_DIR . "/$share/$target_full_path");
                gh_mkdir($path, $original_path);
                $result = @gh_rename("$sp_drive/" . Metastores::METASTORE_DIR . "/$share/$full_path", "$sp_drive/" . Metastores::METASTORE_DIR . "/$share/$target_full_path");
                if ($result) {
                    Log::debug("  Metadata Store directory moved: $sp_drive/" . Metastores::METASTORE_DIR ."/$share/$full_path -> $sp_drive/" . Metastores::METASTORE_DIR . "/$share/$target_full_path");
                }
                $result = @gh_rename("$sp_drive/" . Metastores::METASTORE_BACKUP_DIR . "/$share/$full_path", "$sp_drive/" . Metastores::METASTORE_BACKUP_DIR . "/$share/$target_full_path");
                if ($result) {
                    Log::debug("  Backup Metadata Store directory moved: $sp_drive/" . Metastores::METASTORE_BACKUP_DIR . "/$share/$full_path -> $sp_drive/" . Metastores::METASTORE_BACKUP_DIR . "/$share/$target_full_path");
                }
            }

            // Let look in the LZ too, for files we didn't process yet (maybe the folder was ignored before it was renamed)
            exec('find ' . escapeshellarg("$landing_zone/$target_full_path") . ' -type f', $files_in_lz);
            foreach ($files_in_lz as $file_in_lz) {
                list($file_path, $filename) = explode_full_path($file_in_lz);
                FsckTask::getCurrentTask()->gh_fsck_file($file_path, $filename, 'file', 'landing_zone', $share);
            }

            foreach (Metastores::get_metafiles($share, $target_full_path, null, FALSE, FALSE, FALSE) as $existing_metafiles) {
                Log::debug("Existing metadata files: " . count($existing_metafiles));
                foreach ($existing_metafiles as $file_path => $file_metafiles) {
                    Log::debug("  File metafiles: " . count($file_metafiles));
                    $new_file_metafiles = array();
                    $symlinked = FALSE;
                    foreach ($file_metafiles as $key => $metafile) {
                        $old_path = $metafile->path;
                        $metafile->path = str_replace("/$share/$full_path/$file_path", "/$share/$target_full_path/$file_path", $metafile->path);
                        Log::debug("  Changing metadata file: $old_path -> $metafile->path");
                        $new_file_metafiles[$metafile->path] = $metafile;

                        // is_linked = is the target of the existing symlink
                        if ($metafile->is_linked) {
                            $symlinked = TRUE;
                            $symlink_target = $metafile->path;
                        }
                    }
                    if (!$symlinked && count($file_metafiles) > 0) {
                        // None of the metafiles were is_linked; use the last one for the symlink.
                        /** @noinspection PhpUndefinedVariableInspection */
                        $metafile->is_linked = TRUE;
                        /** @noinspection PhpUndefinedVariableInspection */
                        $file_metafiles[$key] = $metafile;
                        $symlink_target = $metafile->path;
                    }

                    if (is_link("$landing_zone/$target_full_path/$file_path") && !empty($symlink_target) && readlink("$landing_zone/$target_full_path/$file_path") != $symlink_target) {
                        Log::debug("  Updating symlink at $landing_zone/$target_full_path/$file_path to point to $symlink_target");
                        unlink("$landing_zone/$target_full_path/$file_path");
                        gh_symlink($symlink_target, "$landing_zone/$target_full_path/$file_path");
                    } else if (is_link("$landing_zone/$full_path/$file_path") && !empty($symlink_target) && !file_exists(readlink("$landing_zone/$full_path/$file_path"))) {
                        Log::debug("  Updating symlink at $landing_zone/$full_path/$file_path to point to $symlink_target");
                        unlink("$landing_zone/$full_path/$file_path");
                        gh_symlink($symlink_target, "$landing_zone/$full_path/$file_path");
                    } else {
                        $this->fix_symlinks($landing_zone, $share, "$full_path/$file_path", "$target_full_path/$file_path");
                    }

                    list($path, $filename) = explode_full_path("$target_full_path/$file_path");
                    Metastores::save_metafiles($share, $path, $filename, $new_file_metafiles);
                }
            }
        } else {
            Log::info("File renamed: $landing_zone/$full_path -> $landing_zone/$target_full_path");

            // Check if another process locked this file before we work on it.
            if ($this->is_file_locked($share, $target_full_path)) {
                return FALSE;
            }

            list($path, $filename) = explode_full_path($full_path);
            list($target_path, $target_filename) = explode_full_path($target_full_path);

            foreach (Metastores::get_metafiles($share, $path, $filename, FALSE, FALSE, FALSE) as $existing_metafiles) {
                // There might be old metafiles... for example, when a delete task was skipped.
                // Let's remove the file copies if there are any leftovers; correct copies will be re-created below.
                if (file_exists("$landing_zone/$target_full_path") && (count($existing_metafiles) > 0 || !is_link("$landing_zone/$target_full_path"))) {
                    foreach (Metastores::get_metafiles($share, $target_path, $target_filename, TRUE, FALSE, FALSE) as $existing_target_metafiles) {
                        if (count($existing_target_metafiles) > 0) {
                            foreach ($existing_target_metafiles as $metafile) {
                                Trash::trash_file($metafile->path);
                            }
                            Metastores::remove_metafiles($share, $target_path, $target_filename);
                        }
                    }
                }

                if (count($existing_metafiles) == 0) {
                    // Any NOK metafiles that need to be removed?
                    foreach (Metastores::get_metafiles($share, $path, $filename, TRUE, FALSE, FALSE) as $all_existing_metafiles) {
                        if (count($all_existing_metafiles) > 0) {
                            Metastores::remove_metafiles($share, $path, $filename);
                        }
                    }
                    // New file
                    AbstractTask::instantiate(['id' => $task_id, 'action' => 'write', 'share' => $share, 'full_path' => $target_full_path, 'complete' => 'yes'])->execute();
                } else {
                    $symlinked = FALSE;
                    foreach ($existing_metafiles as $key => $metafile) {
                        $old_path = $metafile->path;
                        $metafile->path = str_replace("/$share/$full_path", "/$share/$target_full_path", $old_path);
                        Log::debug("  Renaming copy at $old_path to $metafile->path");

                        // Make sure the target directory exists
                        list($metafile_dir_path, ) = explode_full_path($metafile->path);
                        list($original_path, ) = explode_full_path(get_share_landing_zone($share) . "/$target_full_path");
                        gh_mkdir($metafile_dir_path, $original_path);

                        $it_worked = gh_rename($old_path, $metafile->path);

                        if ($it_worked) {
                            // is_linked = is the target of the existing symlink
                            if ($metafile->is_linked) {
                                $symlinked = TRUE;
                                $symlink_target = $metafile->path;
                            }
                        } else {
                            Log::warn("    Warning! An error occurred while renaming file copy $old_path to $metafile->path.", Log::EVENT_CODE_RENAME_FILE_COPY_FAILED);
                        }
                        $existing_metafiles[$key] = $metafile;
                    }
                    if (!$symlinked && count($existing_metafiles) > 0) {
                        // None of the metafiles were is_linked; use the last one for the symlink.
                        /** @noinspection PhpUndefinedVariableInspection */
                        $metafile->is_linked = TRUE;
                        /** @noinspection PhpUndefinedVariableInspection */
                        $existing_metafiles[$key] = $metafile;
                        $symlink_target = $metafile->path;
                    }
                    Metastores::remove_metafiles($share, $path, $filename);
                    Metastores::save_metafiles($share, $target_path, $target_filename, $existing_metafiles);

                    if (is_link("$landing_zone/$target_full_path")) {
                        // New link exists...
                        /** @noinspection PhpUndefinedVariableInspection */
                        if (readlink("$landing_zone/$target_full_path") != $symlink_target) {
                            // ...and needs to be updated.
                            Log::debug("  Updating symlink at $landing_zone/$target_full_path to point to $symlink_target");
                            unlink("$landing_zone/$target_full_path");
                            gh_symlink($symlink_target, "$landing_zone/$target_full_path");
                        }
                    } else if (is_link("$landing_zone/$full_path") && !file_exists(readlink("$landing_zone/$full_path"))) {
                        /** @noinspection PhpUndefinedVariableInspection */
                        Log::debug("  Updating symlink at $landing_zone/$full_path to point to $symlink_target");
                        unlink("$landing_zone/$full_path");
                        gh_symlink($symlink_target, "$landing_zone/$full_path");
                    } else {
                        $this->fix_symlinks($landing_zone, $share, $full_path, $target_full_path);
                    }
                }
            }
        }
        DBSpool::resetSleepingTasks();

        FileHook::trigger(FileHook::EVENT_TYPE_RENAME, $share, $target_full_path, $full_path);

        return TRUE;
    }

    public function should_ignore_file($share = NULL, $full_path = NULL) {
        // We ignore this task, if the target is ignored, whatever the source ($this->full_path) is
        if (empty($full_path)) {
            $full_path = $this->additional_info;
        }
        return parent::should_ignore_file($share, $full_path);
    }

    private function fix_symlinks($landing_zone, $share, $full_path, $target_full_path) {
        if (isset($this->fix_symlinks_scanned_dirs[$landing_zone])) {
            return;
        }
        Log::info("  Scanning $landing_zone for broken links... This can take a while!");
        exec("find -L " . escapeshellarg($landing_zone) . " -type l", $broken_links);
        Log::debug("    Found " . count($broken_links) . " broken links.");
        foreach ($broken_links as $broken_link) {
            $fixed_link_target = readlink($broken_link);
            Log::debug("    Found a broken symlink to update: $broken_link. Broken target: $fixed_link_target");
            foreach (Config::storagePoolDrives() as $sp_drive) {
                $fixed_link_target = str_replace(clean_dir("$sp_drive/$share/$full_path/"), clean_dir("$sp_drive/$share/$target_full_path/"), $fixed_link_target);
                if ($fixed_link_target == "$sp_drive/$share/$full_path") {
                    $fixed_link_target = "$sp_drive/$share/$target_full_path";
                    break;
                }
            }
            if (gh_is_file($fixed_link_target)) {
                Log::debug("      New (fixed) target: $fixed_link_target");
                unlink($broken_link);
                gh_symlink($fixed_link_target, $broken_link);
            }
        }
        $this->fix_symlinks_scanned_dirs[$landing_zone] = TRUE;
    }

}

?>