<?php
/**
 * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
 * This file is licensed under the Affero General Public License version 3 or
 * later.
 * See the COPYING-README file.
 */

namespace OC\Files\Cache;

class Upgrade {
	/**
	 * @var Legacy $legacy
	 */
	private $legacy;

	private $numericIds = array();

	private $mimeTypeIds = array();

	/**
	 * @param Legacy $legacy
	 */
	public function __construct($legacy) {
		$this->legacy = $legacy;
	}

	/**
	 * Preform a upgrade a path and it's childs
	 *
	 * @param string $path
	 * @param bool $mode
	 */
	function upgradePath($path, $mode = Scanner::SCAN_RECURSIVE) {
		if (!$this->legacy->hasItems()) {
			return;
		}
		\OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $path);
		if ($row = $this->legacy->get($path)) {
			$data = $this->getNewData($row);
			if ($data) {
				$this->insert($data);
				$this->upgradeChilds($data['id'], $mode);
			}
		}
	}

	/**
	 * upgrade all child elements of an item
	 *
	 * @param int $id
	 * @param bool $mode
	 */
	function upgradeChilds($id, $mode = Scanner::SCAN_RECURSIVE) {
		$children = $this->legacy->getChildren($id);
		foreach ($children as $child) {
			$childData = $this->getNewData($child);
			\OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']);
			if ($childData) {
				$this->insert($childData);
				if ($mode == Scanner::SCAN_RECURSIVE) {
					$this->upgradeChilds($child['id']);
				}
			}
		}
	}

	/**
	 * insert data into the new cache
	 *
	 * @param array $data the data for the new cache
	 */
	function insert($data) {
		static $insertQuery = null;
		if(is_null($insertQuery)) {
			$insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`
				( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` )
				VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
		}
		if (!$this->inCache($data['storage'], $data['path_hash'], $data['id'])) {
			\OC_DB::executeAudited($insertQuery, array($data['id'], $data['storage'],
				$data['path'], $data['path_hash'], $data['parent'], $data['name'],
				$data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'], $data['etag']));
		}
	}

	/**
	 * check if an item is already in the new cache
	 *
	 * @param string $storage
	 * @param string $pathHash
	 * @param string $id
	 * @return bool
	 */
	function inCache($storage, $pathHash, $id) {
		static $query = null;
		if(is_null($query)) {
			$query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE (`storage` = ? AND `path_hash` = ?) OR `fileid` = ?');
		}
		$result = \OC_DB::executeAudited($query, array($storage, $pathHash, $id));
		return (bool)$result->fetchRow();
	}

	/**
	 * get the new data array from the old one
	 *
	 * @param array $data the data from the old cache
	 * Example data array
	 * Array
	 *	(
	 *		[id] => 418
	 *		[path] => /tina/files/picture.jpg		//relative to datadir
	 *		[path_hash] => 66d4547e372888deed80b24fec9b192b
	 *		[parent] => 234
	 *		[name] => picture.jpg
	 *		[user] => tina
	 *		[size] => 1265283
	 *		[ctime] => 1363909709
	 *		[mtime] => 1363909709
	 *		[mimetype] => image/jpeg
	 *		[mimepart] => image
	 *		[encrypted] => 0
	 *		[versioned] => 0
	 *		[writable] => 1
	 *	)
	 *
	 * @return array
	 */
	function getNewData($data) {
		//Make sure there is a path, otherwise we can do nothing.
		if(!isset($data['path'])) {
			return false;
		}
		$newData = $data;
		/**
		 * @var \OC\Files\Storage\Storage $storage
		 * @var string $internalPath;
		 */
		list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($data['path']);
		if ($storage) {
			$newData['etag'] = $data['etag'];
			$newData['path_hash'] = md5($internalPath);
			$newData['path'] = $internalPath;
			$newData['storage'] = $this->getNumericId($storage);
			$newData['parent'] = ($internalPath === '') ? -1 : $data['parent'];
			$newData['permissions'] = ($data['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ;
			$newData['storage_object'] = $storage;
			$newData['mimetype'] = $this->getMimetypeId($newData['mimetype'], $storage);
			$newData['mimepart'] = $this->getMimetypeId($newData['mimepart'], $storage);
			return $newData;
		} else {
			\OC_Log::write('core', 'Unable to migrate data from old cache for '.$data['path'].' because the storage was not found', \OC_Log::ERROR);
			return false;
		}
	}

	/**
	 * get the numeric storage id
	 *
	 * @param \OC\Files\Storage\Storage $storage
	 * @return int
	 */
	function getNumericId($storage) {
		$storageId = $storage->getId();
		if (!isset($this->numericIds[$storageId])) {
			$cache = $storage->getCache();
			$this->numericIds[$storageId] = $cache->getNumericStorageId();
		}
		return $this->numericIds[$storageId];
	}

	/**
	 * get the numeric id for a mimetype
	 *
	 * @param string $mimetype
	 * @param \OC\Files\Storage\Storage $storage
	 * @return int
	 */
	function getMimetypeId($mimetype, $storage) {
		if (!isset($this->mimeTypeIds[$mimetype])) {
			$cache = new Cache($storage);
			$this->mimeTypeIds[$mimetype] = $cache->getMimetypeId($mimetype);
		}
		return $this->mimeTypeIds[$mimetype];
	}

	/**
	 * check if a cache upgrade is required for $user
	 *
	 * @param string $user
	 * @return bool
	 */
	static function needUpgrade($user) {
		$cacheVersion = (int)\OCP\Config::getUserValue($user, 'files', 'cache_version', 4);
		if ($cacheVersion < 5) {
			$legacy = new \OC\Files\Cache\Legacy($user);
			if ($legacy->hasItems()) {
				return true;
			}
			self::upgradeDone($user);
		}

		return false;
	}

	/**
	 * mark the filecache as upgrade
	 *
	 * @param string $user
	 */
	static function upgradeDone($user) {
		\OCP\Config::setUserValue($user, 'files', 'cache_version', 5);
	}

	/**
	 * Does a "silent" upgrade, i.e. without an Event-Source as triggered
	 * on User-Login via Ajax. This method is called within the regular
	 * ownCloud upgrade.
	 *
	 * @param string $user a User ID
	 */
	public static function doSilentUpgrade($user) {
		if(!self::needUpgrade($user)) {
			return;
		}
		$legacy = new \OC\Files\Cache\Legacy($user);
		if ($legacy->hasItems()) {
			\OC_DB::beginTransaction();
			$upgrade = new \OC\Files\Cache\Upgrade($legacy);
			$upgrade->upgradePath('/' . $user . '/files');
			\OC_DB::commit();
		}
		\OC\Files\Cache\Upgrade::upgradeDone($user);
	}
}
