fix merge conflicts

This commit is contained in:
Georg Ehrke 2012-04-24 21:59:56 +02:00
commit f17eea506a
42 changed files with 589 additions and 3039 deletions

View File

@ -1,162 +0,0 @@
<?php
/**
* Blob - provides a Git blob object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
use \Granite\Git\Object\Raw as Raw;
use \InvalidArgumentException as InvalidArgumentException;
use \finfo as finfo;
/**
* **Granite\Git\Blob** represents the raw content of an object in a Git repository,
* typically a **file**. This class provides methods related to the handling of
* blob content, mimetypes, sizes and write support.
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Blob
{
/**
* Stores the SHA-1 id of the object requested; accessed through the `sha()`
* method where it is recalculated based on the blob content.
*/
private $sha = null;
/**
* The raw binary string of the file contents.
*/
private $content = "";
/**
* The path to the repository location.
*/
private $path;
/**
* Fetches a raw Git object and parses the result. Throws an
* InvalidArgumentException if the object is not of the correct type,
* or cannot be found.
*
* @param string $path The path to the repository root.
* @param string $sha The SHA-1 id of the requested object, or `null` if
* creating a new blob object.
*
* @throws InvalidArgumentException If the SHA-1 id provided is not a blob.
*/
public function __construct($path, $sha = NULL)
{
$this->path = $path;
if ($sha !== NULL) {
$this->sha = $sha;
$object = Raw::factory($path, $sha);
if ($object->type() !== Raw::OBJ_BLOB) {
throw new InvalidArgumentException(
"The object $sha is not a blob, type is {$object->type()}"
);
}
$this->content = $object->content();
unset($object);
}
}
/**
* Sets or returns the raw file content, depending whether the parameter is
* provided.
*
* @param string $content The object content to set, or `null` if requesting the
* current content.
*
* @return string The raw binary string of the file contents.
*/
public function content($content = NULL)
{
if ($content == NULL) {
return $this->content;
}
$this->content = $content;
}
/**
* Returns the size of the file content in bytes, equivalent to
* `strlen($blob->content())`.
*
* @return int The size of the object in bytes.
*/
public function size()
{
return strlen($this->content);
}
/**
* Updates and returns the SHA-1 id of the object, based on it's contents.
*
* @return int The SHA-1 id of the object.
*/
public function sha()
{
$sha = hash_init('sha1');
$header = 'blob ' . strlen($this->content) . "\0";
hash_update($sha, $header);
hash_update($sha, $this->content);
$this->sha = hash_final($sha);
return $this->sha;
}
/**
* Returns the mimetype of the object, using `finfo()` to determine the mimetype
* of the string.
*
* @return string The object mimetype.
* @see http://php.net/manual/en/function.finfo-open.php
*/
public function mimetype()
{
$finfo = new finfo(FILEINFO_MIME);
return $finfo->buffer($this->content);
}
/**
* Encode and compress the object content, saving it to a 'loose' file.
*
* @return boolean True on success, false on failure.
*/
public function write()
{
$sha = $this->sha(TRUE);
$path = $this->path
. 'objects'
. DIRECTORY_SEPARATOR
. substr($sha, 0, 2)
. DIRECTORY_SEPARATOR
. substr($sha, 2);
// FIXME: currently writes loose objects only
if (file_exists($path)) {
return FALSE;
}
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, TRUE);
}
$loose = fopen($path, 'wb');
$data = 'blob ' . strlen($this->content) . "\0" . $this->content;
$write = fwrite($loose, gzcompress($data));
fclose($loose);
return ($write !== FALSE);
}
}

View File

@ -1,232 +0,0 @@
<?php
/**
* Commit - provides a 'commit' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
use \Granite\Git\Object\Raw as Raw;
use \InvalidArgumentException as InvalidArgumentException;
/**
* Commit represents a full commit object
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Commit
{
/**
* The path to the repository root
*/
private $path;
/**
* The SHA-1 id of the requested commit
*/
private $sha;
/**
* The size of the commit in bytes
*/
private $size;
/**
* The commit message
*/
private $message;
/**
* The full committer string
*/
private $committer;
/**
* The full author string
*/
private $author;
/**
* The SHA-1 ids of the parent commits
*/
private $parents = array();
/**
* Fetches a raw Git object and parses the result. Throws an
* InvalidArgumentException if the object is not of the correct type,
* or cannot be found.
*
* @param string $path The path to the repository root
* @param string $sha The SHA-1 id of the requested object
*
* @throws InvalidArgumentException
*/
public function __construct($path, $sha = NULL)
{
$this->path = $path;
if ($sha !== NULL) {
$this->sha = $sha;
$object = Raw::factory($path, $sha);
$this->size = $object->size();
if ($object->type() !== Raw::OBJ_COMMIT) {
throw new InvalidArgumentException(
"The object $sha is not a commit, type is " . $object->type()
);
}
// Parse headers and commit message (delimited with "\n\n")
list($headers, $this->message) = explode("\n\n", $object->content(), 2);
$headers = explode("\n", $headers);
foreach ($headers as $header) {
list($header, $value) = explode(' ', $header, 2);
if ($header == 'parent') {
$this->parents[] = $value;
} else {
$this->$header = $value;
}
}
$this->tree = new Tree($this->path, $this->tree);
}
}
/**
* Returns the message stored in the commit
*
* @return string The commit message
*/
public function message($message = NULL)
{
if ($message !== NULL) {
$this->message = $message;
return $this;
}
return $this->message;
}
/**
* Returns the commiter string
*
* @return string The committer string
*/
public function committer($committer = NULL)
{
if ($committer !== NULL) {
$this->committer = $committer;
return $this;
}
return $this->committer;
}
/**
* Returns the author string
*
* @return string The author string
*/
public function author($author = NULL)
{
if ($author !== NULL) {
$this->author = $author;
return $this;
}
return $this->author;
}
/**
* Returns the parents of the commit, or an empty array if none
*
* @return array The parents of the commit
*/
public function parents($parents = NULL)
{
if ($parents !== NULL) {
$this->parents = $parents;
return $this;
}
return $this->parents;
}
/**
* Returns a tree object associated with the commit
*
* @return Tree
*/
public function tree(Tree $tree = NULL)
{
if ($tree !== NULL) {
$this->tree = $tree;
return $this;
}
return $this->tree;
}
/**
* Returns the size of the commit in bytes (Git header + data)
*
* @return int
*/
public function size()
{
return $this->size;
}
/**
* Returns the size of the commit in bytes (Git header + data)
*
* @return int
*/
public function sha()
{
$this->sha = hash('sha1', $this->_raw());
return $this->sha;
}
public function write()
{
$sha = $this->sha();
$path = $this->path
. 'objects'
. DIRECTORY_SEPARATOR
. substr($sha, 0, 2)
. DIRECTORY_SEPARATOR
. substr($sha, 2);
// FIXME: currently writes loose objects only
if (file_exists($path)) {
return FALSE;
}
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, TRUE);
}
$loose = fopen($path, 'wb');
$data = $this->_raw();
$write = fwrite($loose, gzcompress($data));
fclose($loose);
return ($write !== FALSE);
}
public function _raw()
{
$data = 'tree ' . $this->tree->sha() . "\n";
foreach ($this->parents as $parent)
{
$data .= "parent $parent\n";
}
$data .= 'author ' . $this->author . "\n";
$data .= 'committer ' . $this->committer . "\n\n";
$data .= $this->message;
$data = 'commit ' . strlen($data) . "\0" . $data;
return $data;
}
}

View File

@ -1,210 +0,0 @@
<?php
/**
* Index - provides an 'index' object for packfile indexes
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git\Object;
use \UnexpectedValueException as UnexpectedValueException;
/**
* Index represents a packfile index
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT License
* @link http://craig0990.github.com/Granite/
*/
class Index
{
const INDEX_MAGIC = "\377tOc";
/**
* The full path to the packfile index
*/
private $path;
/**
* The offset at which the fanout begins, version 2+ indexes have a 2-byte header
*/
private $offset = 8;
/**
* The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to
* total 24 bytes, version 2+ stores offsets separately
*/
private $size = 20;
/**
* The version of the index file format, versions 1 and 2 are in use and
* currently supported
*/
private $version;
/**
* Fetches a raw Git object and parses the result
*
* @param string $path The path to the repository root
* @param string $packname The name of the packfile index to read
*/
public function __construct($path, $packname)
{
$this->path = $path
. 'objects'
. DIRECTORY_SEPARATOR
. 'pack'
. DIRECTORY_SEPARATOR
. 'pack-' . $packname . '.idx';
$this->version = $this->_readVersion();
if ($this->version !== 1 && $this->version !== 2) {
throw new UnexpectedValueException(
"Unsupported index version (version $version)"
);
}
if ($this->version == 1) {
$this->offset = 0; // Version 1 index has no header/version
$this->size = 24; // Offsets + SHA-1 ids are stored together
}
}
/**
* Returns the offset of the object stored in the index
*
* @param string $sha The SHA-1 id of the object being requested
*
* @return int The offset of the object in the packfile
*/
public function find($sha)
{
$index = fopen($this->path, 'rb');
$offset = false; // Offset for object in packfile not found by default
// Read the fanout to skip to the start char in the sorted SHA-1 list
list($start, $after) = $this->_readFanout($index, $sha);
if ($start == $after) {
fclose($index);
return false; // Object is apparently located in a 0-length section
}
// Seek $offset + 255 4-byte fanout entries and read 256th entry
fseek($index, $this->offset + 4 * 255);
$totalObjects = $this->_uint32($index);
// Look up the SHA-1 id of the object
// TODO: Binary search
fseek($index, $this->offset + 1024 + $this->size * $start);
for ($i = $start; $i < $after; $i++) {
if ($this->version == 1) {
$offset = $this->_uint32($index);
}
$name = fread($index, 20);
if ($name == pack('H40', $sha)) {
break; // Found it
}
}
if ($i == $after) {
fclose($index);
return false; // Scanned entire section, couldn't find it
}
if ($this->version == 2) {
// Jump to the offset location and read it
fseek($index, 1032 + 24 * $totalObjects + 4 * $i);
$offset = $this->_uint32($index);
if ($offset & 0x80000000) {
// Offset is a 64-bit integer; packfile is larger than 2GB
fclose($index);
throw new UnexpectedValueException(
"Packfile larger than 2GB, currently unsupported"
);
}
}
fclose($index);
return $offset;
}
/**
* Converts a binary string into a 32-bit unsigned integer
*
* @param handle $file Binary string to convert
*
* @return int Integer value
*/
private function _uint32($file)
{
$val = unpack('Nx', fread($file, 4));
return $val['x'];
}
/**
* Reads the fanout for a particular SHA-1 id
*
* Largely modified from Glip, with some reference to Grit - largely because I
* can't see how to re-implement this in PHP
*
* @param handle $file File handle to the index file
* @param string $sha The SHA-1 id to search for
* @param int $offset The offset at which the fanout begins
*
* @return array Array containing integer 'start' and
* 'past-the-end' locations
*/
private function _readFanout($file, $sha)
{
$sha = pack('H40', $sha);
fseek($file, $this->offset);
if ($sha{0} == "\00") {
/**
* First character is 0, read first fanout entry to provide
* 'past-the-end' location (since first fanout entry provides start
* point for '1'-prefixed SHA-1 ids)
*/
$start = 0;
fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in
$after = $this->_uint32($file);
} else {
/**
* Take ASCII value of first character, minus one to get the fanout
* position of the offset (minus one because the fanout does not
* contain an entry for "\00"), multiplied by four bytes per entry
*/
fseek($file, $this->offset + (ord($sha{0}) - 1) * 4);
$start = $this->_uint32($file);
$after = $this->_uint32($file);
}
return array($start, $after);
}
/**
* Returns the version number of the index file, or 1 if there is no version
* information
*
* @return int
*/
private function _readVersion()
{
$file = fopen($this->path, 'rb');
$magic = fread($file, 4);
$version = $this->_uint32($file);
if ($magic !== self::INDEX_MAGIC) {
$version = 1;
}
fclose($file);
return $version;
}
}

View File

@ -1,81 +0,0 @@
<?php
/**
* Loose - provides a 'loose object' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git\Object;
use \UnexpectedValueException as UnexpectedValueException;
/**
* Loose represents a loose object in the Git repository
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Loose extends Raw
{
/**
* Reads an object from a loose object file based on the SHA-1 id
*
* @param string $path The path to the repository root
* @param string $sha The SHA-1 id of the requested object
*
* @throws UnexpectedValueException If the type is not 'commit', 'tree',
* 'tag' or 'blob'
*/
public function __construct($path, $sha)
{
$this->sha = $sha;
$loose_path = $path
. 'objects/'
. substr($sha, 0, 2)
. '/'
. substr($sha, 2);
if (!file_exists($loose_path)) {
throw new InvalidArgumentException("Cannot open loose object file for $sha");
}
$raw = gzuncompress(file_get_contents($loose_path));
$data = explode("\0", $raw, 2);
$header = $data[0];
$this->content = $data[1];
list($this->type, $this->size) = explode(' ', $header);
switch ($this->type) {
case 'commit':
$this->type = Raw::OBJ_COMMIT;
break;
case 'tree':
$this->type = Raw::OBJ_TREE;
break;
case 'blob':
$this->type = Raw::OBJ_BLOB;
break;
case 'tag':
$this->type = Raw::OBJ_TAG;
break;
default:
throw new UnexpectedValueException(
"Unexpected type '{$this->type}'"
);
break;
}
}
}

View File

@ -1,304 +0,0 @@
<?php
/**
* Packed - provides a 'packed object' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git\Object;
use \UnexpectedValueException as UnexpectedValueException;
/**
* Packed represents a packed object in the Git repository
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Packed extends Raw
{
/**
* The name of the packfile being read
*/
private $_packfile;
/**
* Added to the object size to make a 'best-guess' effort at how much compressed
* data to read - should be reimplemented, ideally with streams.
*/
const OBJ_PADDING = 512;
/**
* Reads the object data from the compressed data at $offset in $packfile
*
* @param string $packfile The path to the packfile
* @param int $offset The offset of the object data
*/
public function __construct($packfile, $offset)
{
$this->_packfile = $packfile;
list($this->type, $this->size, $this->content)
= $this->_readPackedObject($offset);
}
/**
* Reads the object data at $this->_offset
*
* @param int $offset Offset of the object header
*
* @return array Containing the type, size and object data
*/
private function _readPackedObject($offset)
{
$file = fopen($this->_packfile, 'rb');
fseek($file, $offset);
// Read the type and uncompressed size from the object header
list($type, $size) = $this->_readHeader($file, $offset);
$object_offset = ftell($file);
if ($type == self::OBJ_OFS_DELTA || $type == self::OBJ_REF_DELTA) {
return $this->_unpackDeltified(
$file, $offset, $object_offset, $type, $size
);
}
$content = gzuncompress(fread($file, $size + self::OBJ_PADDING), $size);
return array($type, $size, $content);
}
/**
* Reads a packed object header, returning the type and the size. For more
* detailed information, refer to the @see tag.
*
* From the @see tag: "Each byte is really 7 bits of data, with the first bit
* being used to say if that hunk is the last one or not before the data starts.
* If the first bit is a 1, you will read another byte, otherwise the data starts
* next. The first 3 bits in the first byte specifies the type of data..."
*
* @param handle $file File handle to read
* @param int $offset Offset of the object header
*
* @return array Containing the type and the size
* @see http://book.git-scm.com/7_the_packfile.html
*/
private function _readHeader($file, $offset)
{
// Read the object header byte-by-byte
fseek($file, $offset);
$byte = ord(fgetc($file));
/**
* Bit-shift right by four, then ignore the first bit with a bitwise AND
* This gives us the object type in binary:
* 001 commit self::OBJ_COMMIT
* 010 tree self::OBJ_TREE
* 011 blob self::OBJ_BLOB
* 100 tag self::OBJ_TAG
* 110 offset delta self::OBJ_OFS_DELTA
* 111 ref delta self::OBJ_REF_DELTA
*
* (000 is undefined, 101 is not currently in use)
* See http://book.git-scm.com/7_the_packfile.html for details
*/
$type = ($byte >> 4) & 0x07;
// Read the last four bits of the first byte, used to find the size
$size = $byte & 0x0F;
/**
* $shift initially set to four, since we use the last four bits of the first
* byte
*
* $byte & 0x80 checks the initial bit is set to 1 (i.e. keep reading data)
*
* Finally, $shift is incremented by seven for each consecutive byte (because
* we ignore the initial bit)
*/
for ($shift = 4; $byte & 0x80; $shift += 7) {
$byte = ord(fgetc($file));
/**
* The size is ANDed against 0x7F to strip the initial bit, then
* bitshifted by left $shift (4 or 7, depending on whether it's the
* initial byte) and ORed against the existing binary $size. This
* continuously increments the $size variable.
*/
$size |= (($byte & 0x7F) << $shift);
}
return array($type, $size);
}
/**
* Unpacks a deltified object located at $offset in $file
*
* @param handle $file File handle to read
* @param int $offset Offset of the object data
* @param int $object_offset Offset of the object data, past the header
* @param int $type The object type, either OBJ_REF_DELTA
or OBJ_OFS_DELTA
* @param int $size The expected size of the uncompressed data
*
* @return array Containing the type, size and object data
*/
private function _unpackDeltified($file, $offset, $object_offset, $type, $size)
{
fseek($file, $object_offset);
if ($type == self::OBJ_REF_DELTA) {
$base_sha = bin2hex(fread($file, 20));
$path = substr($this->_packfile, 0, strpos($this->_packfile, '.git')+5);
$base = Raw::factory($path, $base_sha);
$type = $base->type();
$base = $base->content();
$delta = gzuncompress(
fread($file, $size + self::OBJ_PADDING), $size
);
$content = $this->_applyDelta($base, $delta);
} elseif ($type == self::OBJ_OFS_DELTA) {
// 20 = maximum varint size according to Glip
$data = fread($file, $size + self::OBJ_PADDING + 20);
list($base_offset, $length) = $this->_bigEndianNumber($data);
$delta = gzuncompress(substr($data, $length), $size);
unset($data);
$base_offset = $offset - $base_offset;
list($type, $size, $base) = $this->_readPackedObject($base_offset);
$content = $this->_applyDelta($base, $delta);
} else {
throw new UnexpectedValueException(
"Unknown type $type for deltified object"
);
}
return array($type, strlen($content), $content);
}
/**
* Applies the $delta byte-sequence to $base and returns the
* resultant binary string.
*
* This code is modified from Grit (see below), the Ruby
* implementation used for GitHub under an MIT license.
*
* @param string $base The base string for the delta to be applied to
* @param string $delta The delta string to apply
*
* @return string The patched binary string
* @see
* https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb
*/
private function _applyDelta($base, $delta)
{
$pos = 0;
$src_size = $this->_varint($delta, $pos);
$dst_size = $this->_varint($delta, $pos);
if ($src_size !== strlen($base)) {
throw new UnexpectedValueException(
'Expected base delta size ' . strlen($base) . ' does not match the expected '
. "value $src_size"
);
}
$dest = "";
while ($pos < strlen($delta)) {
$byte = ord($delta{$pos++});
if ($byte & 0x80) {
/* copy a part of $base */
$offset = 0;
if ($byte & 0x01) $offset = ord($delta{$pos++});
if ($byte & 0x02) $offset |= ord($delta{$pos++}) << 8;
if ($byte & 0x04) $offset |= ord($delta{$pos++}) << 16;
if ($byte & 0x08) $offset |= ord($delta{$pos++}) << 24;
$length = 0;
if ($byte & 0x10) $length = ord($delta{$pos++});
if ($byte & 0x20) $length |= ord($delta{$pos++}) << 8;
if ($byte & 0x40) $length |= ord($delta{$pos++}) << 16;
if ($length == 0) $length = 0x10000;
$dest .= substr($base, $offset, $length);
} else {
/* take the next $byte bytes as they are */
$dest .= substr($delta, $pos, $byte);
$pos += $byte;
}
}
if (strlen($dest) !== $dst_size) {
throw new UnexpectedValueException(
"Deltified string expected to be $dst_size bytes, but actually "
. strlen($dest) . ' bytes'
);
}
return $dest;
}
/**
* Parse a Git varint (variable-length integer). Used in the `_applyDelta()`
* method to read the delta header.
*
* @param string $string The string to parse
* @param int &$pos The position in the string to read from
*
* @return int The integer value
*/
private function _varint($string, &$pos = 0)
{
$varint = 0;
$bitmask = 0x80;
for ($i = 0; $bitmask & 0x80; $i += 7) {
$bitmask = ord($string{$pos++});
$varint |= (($bitmask & 0x7F) << $i);
}
return $varint;
}
/**
* Decodes a big endian modified base 128 number (refer to @see tag); this only
* appears to be used in one place, the offset delta in packfiles. The offset
* is the number of bytes to seek back from the start of the delta object to find
* the base object.
*
* This code has been implemented using the C code given in the @see tag below.
*
* @param string &$data The data to read from and decode the number
*
* @return Array Containing the base offset (number of bytes to seek back) and
* the length to use when reading the delta
* @see http://git.rsbx.net/Documents/Git_Data_Formats.txt
*/
private function _bigEndianNumber(&$data)
{
$i = 0;
$byte = ord($data{$i++});
$number = $byte & 0x7F;
while ($byte & 0x80) {
$byte = ord($data{$i++});
$number = (($number + 1) << 7) | ($byte & 0x7F);
}
return array($number, $i);
}
}

View File

@ -1,153 +0,0 @@
<?php
/**
* Raw - provides a raw Git object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git\Object;
use \InvalidArgumentException as InvalidArgumentException;
/**
* Raw represents a raw Git object, using Index to locate
* packed objects.
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Raw
{
/**
* Integer values for Git objects
* @see http://book.git-scm.com/7_the_packfile.html
*/
const OBJ_COMMIT = 1;
const OBJ_TREE = 2;
const OBJ_BLOB = 3;
const OBJ_TAG = 4;
const OBJ_OFS_DELTA = 6;
const OBJ_REF_DELTA = 7;
/**
* The SHA-1 id of the requested object
*/
protected $sha;
/**
* The type of the requested object (see class constants)
*/
protected $type;
/**
* The binary string content of the requested object
*/
protected $content;
/**
* Returns an instance of a raw Git object
*
* @param string $path The path to the repository root
* @param string $sha The SHA-1 id of the requested object
*
* @return Packed|Loose
*/
public static function factory($path, $sha)
{
$loose_path = $path
. 'objects/'
. substr($sha, 0, 2)
. '/'
. substr($sha, 2);
if (file_exists($loose_path)) {
return new Loose($path, $sha);
} else {
return self::_findPackedObject($path, $sha);
}
}
/**
* Returns the raw content of the Git object requested
*
* @return string Raw object content
*/
public function content()
{
return $this->content;
}
/**
* Returns the size of the Git object
*
* @return int The size of the object in bytes
*/
public function size()
{
return strlen($this->content);
}
/**
* Returns the type of the object as either commit, tag, blob or tree
*
* @return string The object type
*/
public function type()
{
return $this->type;
}
/**
* Searches a packfile for the SHA id and reads the object from the packfile
*
* @param string $path The path to the repository
* @param string $sha The SHA-1 id of the object being requested
*
* @throws \InvalidArgumentException
* @return array An array containing the type, size and object data
*/
private static function _findPackedObject($path, $sha)
{
$packfiles = glob(
$path
. 'objects'
. DIRECTORY_SEPARATOR
. 'pack'
. DIRECTORY_SEPARATOR
. 'pack-*.pack'
);
$offset = false;
foreach ($packfiles as $packfile) {
$packname = substr(basename($packfile, '.pack'), 5);
$idx = new Index($path, $packname);
$offset = $idx->find($sha);
if ($offset !== false) {
break; // Found it
}
}
if ($offset == false) {
throw new InvalidArgumentException("Could not find packed object $sha");
}
$packname = $path
. 'objects'
. DIRECTORY_SEPARATOR
. 'pack'
. DIRECTORY_SEPARATOR
. 'pack-' . $packname . '.pack';
$object = new Packed($packname, $offset);
return $object;
}
}
?>

View File

@ -1,293 +0,0 @@
<?php
/**
* Repository - provides a 'repository' object with a set of helper methods
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
use \InvalidArgumentException as InvalidArgumentException;
use \UnexpectedValueException as UnexpectedValueException;
/**
* Repository represents a Git repository, providing a variety of methods for
* fetching objects from SHA-1 ids or the tip of a branch with `head()`
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Repository
{
/**
* The path to the repository root
*/
private $_path;
/**
* The indexed version of a commit, ready to write with `commit()`
*/
private $idx_commit;
/**
* The indexed version of a tree, modified to with `add()` and `remove()`
*/
private $idx_tree;
/**
* Sets the repository path
*
* @param string $path The path to the repository root (i.e. /repo/.git/)
*/
public function __construct($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException("Unable to find directory $path");
} elseif (!is_readable($path)) {
throw new InvalidArgumentException("Unable to read directory $path");
} elseif (!is_dir($path . DIRECTORY_SEPARATOR . 'objects')
|| !is_dir($path . DIRECTORY_SEPARATOR . 'refs')
) {
throw new UnexpectedValueException(
"Invalid directory, could not find 'objects' or 'refs' in $path"
);
}
$this->_path = $path;
$this->idx_commit = $this->factory('commit');
$this->idx_tree = $this->factory('tree');
}
/**
* Returns an object from the Repository of the given type, with the given
* SHA-1 id, or false if it cannot be found
*
* @param string $type The type (blob, commit, tag or tree) of object being
* requested
* @param string $sha The SHA-1 id of the object (or the name of a tag)
*
* @return Blob|Commit|Tag|Tree
*/
public function factory($type, $sha = null)
{
if (!in_array($type, array('blob', 'commit', 'tag', 'tree'))) {
throw new InvalidArgumentException("Invalid type: $type");
}
if ($type == 'tag') {
$sha = $this->_ref('tags' . DIRECTORY_SEPARATOR . $sha);
}
$type = 'Granite\\Git\\' . ucwords($type);
return new $type($this->_path, $sha);
}
/**
* Returns a Commit object representing the HEAD commit
*
* @param string $branch The branch name to lookup, defaults to 'master'
*
* @return Commit An object representing the HEAD commit
*/
public function head($branch = 'master', $value = NULL)
{
if ($value == NULL)
return $this->factory(
'commit', $this->_ref('heads' . DIRECTORY_SEPARATOR . $branch)
);
file_put_contents(
$this->_path . DIRECTORY_SEPARATOR
. 'refs' . DIRECTORY_SEPARATOR
. 'heads' . DIRECTORY_SEPARATOR . 'master',
$value
);
}
/**
* Returns a string representing the repository's location, which may or may
* not be initialised
*
* @return string A string representing the repository's location
*/
public function path()
{
return $this->_path;
}
/**
* Returns an array of the local branches under `refs/heads`
*
* @return array
*/
public function tags()
{
return $this->_refs('tags');
}
/**
* Returns an array of the local tags under `refs/tags`
*
* @return array
*/
public function branches()
{
return $this->_refs('heads');
}
private function _refs($type)
{
$dir = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $type;
$refs = glob($dir . DIRECTORY_SEPARATOR . '*');
foreach ($refs as &$ref) {
$ref = basename($ref);
}
return $refs;
}
/**
* Initialises a Git repository
*
* @return boolean Returns true on success, false on error
*/
public static function init($path)
{
$path .= '/';
if (!is_dir($path)) {
mkdir($path);
} elseif (is_dir($path . 'objects')) {
return false;
}
mkdir($path . 'objects');
mkdir($path . 'objects/info');
mkdir($path . 'objects/pack');
mkdir($path . 'refs');
mkdir($path . 'refs/heads');
mkdir($path . 'refs/tags');
file_put_contents($path . 'HEAD', 'ref: refs/heads/master');
return true;
}
/**
* Writes the indexed commit to disk, with blobs added/removed via `add()` and
* `rm()`
*
* @param string $message The commit message
* @param string $author The author name
*
* @return boolean True on success, or false on failure
*/
public function commit($message, $author)
{
$user_string = $username . ' ' . time() . ' +0000';
try {
$parents = array($this->repo->head()->sha());
} catch (InvalidArgumentException $e) {
$parents = array();
}
$this->idx_commit->message($message);
$this->idx_commit->author($user_string);
$this->idx_commit->committer($user_string);
$this->idx_commit->tree($this->idx_tree);
$commit->parents($parents);
$this->idx_tree->write();
$this->idx_commit->write();
$this->repo->head('master', $this->idx_commit->sha());
$this->idx_commit = $this->factory('commit');
$this->idx_tree = $this->factory('tree');
}
/**
* Adds a file to the indexed commit, to be written to disk with `commit()`
*
* @param string $filename The filename to save it under
* @param Granite\Git\Blob $blob The raw blob object to add to the tree
*/
public function add($filename, Granite\Git\Blob $blob)
{
$blob->write();
$nodes = $this->idx_tree->nodes();
$nodes[$filename] = new Granite\Git\Tree\Node($filename, '100644', $blob->sha());
$this->idx_tree->nodes($nodes);
}
/**
* Removes a file from the indexed commit
*/
public function rm($filename)
{
$nodes = $this->idx_tree->nodes();
unset($nodes[$filename]);
$this->idx_tree->nodes($nodes);
}
/**
* Returns an SHA-1 id of the ref resource
*
* @param string $ref The ref name to lookup
*
* @return string An SHA-1 id of the ref resource
*/
private function _ref($ref)
{
// All refs are stored in `.git/refs`
$file = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $ref;
if (file_exists($file)) {
return trim(file_get_contents($file));
}
$sha = $this->_packedRef($ref);
if ($sha == false) {
throw new InvalidArgumentException("The ref $ref could not be found");
}
return $sha;
}
/**
* Returns an SHA-1 id of the ref resource, or false if it cannot be found
*
* @param string $ref The ref name to lookup
*
* @return string An SHA-1 id of the ref resource
*/
private function _packedRef($ref)
{
$sha = false;
if (file_exists($this->_path . 'packed-refs')) {
$file = fopen($this->_path . 'packed-refs', 'r');
while (($line = fgets($file)) !== false) {
$info = explode(' ', $line);
if (count($info) == 2
&& trim($info[1]) == 'refs' . DIRECTORY_SEPARATOR . $ref
) {
$sha = trim($info[0]);
break;
}
}
fclose($file);
}
return $sha;
}
}

View File

@ -1,38 +0,0 @@
<?php
/**
* Tag - provides a 'tag' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
/**
* Tag represents a full tag object
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Tag
{
public function __construct($path, $sha)
{
$this->sha = $sha;
}
public function sha()
{
return $this->sha;
}
}

View File

@ -1,198 +0,0 @@
<?php
/**
* Tree - provides a 'tree' object
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git;
use \Granite\Git\Tree\Node as Node;
/**
* Tree represents a full tree object, with nodes pointing to other tree objects
* and file blobs
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Tree
{
/**
* The SHA-1 id of the requested tree
*/
private $sha;
/**
* The nodes/entries for the requested tree
*/
private $nodes = array();
/**
* The path to the repository
*/
private $path;
/**
* Reads a tree object by fetching the raw object
*
* @param string $path The path to the repository root
* @param string $sha The SHA-1 id of the requested object
*/
public function __construct($path, $sha = NULL, $dbg = FALSE)
{
$this->path = $path;
if ($sha !== NULL) {
$object = Object\Raw::factory($path, $sha);
$this->sha = $sha;
if ($object->type() !== Object\Raw::OBJ_TREE) {
throw new \InvalidArgumentException(
"The object $sha is not a tree, type is " . $object->type()
);
}
$content = $object->content();
file_put_contents('/tmp/tree_from_real_repo'.time(), $content);
$nodes = array();
for ($i = 0; $i < strlen($content); $i = $data_start + 21) {
$data_start = strpos($content, "\0", $i);
$info = substr($content, $i, $data_start-$i);
list($mode, $name) = explode(' ', $info, 2);
// Read the object SHA-1 id
$sha = bin2hex(substr($content, $data_start + 1, 20));
$this->nodes[$name] = new Node($name, $mode, $sha);
}
}
}
/**
* Returns an array of Tree and Granite\Git\Blob objects,
* representing subdirectories and files
*
* @return array Array of Tree and Granite\Git\Blob objects
*/
public function nodes($nodes = null)
{
if ($nodes == null) {
return $this->nodes;
}
$this->nodes = $nodes;
}
/**
* Adds a blob or a tree to the list of nodes
*
* @param string $name The basename (filename) of the blob or tree
* @param string $mode The mode of the blob or tree (see above)
* @param string $sha The SHA-1 id of the blob or tree to add
*/
public function add($name, $mode, $sha)
{
$this->nodes[$name] = new Node($name, $mode, $sha);
uasort($this->nodes, array($this, '_sort'));
}
public function write()
{
$sha = $this->sha();
$path = $this->path
. 'objects'
. DIRECTORY_SEPARATOR
. substr($sha, 0, 2)
. DIRECTORY_SEPARATOR
. substr($sha, 2);
// FIXME: currently writes loose objects only
if (file_exists($path)) {
return FALSE;
}
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, TRUE);
}
$loose = fopen($path, 'wb');
$data = $this->_raw();
$data = 'tree ' . strlen($data) . "\0" . $data;
$write = fwrite($loose, gzcompress($data));
fclose($loose);
return ($write !== FALSE);
}
/**
* Returns the SHA-1 id of the Tree
*
* @return string SHA-1 id of the Tree
*/
public function sha()
{
$data = $this->_raw();
$raw = 'tree ' . strlen($data) . "\0" . $data;
$this->sha = hash('sha1', $raw);
return $this->sha;
}
/**
* Generates the raw object content to be saved to disk
*/
public function _raw()
{
uasort($this->nodes, array($this, '_sort'));
$data = '';
foreach ($this->nodes as $node)
{
$data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0";
$data .= pack('H40', $node->sha());
}
file_put_contents('/tmp/tree_made'.time(), $data);
return $data;
}
/**
* Sorts the node entries in a tree, general sort method adapted from original
* Git C code (see @see tag below).
*
* @return 1, 0 or -1 if the first entry is greater than, the same as, or less
* than the second, respectively.
* @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352,
* the `base_name_compare` function
*/
public function _sort(&$a, &$b)
{
$length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name());
$cmp = strncmp($a->name(), $b->name(), $length);
if ($cmp) {
return $cmp;
}
$suffix1 = $a->name();
$suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE;
$suffix2 = $b->name();
$suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE;
if (!$suffix1 && $a->isDirectory()) {
$suffix1 = '/';
}
if (!$suffix2 && $b->isDirectory()) {
$suffix2 = '/';
}
if ($suffix1 < $suffix2) {
return -1;
} elseif ($suffix1 > $suffix2) {
return 1;
}
return 0;
}
}

View File

@ -1,126 +0,0 @@
<?php
/**
* Node - provides a tree node object for tree entries
*
* PHP version 5.3
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
namespace Granite\Git\Tree;
/**
* Node represents an entry in a Tree
*
* @category Git
* @package Granite
* @author Craig Roberts <craig0990@googlemail.com>
* @license http://www.opensource.org/licenses/mit-license.php MIT Expat License
* @link http://craig0990.github.com/Granite/
*/
class Node
{
/**
* Name of the file, directory or submodule
*/
private $_name;
/**
* Mode of the object, in octal
*/
private $_mode;
/**
* SHA-1 id of the tree
*/
private $_sha;
/**
* Boolean value for whether the entry represents a directory
*/
private $_is_dir;
/**
* Boolean value for whether the entry represents a submodule
*/
private $_is_submodule;
/**
* Sets up a Node class with properties corresponding to the $mode parameter
*
* @param string $name The name of the object (file, directory or submodule name)
* @param int $mode The mode of the object, retrieved from the repository
* @param string $sha The SHA-1 id of the object
*/
public function __construct($name, $mode, $sha)
{
$this->_name = $name;
$this->_mode = intval($mode, 8);
$this->_sha = $sha;
$this->_is_dir = (bool) ($this->_mode & 0x4000);
$this->_is_submodule = ($this->_mode == 0xE000);
}
/**
* Returns a boolean value indicating whether the node is a directory
*
* @return boolean
*/
public function isDirectory()
{
return $this->_is_dir;
}
/**
* Returns a boolean value indicating whether the node is a submodule
*
* @return boolean
*/
public function isSubmodule()
{
return $this->_is_submodule;
}
/**
* Returns the object name
*
* @return string
*/
public function name()
{
return $this->_name;
}
/**
* Returns the object's SHA-1 id
*
* @return string
*/
public function sha()
{
return $this->_sha;
}
/**
* Returns the octal value of the file mode
*
* @return int
*/
public function mode()
{
return $this->_mode;
}
public function type()
{
if ($this->isDirectory()) {
return 'tree';
} elseif ($this->isSubmodule()) {
return 'commit';
} else {
return 'blob';
}
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
require_once('../../../lib/base.php');
OC_JSON::checkLoggedIn();
OC_Util::checkAppEnabled('contacts');
$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize'));
$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size'));
$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
$freeSpace=OC_Filesystem::free_space('/');
$freeSpace=max($freeSpace,0);
$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
$tmpl = new OC_Template('contacts', 'part.importaddressbook');
$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize));
$tmpl->printpage();
?>

View File

@ -0,0 +1,50 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Thomas Tanghus
* @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
*
* 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/>.
*
*/
// Init owncloud
require_once('../../../lib/base.php');
// Check if we are a user
OC_JSON::checkLoggedIn();
OC_JSON::checkAppEnabled('contacts');
function bailOut($msg) {
OC_JSON::error(array('data' => array('message' => $msg)));
OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::ERROR);
exit();
}
function debug($msg) {
OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::DEBUG);
}
// If it is a Drag'n'Drop transfer it's handled here.
$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
if($fn) {
$view = OC_App::getStorage('contacts');
$tmpfile = md5(rand());
if($view->file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) {
debug($fn.' uploaded');
OC_JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile)));
} else {
bailOut(OC_Contacts_App::$l10n->t('Error uploading contacts to storage.'));
}
}
?>

View File

@ -33,8 +33,9 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; }
.form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; }
#address.form dt { min-width: 5em; }
#address.form dl { min-width: 10em; }
.loading {/*cursor: progress; */ cursor: wait; }
.droptarget { margin: 0.5em; padding: 0.5em; border: thin solid #ccc; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; }
.droppable { margin: 0.5em; padding: 0.5em; border: thin dashed #333; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; }
.float { float: left; }
.listactions { height: 1em; width:60px; float: left; clear: right; }
.add,.edit,.delete,.mail, .globe, .upload, .cloud { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; opacity: 0.1; }
@ -51,6 +52,7 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; }
#identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ }
/*#contact_photo { max-width: 250px; }*/
#contact_identity { min-width: 30em; }
#note { min-width: 200px; }
.contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto; border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ }
.contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; }
@ -90,5 +92,4 @@ input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; }
.propertylist li > input[type="checkbox"],input[type="radio"] { float: left; clear: left; width: 20px; height: 20px; vertical-align: middle; }
.propertylist li > select { float: left; max-width: 8em; }
.typelist { float: left; max-width: 10em; } /* for multiselect */
.addresslist { clear: both; }
.addresslist { clear: both; }

View File

@ -17,8 +17,14 @@ if(is_writable('import_tmp/')){
fwrite($progressfopen, '10');
fclose($progressfopen);
}
$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']);
if($_POST['method'] == 'new'){
$view = $file = null;
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
$view = OC_App::getStorage('contacts');
$file = $view->file_get_contents('/' . $_POST['file']);
} else {
$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']);
}
if(isset($_POST['method']) && $_POST['method'] == 'new'){
$id = OC_Contacts_Addressbook::add(OC_User::getUser(), $_POST['addressbookname']);
OC_Contacts_Addressbook::setActive($id, 1);
}else{
@ -99,12 +105,16 @@ if(is_writable('import_tmp/')){
if(count($parts) == 1){
$importready = array($file);
}
$imported = 0;
$failed = 0;
foreach($importready as $import){
$card = OC_VObject::parse($import);
if (!$card) {
$failed += 1;
OC_Log::write('contacts','Import: skipping card. Error parsing VCard: '.$import, OC_Log::ERROR);
continue; // Ditch cards that can't be parsed by Sabre.
}
$imported += 1;
OC_Contacts_VCard::add($id, $card);
}
//done the import
@ -117,4 +127,9 @@ sleep(3);
if(is_writable('import_tmp/')){
unlink($progressfile);
}
OC_JSON::success();
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
if(!$view->unlink('/' . $_POST['file'])) {
OC_Log::write('contacts','Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], OC_Log::ERROR);
}
}
OC_JSON::success(array('data' => array('imported'=>$imported, 'failed'=>$failed)));

View File

@ -1263,7 +1263,8 @@ Contacts={
for(ptype in this.data.TEL[phone]['parameters'][param]) {
var pt = this.data.TEL[phone]['parameters'][param][ptype];
$('#phonelist li:last-child').find('select option').each(function(){
if ($(this).val().toUpperCase() == pt.toUpperCase()) {
//if ($(this).val().toUpperCase() == pt.toUpperCase()) {
if ($.inArray($(this).val().toUpperCase(), pt.toUpperCase().split(',')) > -1) {
$(this).attr('selected', 'selected');
}
});
@ -1285,6 +1286,7 @@ Contacts={
},
},
Addressbooks:{
droptarget:undefined,
overview:function(){
if($('#chooseaddressbook_dialog').dialog('isOpen') == true){
$('#chooseaddressbook_dialog').dialog('moveToTop');
@ -1317,14 +1319,13 @@ Contacts={
var tr = $(document.createElement('tr'))
.load(OC.filePath('contacts', 'ajax', 'addbook.php'));
$(object).closest('tr').after(tr).hide();
/* TODO: Shouldn't there be some kinda error checking here? */
},
editAddressbook:function(object, bookid){
var tr = $(document.createElement('tr'))
.load(OC.filePath('contacts', 'ajax', 'editaddressbook.php') + "?bookid="+bookid);
$(object).closest('tr').after(tr).hide();
},
deleteAddressbook:function(bookid){
deleteAddressbook:function(obj, bookid){
var check = confirm("Do you really want to delete this address book?");
if(check == false){
return false;
@ -1332,9 +1333,10 @@ Contacts={
$.post(OC.filePath('contacts', 'ajax', 'deletebook.php'), { id: bookid},
function(jsondata) {
if (jsondata.status == 'success'){
$('#chooseaddressbook_dialog').dialog('destroy').remove();
$(obj).closest('tr').remove();
//$('#chooseaddressbook_dialog').dialog('destroy').remove();
Contacts.UI.Contacts.update();
Contacts.UI.Addressbooks.overview();
//Contacts.UI.Addressbooks.overview();
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
//alert('Error: ' + data.message);
@ -1342,8 +1344,95 @@ Contacts={
});
}
},
doImport:function(){
Contacts.UI.notImplemented();
loadImportHandlers:function() {
this.droptarget = $('#import_drop_target');
console.log($('#import_drop_target').html());
$(this.droptarget).bind('dragover',function(event){
$(event.target).addClass('droppable');
event.stopPropagation();
event.preventDefault();
});
$(this.droptarget).bind('dragleave',function(event){
$(event.target).removeClass('droppable');
});
$(this.droptarget).bind('drop',function(event){
event.stopPropagation();
event.preventDefault();
console.log('drop');
$(event.target).removeClass('droppable');
$(event.target).html(t('contacts', 'Uploading...'));
Contacts.UI.loading(event.target, true);
$.fileUpload(event.originalEvent.dataTransfer.files);
});
$.fileUpload = function(files){
console.log(files + ', ' + files.length);
var file = files[0];
console.log('size: '+file.size+', type: '+file.type);
if(file.size > $('#max_upload').val()){
OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
$(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.'));
Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
return;
}
if(file.type.indexOf('text') != 0) {
OC.dialogs.alert(t('contacts','You have dropped a file type that cannot be imported: ') + file.type, t('contacts','Wrong file type'));
$(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.'));
Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
return;
}
var xhr = new XMLHttpRequest();
if (!xhr.upload) {
OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please upload the contacts file to ownCloud and import that way.'), t('contacts', 'Error'))
}
fileUpload = xhr.upload,
xhr.onreadystatechange = function() {
if (xhr.readyState == 4){
response = $.parseJSON(xhr.responseText);
if(response.status == 'success') {
if(xhr.status == 200) {
$(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Importing...'));
Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, true);
Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file);
} else {
$(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.'));
Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error'));
}
} else {
OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
}
}
};
xhr.open("POST", 'ajax/uploadimport.php?file='+encodeURIComponent(file.name), true);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
xhr.setRequestHeader('X-File-Size', file.size);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
}
},
importAddressbook:function(object){
var tr = $(document.createElement('tr'))
.load(OC.filePath('contacts', 'ajax', 'importaddressbook.php'));
$(object).closest('tr').after(tr).hide();
},
doImport:function(path, file){
var id = $('#importaddressbook_dialog').find('#book').val();
console.log('Selected book: ' + id);
$.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, fstype: 'OC_FilesystemView' },
function(jsondata){
if(jsondata.status == 'success'){
Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Import done. Success/Failure: ')+jsondata.data.imported+'/'+jsondata.data.failed);
$('#chooseaddressbook_dialog').find('#close_button').val(t('contacts', 'OK'));
Contacts.UI.Contacts.update();
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
});
Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
},
submit:function(button, bookid){
var displayname = $("#displayname_"+bookid).val().trim();
@ -1368,7 +1457,7 @@ Contacts={
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
});
});
},
cancel:function(button, bookid){
$(button).closest('tr').prev().show().next().remove();
@ -1385,7 +1474,6 @@ Contacts={
}
else{
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
//alert(jsondata.data.message);
}
});
setTimeout(Contacts.UI.Contacts.lazyupdate, 500);
@ -1507,13 +1595,13 @@ $(document).ready(function(){
});
$('#contacts_details_photo_wrapper').bind('dragover',function(event){
console.log('dragover');
$(event.target).css('background-color','red');
$(event.target).addClass('droppable');
event.stopPropagation();
event.preventDefault();
});
$('#contacts_details_photo_wrapper').bind('dragleave',function(event){
console.log('dragleave');
$(event.target).css('background-color','white');
$(event.target).removeClass('droppable');
//event.stopPropagation();
//event.preventDefault();
});
@ -1521,7 +1609,7 @@ $(document).ready(function(){
event.stopPropagation();
event.preventDefault();
console.log('drop');
$(event.target).css('background-color','white')
$(event.target).removeClass('droppable');
$.fileUpload(event.originalEvent.dataTransfer.files);
});
@ -1618,4 +1706,4 @@ $(document).ready(function() {
$('.ui-autocomplete-loading').css('background', 'url('+OC.filePath('core', 'img', 'loading.gif')+' right center no-repeat');
$('#contacts_details_photo').css('background', 'url('+OC.filePath('core', 'img', 'loading.gif')+' no-repeat center center');
$('#contacts_propertymenu_button').css('background', 'url('+OC.filePath('core', 'img/actions', 'add.svg')+') no-repeat center');
});
});

View File

@ -149,6 +149,7 @@ class OC_Contacts_App {
'WORK' => $l->t('Work'),
'TEXT' => $l->t('Text'),
'VOICE' => $l->t('Voice'),
'MSG' => $l->t('Message'),
'FAX' => $l->t('Fax'),
'VIDEO' => $l->t('Video'),
'PAGER' => $l->t('Pager'),

View File

@ -141,10 +141,38 @@ class OC_Contacts_VCard{
}
/**
* @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0)
* @brief Checks if a contact with the same UID already exist in the address book.
* @param $aid Address book ID.
* @param $uid UID (passed by reference).
* @returns true if the UID has been changed.
*/
protected static function trueUID($aid, &$uid) {
$stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' );
$uri = $uid.'.vcf';
$result = $stmt->execute(array($aid,$uri));
if($result->numRows() > 0){
while(true) {
$tmpuid = substr(md5(rand().time()),0,10);
$uri = $tmpuid.'.vcf';
$result = $stmt->execute(array($aid,$uri));
if($result->numRows() > 0){
continue;
} else {
$uid = $tmpuid;
return true;
}
}
} else {
return false;
}
}
/**
* @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing.
* @param aid Address book id.
* @param vcard An OC_VObject of type VCARD (passed by reference).
*/
protected static function updateValuesFromAdd(&$vcard) { // any suggestions for a better method name? ;-)
protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-)
$stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE');
$typeprops = array('ADR', 'TEL', 'EMAIL');
$upgrade = false;
@ -207,14 +235,19 @@ class OC_Contacts_VCard{
}
if(!$uid) {
$vcard->setUID();
$uid = $vcard->getAsString('UID');
OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OC_Log::DEBUG);
}
if(self::trueUID($aid, $uid)) {
$vcard->setString('UID', $uid);
}
$vcard->setString('VERSION','3.0');
// Add product ID is missing.
$prodid = trim($vcard->getAsString('PRODID'));
if(!$prodid) {
$appinfo = OC_App::getAppInfo('contacts');
$prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appinfo['version'].'//EN';
$appversion = OC_App::getAppVersion('contacts');
$prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
$vcard->setString('PRODID', $prodid);
}
$now = new DateTime;
@ -236,7 +269,7 @@ class OC_Contacts_VCard{
OC_Contacts_App::loadCategoriesFromVCard($card);
self::updateValuesFromAdd($card);
self::updateValuesFromAdd($aid, $card);
$fn = $card->getAsString('FN');
if (empty($fn)) {

View File

@ -1,4 +1,4 @@
<div id="chooseaddressbook_dialog" title="<?php echo $l->t("Choose active Address Books"); ?>">
<div id="chooseaddressbook_dialog" title="<?php echo $l->t("Configure Address Books"); ?>">
<table width="100%" style="border: 0;">
<?php
$option_addressbooks = OC_Contacts_Addressbook::all(OC_User::getUser());
@ -14,6 +14,7 @@ for($i = 0; $i < count($option_addressbooks); $i++){
<tr>
<td colspan="5" style="padding: 0.5em;">
<a class="button" href="#" onclick="Contacts.UI.Addressbooks.newAddressbook(this);"><?php echo $l->t('New Address Book') ?></a>
<a class="button" href="#" onclick="Contacts.UI.Addressbooks.importAddressbook(this);"><?php echo $l->t('Import from VCF') ?></a>
</td>
</tr>
<tr>

View File

@ -2,4 +2,4 @@
// FIXME: Make this readable.
echo "<td width=\"20px\"><input id=\"active_" . $_['addressbook']["id"] . "\" type=\"checkbox\" onClick=\"Contacts.UI.Addressbooks.activation(this, " . $_['addressbook']["id"] . ")\"" . (OC_Contacts_Addressbook::isActive($_['addressbook']["id"]) ? ' checked="checked"' : '') . "></td>";
echo "<td><label for=\"active_" . $_['addressbook']["id"] . "\">" . htmlspecialchars($_['addressbook']["displayname"]) . "</label></td>";
echo "<td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.showCardDAVUrl('" . OC_User::getUser() . "', '" . rawurlencode($_['addressbook']["uri"]) . "');\" title=\"" . $l->t("CardDav Link") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/public.svg\"></a></td><td width=\"20px\"><a href=\"export.php?bookid=" . $_['addressbook']["id"] . "\" title=\"" . $l->t("Download") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/download.svg\"></a></td><td width=\"20px\"><a href=\"#\" title=\"" . $l->t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\"><img class=\"svg action\" src=\"../../core/img/actions/rename.svg\"></a></td><td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.Addressbooks.deleteAddressbook('" . $_['addressbook']["id"] . "');\" title=\"" . $l->t("Delete") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/delete.svg\"></a></td>";
echo "<td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.showCardDAVUrl('" . OC_User::getUser() . "', '" . rawurlencode($_['addressbook']["uri"]) . "');\" title=\"" . $l->t("CardDav Link") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/public.svg\"></a></td><td width=\"20px\"><a href=\"export.php?bookid=" . $_['addressbook']["id"] . "\" title=\"" . $l->t("Download") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/download.svg\"></a></td><td width=\"20px\"><a href=\"#\" title=\"" . $l->t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\"><img class=\"svg action\" src=\"../../core/img/actions/rename.svg\"></a></td><td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.Addressbooks.deleteAddressbook(this, '" . $_['addressbook']["id"] . "');\" title=\"" . $l->t("Delete") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/delete.svg\"></a></td>";

View File

@ -112,7 +112,7 @@ $id = isset($_['id']) ? $_['id'] : '';
<div id="contact_note" class="contactsection">
<form class="float" method="post">
<fieldset id="note" class="formfloat propertycontainer contactpart" data-element="NOTE">
<textarea class="contacts_property note" name="value" cols="40" rows="10" required="required" placeholder="<?php echo $l->t('Add notes here.'); ?>"></textarea>
<textarea class="contacts_property note" name="value" cols="60" rows="15" required="required" placeholder="<?php echo $l->t('Add notes here.'); ?>"></textarea>
</fieldset>
</form>
</div> <!-- contact_note -->

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.
*/
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.
*/
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,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;
}
}

View File

@ -0,0 +1,19 @@
<?php
require_once('apps/files_versions/versions.php');
// Add an entry in the app list
OC_App::register( array(
'order' => 10,
'id' => 'files_versions',
'name' => 'Versioning' ));
OC_APP::registerAdmin('files_versions', 'settings');
// Listen to write signals
OC_Hook::connect(OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, "OCA_Versions\Storage", "write_hook");
?>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<info>
<id>files_versions</id>
<name>Versions</name>
<licence>AGPL</licence>
<author>Frank Karlitschek</author>
<require>3</require>
<description>Versioning of files</description>
<types>
<filesystem/>
</types>
</info>

View File

@ -0,0 +1,2 @@

View File

@ -0,0 +1,60 @@
<?php
/**
* ownCloud - History page of the Versions App
*
* @author Frank Karlitschek
* @copyright 2011 Frank Karlitschek karlitschek@kde.org
*
* 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
require_once('../../lib/base.php');
OC_Util::checkLoggedIn();
if (isset($_GET['path'])) {
$path = $_GET['path'];
$path = strip_tags($path);
// roll back to old version if button clicked
if(isset($_GET['revert'])) {
\OCA_Versions\Storage::rollback($path,$_GET['revert']);
}
// show the history only if there is something to show
if(OCA_Versions\Storage::isversioned($path)) {
$count=5; //show the newest revisions
$versions=OCA_Versions\Storage::getversions($path,$count);
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('path', $path);
$tmpl->assign('versions', array_reverse($versions));
$tmpl->printPage();
}else{
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('path', $path);
$tmpl->assign('message', 'No old versions available');
$tmpl->printPage();
}
}else{
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('message', 'No path specified');
$tmpl->printPage();
}
?>

View File

@ -0,0 +1,2 @@

View File

@ -0,0 +1,10 @@
<?php
OC_Util::checkAdminUser();
OC_Util::addScript( 'files_versions', 'versions' );
$tmpl = new OC_Template( 'files_versions', 'settings');
return $tmpl->fetchPage();
?>

View File

@ -0,0 +1,18 @@
<?php
if(isset($_['message'])){
if(isset($_['path'])) echo('<strong>File: '.$_['path']).'</strong><br>';
echo('<strong>'.$_['message']).'</strong><br>';
}else{
echo('<strong>Versions of '.$_['path']).'</strong><br>';
echo('<p><em>You can click on the revert button to revert to the specific verson.</em></p><br />');
foreach ($_['versions'] as $v){
echo(' '.OC_Util::formatDate($v).' <a href="history.php?path='.urlencode($_['path']).'&revert='.$v.'" class="button">revert</a><br /><br />');
}
}
?>

View File

@ -0,0 +1,7 @@
<form id="external">
<fieldset class="personalblock">
<strong>Versions</strong><br />
Configuration goes here...
</fieldset>
</form>

View File

@ -0,0 +1,216 @@
<?php
/**
* Copyright (c) 2012 Frank Karlitschek <frank@owncloud.org>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
/**
* Versions
*
* A class to handle the versioning of files.
*/
namespace OCA_Versions;
class Storage {
// config.php configuration:
// - files_versions
// - files_versionsfolder
// - files_versionsblacklist
// - files_versionsmaxfilesize
// - files_versionsinterval
// - files_versionmaxversions
//
// todo:
// - port to oc_filesystem to enable network transparency
// - check if it works well together with encryption
// - do configuration web interface
// - implement expire all function. And find a place to call it ;-)
// - add transparent compression. first test if it´s worth it.
const DEFAULTENABLED=true;
const DEFAULTFOLDER='versions';
const DEFAULTBLACKLIST='avi mp3 mpg mp4';
const DEFAULTMAXFILESIZE=1048576; // 10MB
const DEFAULTMININTERVAL=300; // 5 min
const DEFAULTMAXVERSIONS=50;
/**
* init the versioning and create the versions folder.
*/
public static function init() {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
// create versions folder
$foldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
if(!is_dir($foldername)){
mkdir($foldername);
}
}
}
/**
* listen to write event.
*/
public static function write_hook($params) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$path = $params[\OC_Filesystem::signal_param_path];
if($path<>'') Storage::store($path);
}
}
/**
* store a new version of a file.
*/
public static function store($filename) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files';
Storage::init();
// check if filename is a directory
if(is_dir($filesfoldername.$filename)){
return false;
}
// check filetype blacklist
$blacklist=explode(' ',\OC_Config::getValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST));
foreach($blacklist as $bl) {
$parts=explode('.', $filename);
$ext=end($parts);
if(strtolower($ext)==$bl) {
return false;
}
}
// check filesize
if(filesize($filesfoldername.$filename)>\OC_Config::getValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){
return false;
}
// check mininterval
$matches=glob($versionsfoldername.$filename.'.v*');
sort($matches);
$parts=explode('.v',end($matches));
if((end($parts)+Storage::DEFAULTMININTERVAL)>time()){
return false;
}
// create all parent folders
$info=pathinfo($filename);
@mkdir($versionsfoldername.$info['dirname'],0700,true);
// store a new version of a file
copy($filesfoldername.$filename,$versionsfoldername.$filename.'.v'.time());
// expire old revisions
Storage::expire($filename);
}
}
/**
* rollback to an old version of a file.
*/
public static function rollback($filename,$revision) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files';
// rollback
@copy($versionsfoldername.$filename.'.v'.$revision,$filesfoldername.$filename);
}
}
/**
* check if old versions of a file exist.
*/
public static function isversioned($filename) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
// check for old versions
$matches=glob($versionsfoldername.$filename.'.v*');
if(count($matches)>1){
return true;
}else{
return false;
}
}else{
return(false);
}
}
/**
* get a list of old versions of a file.
*/
public static function getversions($filename,$count=0) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$versions=array();
// fetch for old versions
$matches=glob($versionsfoldername.$filename.'.v*');
sort($matches);
foreach($matches as $ma) {
$parts=explode('.v',$ma);
$versions[]=(end($parts));
}
// only show the newest commits
if($count<>0 and (count($versions)>$count)) {
$versions=array_slice($versions,count($versions)-$count);
}
return($versions);
}else{
return(array());
}
}
/**
* expire old versions of a file.
*/
public static function expire($filename) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
// check for old versions
$matches=glob($versionsfoldername.$filename.'.v*');
if(count($matches)>\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)){
$numbertodelete=count($matches-\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS));
// delete old versions of a file
$deleteitems=array_slice($matches,0,$numbertodelete);
foreach($deleteitems as $de){
unlink($versionsfoldername.$filename.'.v'.$de);
}
}
}
}
/**
* expire all old versions.
*/
public static function expireall($filename) {
// todo this should go through all the versions directories and delete all the not needed files and not needed directories.
// useful to be included in a cleanup cronjob.
}
}

View File

@ -519,6 +519,10 @@ class OC_App{
public static function getStorage($appid){
if(OC_App::isEnabled($appid)){//sanity check
if(OC_User::isLoggedIn()){
$view = new OC_FilesystemView('/'.OC_User::getUser());
if(!$view->file_exists($appid)) {
$view->mkdir($appid);
}
return new OC_FilesystemView('/'.OC_User::getUser().'/'.$appid);
}else{
OC_Log::write('core','Can\'t get app storage, app, user not logged in',OC_Log::ERROR);