init(); } catch(\Throwable $error) { foreach (self::$errorCallbacks as $errorCallback) { $errorCallback($error, "init", $this->getName()); } throw $error; } } /** * A wrapper around 'run' function with non-worker-specific code * * @return void * @throws \Exception|\Throwable */ public function perform(): void { try { $this->run(); } catch(\Throwable $error) { foreach (self::$errorCallbacks as $errorCallback) { $errorCallback($error, "run", $this->getName(), $this->args); } throw $error; } } /** * A wrapper around 'shutdown' function with non-worker-specific code * * @return void * @throws \Exception|\Throwable */ public function tearDown(): void { try { $this->shutdown(); } catch(\Throwable $error) { foreach (self::$errorCallbacks as $errorCallback) { $errorCallback($error, "shutdown", $this->getName()); } throw $error; } } /** * Register callback. Will be executed when error occurs. * @param callable $callback * @param Throwable $error * @return self */ public static function error(callable $callback): void { \array_push(self::$errorCallbacks, $callback); } /** * Get internal project database * @param string $projectId * @return Database */ protected function getProjectDB(string $projectId): Database { return $this->getDB(self::DATABASE_PROJECT, $projectId); } /** * Get console database * @return Database */ protected function getConsoleDB(): Database { return $this->getDB(self::DATABASE_CONSOLE); } /** * Get console database * @param string $type One of (internal, external, console) * @param string $projectId of internal or external DB * @return Database */ private function getDB($type, $projectId = ''): Database { global $register; $namespace = ''; $sleep = DATABASE_RECONNECT_SLEEP; // overwritten when necessary switch ($type) { case self::DATABASE_PROJECT: if (!$projectId) { throw new \Exception('ProjectID not provided - cannot get database'); } $namespace = "_{$projectId}"; break; case self::DATABASE_CONSOLE: $namespace = "_console"; $sleep = 5; // ConsoleDB needs extra sleep time to ensure tables are created break; default: throw new \Exception('Unknown database type: ' . $type); break; } $attempts = 0; do { try { $attempts++; $cache = new Cache(new RedisCache($register->get('cache'))); $database = new Database(new MariaDB($register->get('db')), $cache); $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); $database->setNamespace($namespace); // Main DB if (!empty($projectId) && !$database->getDocument('projects', $projectId)->isEmpty()) { throw new \Exception("Project does not exist: {$projectId}"); } if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), 'realtime')) { throw new \Exception('Console project not ready'); } break; // leave loop if successful } catch(\Exception $e) { Console::warning("Database not ready. Retrying connection ({$attempts})..."); if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) { throw new \Exception('Failed to connect to database: '. $e->getMessage()); } sleep($sleep); } } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS); return $database; } /** * Get Functions Storage Device * @param string $projectId of the project * @return Device */ protected function getFunctionsDevice($projectId): Device { return $this->getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $projectId); } /** * Get Files Storage Device * @param string $projectId of the project * @return Device */ protected function getFilesDevice($projectId): Device { return $this->getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId); } /** * Get Builds Storage Device * @param string $projectId of the project * @return Device */ protected function getBuildsDevice($projectId): Device { return $this->getDevice(APP_STORAGE_BUILDS . '/app-' . $projectId); } /** * Get Device based on selected storage environment * @param string $root path of the device * @return Device */ public function getDevice($root): Device { switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) { case Storage::DEVICE_LOCAL:default: return new Local($root); case Storage::DEVICE_S3: $s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); $s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', ''); $s3Region = App::getEnv('_APP_STORAGE_S3_REGION', ''); $s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', ''); $s3Acl = 'private'; return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); case Storage::DEVICE_DO_SPACES: $doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); $doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); $doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); $doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); $doSpacesAcl = 'private'; return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); case Storage::DEVICE_BACKBLAZE: $backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); $backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); $backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); $backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); $backblazeAcl = 'private'; return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); } } }