Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhilton committed Oct 14, 2024
1 parent dd8bc90 commit 398cabb
Show file tree
Hide file tree
Showing 6 changed files with 645 additions and 4 deletions.
33 changes: 33 additions & 0 deletions classes/azure_blob_storage_file_system.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_objectfs;

use tool_objectfs\local\store\azure_blob_storage\file_system;

/**
* File system for Azure Blob Storage.
* This file tells objectfs that this storage system is available for use.
* E.g. via $CFG->alternative_file_storage_class // TODO check this config var name
*
* @package tool_objectfs
* @author Matthew Hilton <[email protected]>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class azure_blob_storage_file_system extends file_system {

}
1 change: 1 addition & 0 deletions classes/local/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ public static function get_available_fs_list() {
$filesystems['\tool_objectfs\digitalocean_file_system'] = '\tool_objectfs\digitalocean_file_system';
$filesystems['\tool_objectfs\s3_file_system'] = '\tool_objectfs\s3_file_system';
$filesystems['\tool_objectfs\swift_file_system'] = '\tool_objectfs\swift_file_system';
$filesystems['\tool_objectfs\azure_blob_storage_file_system'] = '\tool_objectfs\azure_blob_storage_file_system';

foreach ($filesystems as $filesystem) {
$clientclass = self::get_client_classname_from_fs($filesystem);
Expand Down
13 changes: 9 additions & 4 deletions classes/local/store/azure/stream_wrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@

use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\CachingStream;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Utils;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
use Psr\Http\Message\StreamInterface;
use local_azureblobstorage\api;

/**
* stream_wrapper
Expand Down Expand Up @@ -161,7 +162,11 @@ public function stream_flush() {
}

$hash = hash_final($this->hash);
$md5 = base64_encode(hex2bin($hash));
$md5 = hex2bin($hash);

// TODO get rid of getOptions, get the key directly?.
$params = $this->getOptions(true);
$this->getclient()->put_blob($params['Key'], $this->body, $md5);

$params = $this->getOptions(true);
$params['Body'] = $this->body;
Expand Down Expand Up @@ -409,7 +414,7 @@ private function getoption($name) {
/**
* Gets the client.
*
* @return BlobRestProxy
* @return api
* @throws \RuntimeException if no client has been configured
*/
private function getclient() {
Expand Down Expand Up @@ -443,7 +448,7 @@ private function openreadstream() {

try {
$blob = $client->getBlob($params['Container'], $params['Key']);
$this->body = Psr7\stream_for($blob->getContentStream());
$this->body = Utils::streamFor($blob->getContentStream());
} catch (ServiceException $e) {
// Prevent the client from keeping the request open when the content cannot be found.
$response = $e->getResponse();
Expand Down
230 changes: 230 additions & 0 deletions classes/local/store/azure_blob_storage/client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_objectfs\local\store\azure_blob_storage;

use GuzzleHttp\Psr7\Utils;
use tool_objectfs\local\store\object_client_base;

// TODO does this fail when the plugin is not installed ?
use local_azureblobstorage\api;
use stdClass;
use Throwable;

/**
* Azure blob storage client
*
* @package tool_objectfs
* @author Matthew Hilton <[email protected]>
* @copyright 2024 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class client extends object_client_base {

/** @var api $api Azure API */
protected api $api;

/**
* Creates object client
* @param stdClass $config / TODO is this maybe null ?
*/
public function __construct($config) {
if (empty($config)) {
parent::__construct($config);
return;
}

$this->api = new api($config->azure_accountname, $config->azure_container, $config->azure_sastoken);
}

public function get_availability() {
// Requires local_azureblobstorage to be installed.
$info = \core\plugin_manager::instance()->get_plugin_info('local_azureblobstorage');

// Info is empty if plugin is not installed.
return !empty($info);
}

/**
* Returns the full path for a given file by contenthash
* @param string $contenthash
* @return string filepath
*/
public function get_fullpath_from_hash($contenthash) {
$filepath = $this->get_filepath_from_hash($contenthash);
$container = $this->api->container;
return "blob://$container/$filepath";
}

/**
* Returns the filepath from the contenthash, mimicking the
* structure of the filedir storage system.
*
* @param string $contenthash
* @return string filepath
*/
protected function get_filepath_from_hash($contenthash): string {
$l1 = $contenthash[0] . $contenthash[1];
$l2 = $contenthash[2] . $contenthash[3];
return "$l1/$l2/$contenthash";
}

/**
* Returns the blob key (the key used to reference the blob) from a given filepath.
* @param string $filepath
* @return string
*/
protected function get_blob_key_from_path(string $filepath): string {
$container = $this->api->container;
return str_replace("blob://$container/", '', $filepath);
}

/**
* Deletes a given file
* @param string $fullpath
*/
public function delete_file($fullpath) {
$blobkey = $this->get_blob_key_from_path($fullpath);
// TODO call api to delete.
}

/**
* Renames a given file
* @param string $currentpath
* @param string $destinationpath
*/
public function rename_file($currentpath, $destinationpath) {
// Azure does not support renaming, instead the file is copied
// and the old one is deleted.
copy($currentpath, $destinationpath);
$this->delete_file($currentpath);
}

/**
* Verifies an object is uploaded correctly.
* In Azure, this is done by checking the md5 hash of the contents.
*
* @param string $contenthash
* @param string $localpath
* @return bool
*/
public function verify_object($contenthash, $localpath) {
// TODO get blob properties
// and check md5 hash is correct.

return true;
}

/**
* Returns a stream context used to handle file IO
* @return resource stream resource
*/
public function get_seekable_stream_context() {
$context = stream_context_create([
'blob' => [
'seekable' => true,
],
]);
return $context;
}

// TODO test_permissions

// TODO test_connection

public function test_permissions($testdelete) {
$key = 'permissions_check_test';
$file = Utils::streamFor('test permission file');
$filemd5 = hex2bin(md5('test permission file'));

// Try create a file.
try {
$this->api->put_blob($key, $file, $filemd5)->wait();
} catch (Throwable $e) {
return (object) [
'success' => false,
// TODO can we clean this up... this is awful.
'messages' => [get_string('settings:writefailure', 'tool_objectfs') . $e->getMessage() => 'notifyproblem'],
];
}

// Try read the file that was created.
try {
$this->api->get_blob($key, $file, $filemd5)->wait();
} catch (Throwable $e) {
return (object) [
'success' => false,
// TODO can we clean this up... this is awful.
'messages' => [get_string('settings:permissionreadfailure', 'tool_objectfs') . $e->getMessage() => 'notifyproblem'],
];
}


// If testing delete, try delete the test file.
if ($testdelete) {
try {
// TODO call delete blob, not implemented yet.
} catch (Throwable $e) {
return (object) [
'success' => false,
// TODO can we clean this up... this is awful.
'messages' => [get_string('settings:deleteerror', 'tool_objectfs') . $e->getMessage() => 'notifyproblem'],
];
}
}

return (object) [
'success' => true,
'messages' => [get_string('settings:permissioncheckpassed', 'tool_objectfs') => 'notifysuccess'],
];
}

public function test_connection() {
// Try to create a file.
try {
$this->api->put_blob('connection_check_test', Utils::streamFor('test contents'), hex2bin(md5('test contents')));
} catch (Throwable $e) {
return (object) [
'success' => false,
'details' => $e->getMessage(),
];
}

return (object) [
'success' => true,
'details' => '',
];
}

public function define_client_section($settings, $config) {
$settings->add(new \admin_setting_heading('tool_objectfs/azure',
new \lang_string('settings:azure:header', 'tool_objectfs'), $this->define_client_check()));

$settings->add(new \admin_setting_configtext('tool_objectfs/azure_accountname',
new \lang_string('settings:azure:accountname', 'tool_objectfs'),
new \lang_string('settings:azure:accountname_help', 'tool_objectfs'), ''));

$settings->add(new \admin_setting_configtext('tool_objectfs/azure_container',
new \lang_string('settings:azure:container', 'tool_objectfs'),
new \lang_string('settings:azure:container_help', 'tool_objectfs'), ''));

$settings->add(new \admin_setting_configpasswordunmask('tool_objectfs/azure_sastoken',
new \lang_string('settings:azure:sastoken', 'tool_objectfs'),
new \lang_string('settings:azure:sastoken_help', 'tool_objectfs'), ''));

return $settings;
}
}
39 changes: 39 additions & 0 deletions classes/local/store/azure_blob_storage/file_system.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_objectfs\local\store\azure_blob_storage;

use tool_objectfs\local\store\azure_blob_storage\client;
use tool_objectfs\local\store\object_file_system;

/**
* Azure blob store file system
*
* @package tool_objectfs
* @author Matthew Hilton <[email protected]>
* @copyright 2024 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class file_system extends object_file_system {
/**
* Initialise client
* @param mixed $config
* @return client
*/
protected function initialise_external_client($config) {
return new client($config);
}
}
Loading

0 comments on commit 398cabb

Please sign in to comment.