non working app. moved to the apps-playground repository

This commit is contained in:
Frank Karlitschek 2012-04-24 14:00:44 +02:00
parent 4ea927a798
commit 69a2173fdb
12 changed files with 0 additions and 1217 deletions

View File

@ -1,12 +0,0 @@
<?php
/**
* Copyright (c) 2011 Craig Roberts craig0990@googlemail.com
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
require_once('../../../lib/base.php');
OC_JSON::checkLoggedIn();
// Fetch current commit (or HEAD if not yet set)
$head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD');
OC_JSON::encodedPrint(array("head" => $head));

View File

@ -1,14 +0,0 @@
<?php
/**
* Copyright (c) 2011 Craig Roberts craig0990@googlemail.com
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
require_once('../../../lib/base.php');
OC_JSON::checkLoggedIn();
if(isset($_POST["file_versioning_head"])){
OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $_POST["file_versioning_head"]);
OC_JSON::success();
}else{
OC_JSON::error();
}

View File

@ -1,20 +0,0 @@
<?php
// Include required files
require_once('apps/files_versioning/versionstorage.php');
require_once('apps/files_versioning/versionwrapper.php');
// Register streamwrapper for versioned:// paths
stream_wrapper_register('versioned', 'OC_VersionStreamWrapper');
// Add an entry in the app list for versioning and backup
OC_App::register( array(
'order' => 10,
'id' => 'files_versioning',
'name' => 'Versioning and Backup' ));
// Include stylesheets for the settings page
OC_Util::addStyle( 'files_versioning', 'settings' );
OC_Util::addScript('files_versioning','settings');
// Register a settings section in the Admin > Personal page
OC_APP::registerPersonal('files_versioning','settings');

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<info>
<id>files_versioning</id>
<name>Versioning and Backup</name>
<licence>GPLv2</licence>
<author>Craig Roberts</author>
<require>3</require>
<description>Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind.</description>
<types>
<filesystem/>
</types>
</info>

View File

@ -1 +0,0 @@
1.0.0

View File

@ -1,3 +0,0 @@
#file_versioning_commit_chzn {
width: 15em;
}

View File

@ -1,25 +0,0 @@
$(document).ready(function(){
$('#file_versioning_head').chosen();
$.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) {
if (jsondata.head == 'HEAD') {
// Most recent commit, do nothing
} else {
$("#file_versioning_head").val(jsondata.head);
// Trigger the chosen update call
// See http://harvesthq.github.com/chosen/
$("#file_versioning_head").trigger("liszt:updated");
}
});
$('#file_versioning_head').change(function() {
var data = $(this).serialize();
$.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){
if(data == 'error'){
console.log('Saving new HEAD failed');
}
});
});
});

View File

@ -1,12 +0,0 @@
<?php
require_once('granite/git/blob.php');
require_once('granite/git/commit.php');
require_once('granite/git/repository.php');
require_once('granite/git/tag.php');
require_once('granite/git/tree.php');
require_once('granite/git/tree/node.php');
require_once('granite/git/object/index.php');
require_once('granite/git/object/raw.php');
require_once('granite/git/object/loose.php');
require_once('granite/git/object/packed.php');

View File

@ -1,34 +0,0 @@
<?php
// Get the full path to the repository folder (FIXME: hard-coded to 'Backup')
$path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data')
. DIRECTORY_SEPARATOR
. OC_User::getUser()
. DIRECTORY_SEPARATOR
. 'files'
. DIRECTORY_SEPARATOR
. 'Backup'
. DIRECTORY_SEPARATOR
. '.git'
. DIRECTORY_SEPARATOR;
$repository = new Granite\Git\Repository($path);
$commits = array();
// Fetch most recent 50 commits (FIXME - haven't tested this much)
$commit = $repository->head();
for ($i = 0; $i < 50; $i++) {
$commits[] = $commit;
$parents = $commit->parents();
if (count($parents) > 0) {
$parent = $parents[0];
} else {
break;
}
$commit = $repository->factory('commit', $parent);
}
$tmpl = new OC_Template( 'files_versioning', 'settings');
$tmpl->assign('commits', $commits);
return $tmpl->fetchPage();

View File

@ -1,12 +0,0 @@
<fieldset id="status_list" class="personalblock">
<strong>Versioning and Backup</strong><br>
<p><em>Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.</em></p>
<label class="bold">Backup Folder</label>
<select name="file_versioning_head" id="file_versioning_head">
<?php
foreach ($_['commits'] as $commit):
echo '<option value="' . $commit->sha() . '">' . $commit->message() . '</option>';
endforeach;
?>
</select>
</fieldset>

View File

@ -1,386 +0,0 @@
<?php
/**
* ownCloud file storage implementation for Git repositories
* @author Craig Roberts
* @copyright 2012 Craig Roberts craig0990@googlemail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
// Include Granite
require_once('lib_granite.php');
// Create a top-level 'Backup' directory if it does not already exist
$user = OC_User::getUser();
if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) {
OC_Filesystem::mkdir('/Backup');
OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD');
}
// Generate the repository path (currently using 'full' repositories, as opposed to bare ones)
$repo_path = DIRECTORY_SEPARATOR
. OC_User::getUser()
. DIRECTORY_SEPARATOR
. 'files'
. DIRECTORY_SEPARATOR
. 'Backup';
// Mount the 'Backup' folder using the versioned storage provider below
OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR);
class OC_Filestorage_Versioned extends OC_Filestorage {
/**
* Holds an instance of Granite\Git\Repository
*/
protected $repo;
/**
* Constructs a new OC_Filestorage_Versioned instance, expects an associative
* array with a `repo` key set to the path of the repository's `.git` folder
*
* @param array $parameters An array containing the key `repo` pointing to the
* repository path.
*/
public function __construct($parameters) {
// Get the full path to the repository folder
$path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data')
. $parameters['repo']
. DIRECTORY_SEPARATOR
. '.git'
. DIRECTORY_SEPARATOR;
try {
// Attempt to load the repository
$this->repo = new Granite\Git\Repository($path);
} catch (InvalidArgumentException $e) {
// $path is not a valid Git repository, we must create one
Granite\Git\Repository::init($path);
// Load the newly-initialised repository
$this->repo = new Granite\Git\Repository($path);
/**
* Create an initial commit with a README file
* FIXME: This functionality should be transferred to the Granite library
*/
$blob = new Granite\Git\Blob($this->repo->path());
$blob->content('Your Backup directory is now ready for use.');
// Create a new tree to hold the README file
$tree = $this->repo->factory('tree');
// Create a tree node to represent the README blob
$tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha());
$tree->nodes(array($tree_node->name() => $tree_node));
// Create an initial commit
$commit = new Granite\Git\Commit($this->repo->path());
$user_string = OC_User::getUser() . ' ' . time() . ' +0000';
$commit->author($user_string);
$commit->committer($user_string);
$commit->message('Initial commit');
$commit->tree($tree);
// Write it all to disk
$blob->write();
$tree->write();
$commit->write();
// Update the HEAD for the 'master' branch
$this->repo->head('master', $commit->sha());
}
// Update the class pointer to the HEAD
$head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD');
// Load the most recent commit if the preference is not set
if ($head == 'HEAD') {
$this->head = $this->repo->head()->sha();
} else {
$this->head = $head;
}
}
public function mkdir($path) {
if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) {
$this->head = $this->repo->head()->sha();
OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head);
return true;
}
return false;
}
public function rmdir($path) {
}
/**
* Returns a directory handle to the requested path, or FALSE on failure
*
* @param string $path The directory path to open
*
* @return boolean|resource A directory handle, or FALSE on failure
*/
public function opendir($path) {
return opendir("versioned:/{$this->repo->path()}$path#{$this->head}");
}
/**
* Returns TRUE if $path is a directory, or FALSE if not
*
* @param string $path The path to check
*
* @return boolean
*/
public function is_dir($path) {
return $this->filetype($path) == 'dir';
}
/**
* Returns TRUE if $path is a file, or FALSE if not
*
* @param string $path The path to check
*
* @return boolean
*/
public function is_file($path) {
return $this->filetype($path) == 'file';
}
public function stat($path)
{
return stat("versioned:/{$this->repo->path()}$path#{$this->head}");
}
/**
* Returns the strings 'dir' or 'file', depending on the type of $path
*
* @param string $path The path to check
*
* @return string Returns 'dir' if a directory, 'file' otherwise
*/
public function filetype($path) {
if ($path == "" || $path == "/") {
return 'dir';
} else {
if (substr($path, -1) == '/') {
$path = substr($path, 0, -1);
}
$node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path);
// Does it exist, or is it new?
if ($node == null) {
// New file
return 'file';
} else {
// Is it a tree?
try {
$this->repo->factory('tree', $node);
return 'dir';
} catch (InvalidArgumentException $e) {
// Nope, must be a blob
return 'file';
}
}
}
}
public function filesize($path) {
return filesize("versioned:/{$this->repo->path()}$path#{$this->head}");
}
/**
* Returns a boolean value representing whether $path is readable
*
* @param string $path The path to check
*(
* @return boolean Whether or not the path is readable
*/
public function is_readable($path) {
return true;
}
/**
* Returns a boolean value representing whether $path is writable
*
* @param string $path The path to check
*(
* @return boolean Whether or not the path is writable
*/
public function is_writable($path) {
$head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD');
if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) {
// Cannot modify previous commits
return false;
}
return true;
}
/**
* Returns a boolean value representing whether $path exists
*
* @param string $path The path to check
*(
* @return boolean Whether or not the path exists
*/
public function file_exists($path) {
return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}");
}
/**
* Returns an integer value representing the inode change time
* (NOT IMPLEMENTED)
*
* @param string $path The path to check
*(
* @return int Timestamp of the last inode change
*/
public function filectime($path) {
return -1;
}
/**
* Returns an integer value representing the file modification time
*
* @param string $path The path to check
*(
* @return int Timestamp of the last file modification
*/
public function filemtime($path) {
return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}");
}
public function file_get_contents($path) {
return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}");
}
public function file_put_contents($path, $data) {
$success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data);
if ($success !== false) {
// Update the HEAD in the preferences
OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha());
return $success;
}
return false;
}
public function unlink($path) {
}
public function rename($path1, $path2) {
}
public function copy($path1, $path2) {
}
public function fopen($path, $mode) {
return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode);
}
public function getMimeType($path) {
if ($this->filetype($path) == 'dir') {
return 'httpd/unix-directory';
} elseif ($this->filesize($path) == 0) {
// File's empty, returning text/plain allows opening in the web editor
return 'text/plain';
} else {
$finfo = new finfo(FILEINFO_MIME_TYPE);
/**
* We need to represent the repository path, the file path, and the
* revision, which can be simply achieved with a convention of using
* `.git` in the repository directory (bare or not) and the '#part'
* segment of a URL to specify the revision. For example
*
* versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo)
* versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo)
* versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID)
*/
$mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"));
return $mime;
}
}
/**
* Generates a hash based on the file contents
*
* @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.)
* @param string $path The file to be hashed
* @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise
*
* @return string Hashed string representing the file contents
*/
public function hash($type, $path, $raw) {
return hash($type, file_get_contents($path), $raw);
}
public function free_space($path) {
}
public function search($query) {
}
public function touch($path, $mtime=null) {
}
public function getLocalFile($path) {
}
/**
* Recursively searches a tree for a path, returning FALSE if is not found
* or an SHA-1 id if it is found.
*
* @param string $repo The repository containing the tree object
* @param string $tree The tree object to search
* @param string $path The path to search for (relative to the tree)
* @param int $depth The depth of the current search (for recursion)
*
* @return string|boolean The SHA-1 id of the sub-tree
*/
private function tree_search($repo, $tree, $path, $depth = 0)
{
$paths = array_values(explode(DIRECTORY_SEPARATOR, $path));
$current_path = $paths[$depth];
$nodes = $tree->nodes();
foreach ($nodes as $node) {
if ($node->name() == $current_path) {
if (count($paths)-1 == $depth) {
// Stop, found it
return $node->sha();
}
// Recurse if necessary
if ($node->isDirectory()) {
$tree = $this->repo->factory('tree', $node->sha());
return $this->tree_search($repo, $tree, $path, $depth + 1);
}
}
}
return false;
}
}

View File

@ -1,686 +0,0 @@
<?php
final class OC_VersionStreamWrapper {
/**
* Determines whether or not to log debug messages with `OC_Log::write()`
*/
private $debug = true;
/**
* The name of the ".empty" files created in new directories
*/
const EMPTYFILE = '.empty';
/**
* Stores the current position for `readdir()` etc. calls
*/
private $dir_position = 0;
/**
* Stores the current position for `fread()`, `fseek()` etc. calls
*/
private $file_position = 0;
/**
* Stores the current directory tree for `readdir()` etc. directory traversal
*/
private $tree;
/**
* Stores the current file for `fread()`, `fseek()`, etc. calls
*/
private $blob;
/**
* Stores the current commit for `fstat()`, `stat()`, etc. calls
*/
private $commit;
/**
* Stores the current path for `fwrite()`, `file_put_contents()` etc. calls
*/
private $path;
/**
* Close directory handle
*/
public function dir_closedir() {
unset($this->tree);
return true;
}
/**
* Open directory handle
*/
public function dir_opendir($path, $options) {
// Parse the URL into a repository directory, file path and commit ID
list($this->repo, $repo_file, $this->commit) = $this->parse_url($path);
if ($repo_file == '' || $repo_file == '/') {
// Set the tree property for the future `readdir()` etc. calls
$this->tree = array_values($this->commit->tree()->nodes());
return true;
} elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) {
// Something exists at this path, is it a directory though?
try {
$tree = $this->repo->factory(
'tree',
$this->tree_search($this->repo, $this->commit->tree(), $repo_file)
);
$this->tree = array_values($tree->nodes());
return true;
} catch (InvalidArgumentException $e) {
// Trying to call `opendir()` on a file, return false below
}
}
// Unable to find the directory, return false
return false;
}
/**
* Read entry from directory handle
*/
public function dir_readdir() {
return isset($this->tree[$this->dir_position])
? $this->tree[$this->dir_position++]->name()
: false;
}
/**
* Rewind directory handle
*/
public function dir_rewinddir() {
$this->dir_position = 0;
}
/**
* Create a directory
* Git doesn't track empty directories, so a ".empty" file is added instead
*/
public function mkdir($path, $mode, $options) {
// Parse the URL into a repository directory, file path and commit ID
list($this->repo, $repo_file, $this->commit) = $this->parse_url($path);
// Create an empty file for Git
$empty = new Granite\Git\Blob($this->repo->path());
$empty->content('');
$empty->write();
if (dirname($repo_file) == '.') {
// Adding a new directory to the root tree
$tree = $this->repo->head()->tree();
} else {
$tree = $this->repo->factory('tree', $this->tree_search(
$this->repo, $this->repo->head()->tree(), dirname($repo_file)
)
);
}
// Create our new tree, with our empty file
$dir = $this->repo->factory('tree');
$nodes = array();
$nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha());
$dir->nodes($nodes);
$dir->write();
// Add our new tree to its parent
$nodes = $tree->nodes();
$nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha());
$tree->nodes($nodes);
$tree->write();
// We need to recursively update each parent tree, since they are all
// hashed and the changes will cascade back up the chain
// So, we're currently at the bottom-most directory
$current_dir = dirname($repo_file);
$previous_tree = $tree;
if ($current_dir !== '.') {
do {
// Determine the parent directory
$previous_dir = $current_dir;
$current_dir = dirname($current_dir);
$current_tree = $current_dir !== '.'
? $this->repo->factory(
'tree', $this->tree_search(
$this->repo,
$this->repo->head()->tree(),
$current_dir
)
)
: $this->repo->head()->tree();
$current_nodes = $current_tree->nodes();
$current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node(
basename($previous_dir), '040000', $previous_tree->sha()
);
$current_tree->nodes($current_nodes);
$current_tree->write();
$previous_tree = $current_tree;
} while ($current_dir !== '.');
$tree = $previous_tree;
}
// Create a new commit to represent this write
$commit = $this->repo->factory('commit');
$username = OC_User::getUser();
$user_string = $username . ' ' . time() . ' +0000';
$commit->author($user_string);
$commit->committer($user_string);
$commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.');
$commit->parents(array($this->repo->head()->sha()));
$commit->tree($tree);
// Write it to disk
$commit->write();
// Update the HEAD for the 'master' branch
$this->repo->head('master', $commit->sha());
return true;
}
/**
* Renames a file or directory
*/
public function rename($path_from, $path_to) {
}
/**
* Removes a directory
*/
public function rmdir($path, $options) {
}
/**
* Retrieve the underlaying resource (NOT IMPLEMENTED)
*/
public function stream_cast($cast_as) {
return false;
}
/**
* Close a resource
*/
public function stream_close() {
unset($this->blob);
return true;
}
/**
* Tests for end-of-file on a file pointer
*/
public function stream_eof() {
return !($this->file_position < strlen($this->blob));
}
/**
* Flushes the output (NOT IMPLEMENTED)
*/
public function stream_flush() {
return false;
}
/**
* Advisory file locking (NOT IMPLEMENTED)
*/
public function stream_lock($operation) {
return false;
}
/**
* Change stream options (NOT IMPLEMENTED)
* Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()`
*/
public function stream_metadata($path, $option, $var) {
return false;
}
/**
* Opens file or URL
*/
public function stream_open($path, $mode, $options, &$opened_path) {
// Store the path, so we can use it later in `stream_write()` if necessary
$this->path = $path;
// Parse the URL into a repository directory, file path and commit ID
list($this->repo, $repo_file, $this->commit) = $this->parse_url($path);
$file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file);
if ($file !== false) {
try {
$this->blob = $this->repo->factory('blob', $file)->content();
return true;
} catch (InvalidArgumentException $e) {
// Trying to open a directory, return false below
}
} elseif ($mode !== 'r') {
// All other modes allow opening for reading and writing, clearly
// some 'write' files may not exist yet...
return true;
}
// File could not be found or is not actually a file
return false;
}
/**
* Read from stream
*/
public function stream_read($count) {
// Fetch the remaining set of bytes
$bytes = substr($this->blob, $this->file_position, $count);
// If EOF or empty string, return false
if ($bytes == '' || $bytes == false) {
return false;
}
// If $count does not extend past EOF, add $count to stream offset
if ($this->file_position + $count < strlen($this->blob)) {
$this->file_position += $count;
} else {
// Otherwise return all remaining bytes
$this->file_position = strlen($this->blob);
}
return $bytes;
}
/**
* Seeks to specific location in a stream
*/
public function stream_seek($offset, $whence = SEEK_SET) {
$new_offset = false;
switch ($whence)
{
case SEEK_SET:
$new_offset = $offset;
break;
case SEEK_CUR:
$new_offset = $this->file_position += $offset;
break;
case SEEK_END:
$new_offset = strlen($this->blob) + $offset;
break;
}
$this->file_position = $offset;
return ($new_offset !== false);
}
/**
* Change stream options (NOT IMPLEMENTED)
*/
public function stream_set_option($option, $arg1, $arg2) {
return false;
}
/**
* Retrieve information about a file resource (NOT IMPLEMENTED)
*/
public function stream_stat() {
}
/**
* Retrieve the current position of a stream
*/
public function stream_tell() {
return $this->file_position;
}
/**
* Truncate stream
*/
public function stream_truncate($new_size) {
}
/**
* Write to stream
* FIXME: Could use heavy refactoring
*/
public function stream_write($data) {
/**
* FIXME: This also needs to be added to Granite, in the form of `add()`,
* `rm()` and `commit()` calls
*/
// Parse the URL into a repository directory, file path and commit ID
list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path);
$node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file);
if ($node !== false) {
// File already exists, attempting modification of existing tree
try {
$this->repo->factory('blob', $node);
// Create our new blob with the provided $data
$blob = $this->repo->factory('blob');
$blob->content($data);
$blob->write();
// We know the tree exists, so strip the filename from the path and
// find it...
if (dirname($repo_file) == '.' || dirname($repo_file) == '') {
// Root directory
$tree = $this->repo->head()->tree();
} else {
// Sub-directory
$tree = $this->repo->factory('tree', $this->tree_search(
$this->repo,
$this->repo->head()->tree(),
dirname($repo_file)
)
);
}
// Replace the old blob with our newly modified one
$tree_nodes = $tree->nodes();
$tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node(
basename($repo_file), '100644', $blob->sha()
);
$tree->nodes($tree_nodes);
$tree->write();
// We need to recursively update each parent tree, since they are all
// hashed and the changes will cascade back up the chain
// So, we're currently at the bottom-most directory
$current_dir = dirname($repo_file);
$previous_tree = $tree;
if ($current_dir !== '.') {
do {
// Determine the parent directory
$previous_dir = $current_dir;
$current_dir = dirname($current_dir);
$current_tree = $current_dir !== '.'
? $this->repo->factory(
'tree', $this->tree_search(
$this->repo,
$this->repo->head()->tree(),
$current_dir
)
)
: $this->repo->head()->tree();
$current_nodes = $current_tree->nodes();
$current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node(
basename($previous_dir), '040000', $previous_tree->sha()
);
$current_tree->nodes($current_nodes);
$current_tree->write();
$previous_tree = $current_tree;
} while ($current_dir !== '.');
}
// Create a new commit to represent this write
$commit = $this->repo->factory('commit');
$username = OC_User::getUser();
$user_string = $username . ' ' . time() . ' +0000';
$commit->author($user_string);
$commit->committer($user_string);
$commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.');
$commit->parents(array($this->repo->head()->sha()));
$commit->tree($previous_tree);
// Write it to disk
$commit->write();
// Update the HEAD for the 'master' branch
$this->repo->head('master', $commit->sha());
// If we made it this far, write was successful - update the stream
// position and return the number of bytes written
$this->file_position += strlen($data);
return strlen($data);
} catch (InvalidArgumentException $e) {
// Attempting to write to a directory or other error, fail
return 0;
}
} else {
// File does not exist, needs to be created
// Create our new blob with the provided $data
$blob = $this->repo->factory('blob');
$blob->content($data);
$blob->write();
if (dirname($repo_file) == '.') {
// Trying to add a new file to the root tree, nice and easy
$tree = $this->repo->head()->tree();
$tree_nodes = $tree->nodes();
$tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node(
basename($repo_file), '100644', $blob->sha()
);
$tree->nodes($tree_nodes);
$tree->write();
} else {
// Trying to add a new file to a subdirectory, try and find it
$tree = $this->repo->factory('tree', $this->tree_search(
$this->repo, $this->repo->head()->tree(), dirname($repo_file)
)
);
// Add the blob to the tree
$nodes = $tree->nodes();
$nodes[basename($repo_file)] = new Granite\Git\Tree\Node(
basename($repo_file), '100644', $blob->sha()
);
$tree->nodes($nodes);
$tree->write();
// We need to recursively update each parent tree, since they are all
// hashed and the changes will cascade back up the chain
// So, we're currently at the bottom-most directory
$current_dir = dirname($repo_file);
$previous_tree = $tree;
if ($current_dir !== '.') {
do {
// Determine the parent directory
$previous_dir = $current_dir;
$current_dir = dirname($current_dir);
$current_tree = $current_dir !== '.'
? $this->repo->factory(
'tree', $this->tree_search(
$this->repo,
$this->repo->head()->tree(),
$current_dir
)
)
: $this->repo->head()->tree();
$current_nodes = $current_tree->nodes();
$current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node(
basename($previous_dir), '040000', $previous_tree->sha()
);
$current_tree->nodes($current_nodes);
$current_tree->write();
$previous_tree = $current_tree;
} while ($current_dir !== '.');
$tree = $previous_tree;
}
}
// Create a new commit to represent this write
$commit = $this->repo->factory('commit');
$username = OC_User::getUser();
$user_string = $username . ' ' . time() . ' +0000';
$commit->author($user_string);
$commit->committer($user_string);
$commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.');
$commit->parents(array($this->repo->head()->sha()));
$commit->tree($tree); // Top-level tree (NOT the newly modified tree)
// Write it to disk
$commit->write();
// Update the HEAD for the 'master' branch
$this->repo->head('master', $commit->sha());
// If we made it this far, write was successful - update the stream
// position and return the number of bytes written
$this->file_position += strlen($data);
return strlen($data);
}
// Write failed
return 0;
}
/**
* Delete a file
*/
public function unlink($path) {
}
/**
* Retrieve information about a file
*/
public function url_stat($path, $flags) {
// Parse the URL into a repository directory, file path and commit ID
list($this->repo, $repo_file, $this->commit) = $this->parse_url($path);
$node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file);
if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) {
// A new file - no information available
$size = 0;
$mtime = -1;
} else {
// Is it a directory?
try {
$this->repo->factory('tree', $node);
$size = 4096; // FIXME
} catch (InvalidArgumentException $e) {
// Must be a file
$size = strlen(file_get_contents($path));
}
// Parse the timestamp from the commit message
preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches);
$mtime = $matches[0];
}
$stat["dev"] = "";
$stat["ino"] = "";
$stat["mode"] = "";
$stat["nlink"] = "";
$stat["uid"] = "";
$stat["gid"] = "";
$stat["rdev"] = "";
$stat["size"] = $size;
$stat["atime"] = $mtime;
$stat["mtime"] = $mtime;
$stat["ctime"] = $mtime;
$stat["blksize"] = "";
$stat["blocks"] = "";
return $stat;
}
/**
* Debug function for development purposes
*/
private function debug($message, $level = OC_Log::DEBUG)
{
if ($this->debug) {
OC_Log::write('files_versioning', $message, $level);
}
}
/**
* Parses a URL of the form:
* `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id`
* FIXME: Will throw an InvalidArgumentException if $path is invaid
*
* @param string $path The path to parse
*
* @return array An array containing an instance of Granite\Git\Repository,
* the file path, and an instance of Granite\Git\Commit
* @throws InvalidArgumentException If the repository cannot be loaded
*/
private function parse_url($path)
{
preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches);
// Load up the repo
$repo = new \Granite\Git\Repository($matches[1]);
// Parse the filename (stripping any trailing slashes)
$repo_file = $matches[2];
if (substr($repo_file, -1) == '/') {
$repo_file = substr($repo_file, 0, -1);
}
// Default to HEAD if no commit is provided
$repo_commit = isset($matches[4])
? $matches[4]
: $repo->head()->sha();
// Load the relevant commit
$commit = $repo->factory('commit', $repo_commit);
return array($repo, $repo_file, $commit);
}
/**
* Recursively searches a tree for a path, returning FALSE if is not found
* or an SHA-1 id if it is found.
*
* @param string $repo The repository containing the tree object
* @param string $tree The tree object to search
* @param string $path The path to search for (relative to the tree)
* @param int $depth The depth of the current search (for recursion)
*
* @return string|boolean The SHA-1 id of the sub-tree
*/
private function tree_search($repo, $tree, $path, $depth = 0)
{
$paths = array_values(explode(DIRECTORY_SEPARATOR, $path));
$current_path = $paths[$depth];
$nodes = $tree->nodes();
foreach ($nodes as $node) {
if ($node->name() == $current_path) {
if (count($paths)-1 == $depth) {
// Stop, found it
return $node->sha();
}
// Recurse if necessary
if ($node->isDirectory()) {
$tree = $this->repo->factory('tree', $node->sha());
return $this->tree_search($repo, $tree, $path, $depth + 1);
}
}
}
return false;
}
}