Passed
Push — develop ( c8e8dc...cb2748 )
by Nikolay
06:11 queued 13s
created

InstallFromPackage   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 11
eloc 47
c 1
b 0
f 0
dl 0
loc 109
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A start() 0 45 5
A waitForFileUpload() 0 23 5
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Lib\Modules;
21
22
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
23
use MikoPBX\Core\System\Util;
24
use MikoPBX\PBXCoreREST\Lib\Files\FilesConstants;
25
use MikoPBX\PBXCoreREST\Lib\Files\StatusUploadFile;
26
use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
27
28
/**
29
 *  Class InstallModuleFromPackage
30
 *  Installs a new additional extension module from an early uploaded zip archive.
31
 *
32
 * @package MikoPBX\PBXCoreREST\Lib\Modules
33
 */
34
class InstallFromPackage extends ModuleInstallationBase
35
{
36
37
    const UPLOAD_TIMEOUT = 120;
38
39
    // Path to the downloaded zip archive
40
    private string $filePath;
41
42
    // File id of the uploaded zip archive
43
    private string $fileId;
44
45
    /**
46
     * @param string $asyncChannelId Pub/sub nchan channel id to send response to frontend
47
     * @param string $filePath Path to the downloaded zip archive
48
     * @param string $fileId File id of the uploaded zip archive
49
     */
50
    public function __construct(string $asyncChannelId, string $filePath, string $fileId)
51
    {
52
        $this->asyncChannelId = $asyncChannelId;
53
        $this->filePath = $filePath;
54
        $this->fileId = $fileId;
55
        $this->moduleUniqueId = $fileId;
56
    }
57
58
    /**
59
     * Main entry point to install a new module from a zip archive.
60
     * This function handles the entire process of installing a new module.
61
     *
62
     */
63
    public function start(): void
64
    {
65
        // Calculate total mutex timeout and extra 5 seconds to prevent installing the same module in the second thread
66
        $mutexTimeout = self::INSTALLATION_TIMEOUT+self::UPLOAD_TIMEOUT+5;
67
68
        // Create a mutex to ensure synchronized access
69
        $mutex = Util::createMutex(self::INSTALLATION_MUTEX, $this->moduleUniqueId, $mutexTimeout);
70
71
        $res = new PBXApiResult();
72
        $res->processor = __METHOD__;
73
        $res->success = true;
74
75
        // Synchronize the installation process
76
        try{
77
            $mutex->synchronized(
78
                function () use (&$res): void {
79
80
                    // Wait until file upload and merge
81
                    list($fileUploadResult, $res->success) = $this->waitForFileUpload($this->fileId);
82
                    $this->pushMessageToBrowser(self::STAGE_I_UPLOAD_MODULE, $fileUploadResult);
83
                    if (!$res->success) {
84
                        $res->messages['error'] = $fileUploadResult;
85
                        return;
86
                    }
87
88
                    // Install the downloaded module
89
                    list($installationResult, $res->success) = $this->installNewModule($this->filePath);
90
                    if (!$res->success) {
91
                        $res->messages['error'][] = $installationResult;
92
                        return;
93
                    }
94
95
                    // Enable the module if it was previously enabled
96
                    list($enableResult, $res->success) = $this->enableModule($installationResult);
97
                    if (!$res->success) {
98
                        $res->messages['error'][] = $enableResult;
99
                    }
100
101
                });
102
        } catch (\Throwable $e) {
103
            $res->success = false;
104
            $res->messages['error'][] = $e->getMessage();
105
            CriticalErrorsHandler::handleExceptionWithSyslog($e);
106
        } finally {
107
            $this->pushMessageToBrowser( self::STAGE_VII_FINAL_STATUS, $res->getResult());
108
        }
109
110
    }
111
112
    /**
113
     * Uploads the module file, merge it by the provided file ID.
114
     * This function handles the upload process, ensuring that it completes within the allotted time.
115
     *
116
     * @param string $fileId File id of the uploaded zip archive
117
     *
118
     * @return array An array containing the path to the downloaded module or an error message, and a success flag.
119
     */
120
    private function waitForFileUpload(string $fileId): array
121
    {
122
        // Initialization
123
        $maximumUploadTime = self::UPLOAD_TIMEOUT;
124
125
        // Monitor upload and merging progress
126
        while ($maximumUploadTime > 0) {
127
            $resUploadStatus = StatusUploadFile::main($fileId);
128
129
            $this->pushMessageToBrowser( self::STAGE_I_UPLOAD_MODULE, $resUploadStatus->getResult());
130
            if (!$resUploadStatus->success) {
131
                return [$resUploadStatus->messages, false];
132
            } elseif ($resUploadStatus->data[FilesConstants::D_STATUS] === FilesConstants::UPLOAD_IN_PROGRESS) {
133
                sleep(1); // Adjust sleep time as needed
134
                $maximumUploadTime--;
135
            } elseif ($resUploadStatus->data[FilesConstants::D_STATUS] === FilesConstants::UPLOAD_COMPLETE) {
136
                return [$resUploadStatus->messages, true];
137
            }
138
        }
139
140
        // Upload or merging timeout
141
        $this->pushMessageToBrowser( self::STAGE_I_UPLOAD_MODULE, [self::ERR_UPLOAD_TIMEOUT]);
142
        return [self::ERR_UPLOAD_TIMEOUT, false];
143
    }
144
}