Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/files_sharing/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Turning the feature off removes shared files and folders on the server for all s
<licence>AGPL</licence>
<author>Michael Gapczynski, Bjoern Schiessle</author>
<default_enable/>
<version>0.8.1</version>
<version>0.9.0</version>
<types>
<filesystem/>
</types>
Expand Down
9 changes: 5 additions & 4 deletions apps/files_sharing/appinfo/update.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@

$installedVersion = \OC::$server->getConfig()->getAppValue('files_sharing', 'installed_version');

// Migration OC7 -> OC8
if (version_compare($installedVersion, '0.6.0', '<')) {
$m = new Migration();
$m->addAcceptRow();
// Migration OC8.2 -> OC9
if (version_compare($installedVersion, '0.9.0', '<')) {
$m = new Migration(\OC::$server->getDatabaseConnection());
$m->removeReShares();
$m->updateInitiatorInfo();
}

\OC::$server->getJobList()->add('OCA\Files_sharing\Lib\DeleteOrphanedSharesJob');
Expand Down
243 changes: 233 additions & 10 deletions apps/files_sharing/lib/migration.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @license AGPL-3.0
Expand All @@ -20,22 +19,246 @@
*
*/

namespace OCA\Files_Sharing;
namespace OCA\Files_Sharing;

use Doctrine\DBAL\Connection;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing you can fix here. But it is kinda weird this needs to be included here. Breaks the abstraction

use OCP\IDBConnection;
use OC\Cache\CappedMemoryCache;

/**
* Class Migration
*
* @package OCA\Files_Sharing
* @group DB
*/
class Migration {

/** @var IDBConnection */
private $connection;

/** @var array with all shares we already saw */
private $shareCache;

/** @var string */
private $table = 'share';

public function __construct(IDBConnection $connection) {
$this->connection = $connection;

// We cache up to 10k share items (~20MB)
$this->shareCache = new CappedMemoryCache(10000);
}

/**
* move all re-shares to the owner in order to have a flat list of shares
* upgrade from oC 8.2 to 9.0 with the new sharing
*/
public function removeReShares() {

while(true) {
$reShares = $this->getReShares(1000);

if (empty($reShares)) {
break;
}

// Update the cache
foreach($reShares as $reShare) {
$this->shareCache[$reShare['id']] = $reShare;
}

$owners = [];
foreach ($reShares as $share) {
$owners[$share['id']] = [
'owner' => $this->findOwner($share),
'initiator' => $share['uid_owner']
];
}
$this->updateOwners($owners);

//Clear the cache of the shares we just updated so we have more room
foreach($owners as $id => $owner) {
unset($this->shareCache[$id]);
}
}
}

/**
* set accepted to 1 for all external shares. At this point in time we only
* have shares from the first version of server-to-server sharing so all should
* be accepted
* update all owner information so that all shares have an owner
* and an initiator for the upgrade from oC 8.2 to 9.0 with the new sharing
*/
public function addAcceptRow() {
$statement = 'UPDATE `*PREFIX*share_external` SET `accepted` = 1';
$connection = \OC::$server->getDatabaseConnection();
$query = $connection->prepare($statement);
$query->execute();
public function updateInitiatorInfo() {
while (true) {
$shares = $this->getMissingInitiator(1000);

if (empty($shares)) {
break;
}

$owners = [];
foreach ($shares as $share) {
$owners[$share['id']] = [
'owner' => $share['uid_owner'],
'initiator' => $share['uid_owner']
];
}
$this->updateOwners($owners);
}
}

/**
* find the owner of a re-shared file/folder
*
* @param array $share
* @return array
*/
private function findOwner($share) {
$currentShare = $share;
while(!is_null($currentShare['parent'])) {
if (isset($this->shareCache[$currentShare['parent']])) {
$currentShare = $this->shareCache[$currentShare['parent']];
} else {
$currentShare = $this->getShare((int)$currentShare['parent']);
$this->shareCache[$currentShare['id']] = $currentShare;
}
}

return $currentShare['uid_owner'];
}

/**
* Get $n re-shares from the database
*
* @param int $n The max number of shares to fetch
* @return array
*/
private function getReShares($n = 1000) {
$query = $this->connection->getQueryBuilder();
$query->select(['id', 'parent', 'uid_owner'])
->from($this->table)
->where($query->expr()->in(
'share_type',
$query->createNamedParameter(
[
\OCP\Share::SHARE_TYPE_USER,
\OCP\Share::SHARE_TYPE_GROUP,
\OCP\Share::SHARE_TYPE_LINK
],
Connection::PARAM_INT_ARRAY
)
))
->andWhere($query->expr()->in(
'item_type',
$query->createNamedParameter(
['file', 'folder'],
Connection::PARAM_STR_ARRAY
)
))
->andWhere($query->expr()->isNotNull('parent'))
->orderBy('id', 'asc')
->setMaxResults($n);
$result = $query->execute();
$shares = $result->fetchAll();
$result->closeCursor();

$ordered = [];
foreach ($shares as $share) {
$ordered[(int)$share['id']] = $share;
}

return $ordered;
}

/**
* Get $n re-shares from the database
*
* @param int $n The max number of shares to fetch
* @return array
*/
private function getMissingInitiator($n = 1000) {
$query = $this->connection->getQueryBuilder();
$query->select(['id', 'uid_owner'])
->from($this->table)
->where($query->expr()->in(
'share_type',
$query->createNamedParameter(
[
\OCP\Share::SHARE_TYPE_USER,
\OCP\Share::SHARE_TYPE_GROUP,
\OCP\Share::SHARE_TYPE_LINK
],
Connection::PARAM_INT_ARRAY
)
))
->andWhere($query->expr()->in(
'item_type',
$query->createNamedParameter(
['file', 'folder'],
Connection::PARAM_STR_ARRAY
)
))
->andWhere($query->expr()->isNull('uid_initiator'))
->orderBy('id', 'asc')
->setMaxResults($n);
$result = $query->execute();
$shares = $result->fetchAll();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about big installations this can be quite a lot. An iterativ approach would be better.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I think even for big installations the number of reshares is still manageable. I do agree that iterative approach would be safer.

$result->closeCursor();

$ordered = [];
foreach ($shares as $share) {
$ordered[(int)$share['id']] = $share;
}

return $ordered;
}

/**
* get a specific share
*
* @param int $id
* @return array
*/
private function getShare($id) {
$query = $this->connection->getQueryBuilder();
$query->select(['id', 'parent', 'uid_owner'])
->from($this->table)
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
$result = $query->execute();
$share = $result->fetchAll();
$result->closeCursor();

return $share[0];
}

/**
* update database with the new owners
*
* @param array $owners
* @throws \Exception
*/
private function updateOwners($owners) {

$this->connection->beginTransaction();

try {

foreach ($owners as $id => $owner) {
$query = $this->connection->getQueryBuilder();
$query->update($this->table)
->set('parent', $query->createNamedParameter(null))
->set('uid_owner', $query->createNamedParameter($owner['owner']))
->set('uid_initiator', $query->createNamedParameter($owner['initiator']))
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
->execute();
}

$this->connection->commit();

} catch (\Exception $e) {
$this->connection->rollBack();
throw $e;
}

}

}
Loading