aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/l10n/lt_LT.js11
-rw-r--r--apps/files/l10n/lt_LT.json11
-rw-r--r--apps/files_external/appinfo/database.xml6
-rw-r--r--apps/files_external/appinfo/info.xml2
-rw-r--r--apps/files_external/l10n/fr.js6
-rw-r--r--apps/files_external/l10n/fr.json6
-rw-r--r--apps/files_external/l10n/ja.js1
-rw-r--r--apps/files_external/l10n/ja.json1
-rw-r--r--apps/files_external/l10n/nl.js9
-rw-r--r--apps/files_external/l10n/nl.json9
-rwxr-xr-xapps/files_external/tests/env/entrypoint.sh274
-rwxr-xr-xapps/files_external/tests/env/start-swift-ceph.sh13
-rw-r--r--apps/files_sharing/tests/etagpropagation.php80
-rw-r--r--apps/files_sharing/tests/groupetagpropagation.php104
-rw-r--r--apps/files_sharing/tests/propagationtestcase.php103
-rw-r--r--apps/files_sharing/tests/testcase.php6
-rw-r--r--apps/files_versions/command/expire.php18
-rw-r--r--apps/files_versions/lib/storage.php29
-rw-r--r--build/integration/features/bootstrap/WebDav.php23
-rw-r--r--build/integration/features/sharing-v1.feature41
-rw-r--r--config/config.sample.php13
-rw-r--r--core/l10n/fr.js1
-rw-r--r--core/l10n/fr.json1
-rw-r--r--core/l10n/ja.js3
-rw-r--r--core/l10n/ja.json3
-rw-r--r--core/l10n/nl.js1
-rw-r--r--core/l10n/nl.json1
-rw-r--r--lib/private/app/appmanager.php7
-rw-r--r--lib/private/comments/comment.php357
-rw-r--r--lib/private/comments/manager.php549
-rw-r--r--lib/private/comments/managerfactory.php23
-rw-r--r--lib/private/files/storage/wrapper/availability.php1
-rw-r--r--lib/private/lock/abstractlockingprovider.php2
-rw-r--r--lib/private/lock/dblockingprovider.php8
-rw-r--r--lib/private/lock/memcachelockingprovider.php9
-rw-r--r--lib/private/memcache/redis.php8
-rw-r--r--lib/private/server.php14
-rw-r--r--lib/private/setup/mysql.php25
-rw-r--r--lib/private/user/user.php2
-rw-r--r--lib/public/comments/icomment.php21
-rw-r--r--lib/public/comments/icommentsmanager.php22
-rw-r--r--lib/public/comments/icommentsmanagerfactory.php23
-rw-r--r--lib/public/imemcachettl.php38
-rw-r--r--lib/public/iservercontainer.php6
-rw-r--r--settings/l10n/ja.js2
-rw-r--r--settings/l10n/ja.json2
-rw-r--r--tests/lib/comments/comment.php112
-rw-r--r--tests/lib/comments/fakefactory.php13
-rw-r--r--tests/lib/comments/fakemanager.php33
-rw-r--r--tests/lib/comments/manager.php564
-rw-r--r--tests/lib/lock/dblockingprovider.php8
-rw-r--r--tests/lib/server.php13
-rw-r--r--tests/startsessionlistener.php3
53 files changed, 2482 insertions, 159 deletions
diff --git a/apps/files/l10n/lt_LT.js b/apps/files/l10n/lt_LT.js
index b4001e0c8ea..e93fb079d56 100644
--- a/apps/files/l10n/lt_LT.js
+++ b/apps/files/l10n/lt_LT.js
@@ -40,6 +40,17 @@ OC.L10N.register(
"Unable to determine date" : "Nepavyksta nustatyti datos",
"This operation is forbidden" : "Ši operacija yra uždrausta",
"This directory is unavailable, please check the logs or contact the administrator" : "Katalogas nepasiekiamas, prašome peržiūrėti žurnalo įrašus arba susisiekti su administratoriumi",
+ "Could not move \"{file}\", target exists" : "Nepavyko perkelti \"{file}\", toks jau egzistuoja",
+ "Could not move \"{file}\"" : "Nepavyko perkelti \"{file}\"",
+ "{newName} already exists" : "{newName} jau egzistuoja",
+ "Could not rename \"{fileName}\", it does not exist any more" : "Nepavyko pervadinti failo \"{fileName}\", nes jis jau nebeegzistuoja",
+ "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Pavadinimas \"{targetName}\" jau naudojamas aplanke \"{dir}\". Prašome pasirinkti kitokį pavadinimą.",
+ "Could not rename \"{fileName}\"" : "Nepavyko pervadinti failo \"{fileName}\"",
+ "Could not create file \"{file}\"" : "Nepavyko sukurti failo \"{file}\"",
+ "Could not create file \"{file}\" because it already exists" : "Nepavyko sukurti failo \"{file}\" - failas su tokiu pavadinimu jau egzistuoja",
+ "Could not create folder \"{dir}\"" : "Nepavyko sukurti aplanko \"{dir}\"",
+ "Could not create folder \"{dir}\" because it already exists" : "Nepavyko sukurti aplanko \"{dir}\"- aplankas su tokiu pavadinimu jau egzistuoja",
+ "Error deleting file \"{fileName}\"." : "Klaida trinant failą \"{fileName}\".",
"No entries in this folder match '{filter}'" : "Nėra įrašų šiame aplanko atitikmeniui „{filter}“",
"Name" : "Pavadinimas",
"Size" : "Dydis",
diff --git a/apps/files/l10n/lt_LT.json b/apps/files/l10n/lt_LT.json
index ba8610da86b..e6724be98cf 100644
--- a/apps/files/l10n/lt_LT.json
+++ b/apps/files/l10n/lt_LT.json
@@ -38,6 +38,17 @@
"Unable to determine date" : "Nepavyksta nustatyti datos",
"This operation is forbidden" : "Ši operacija yra uždrausta",
"This directory is unavailable, please check the logs or contact the administrator" : "Katalogas nepasiekiamas, prašome peržiūrėti žurnalo įrašus arba susisiekti su administratoriumi",
+ "Could not move \"{file}\", target exists" : "Nepavyko perkelti \"{file}\", toks jau egzistuoja",
+ "Could not move \"{file}\"" : "Nepavyko perkelti \"{file}\"",
+ "{newName} already exists" : "{newName} jau egzistuoja",
+ "Could not rename \"{fileName}\", it does not exist any more" : "Nepavyko pervadinti failo \"{fileName}\", nes jis jau nebeegzistuoja",
+ "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Pavadinimas \"{targetName}\" jau naudojamas aplanke \"{dir}\". Prašome pasirinkti kitokį pavadinimą.",
+ "Could not rename \"{fileName}\"" : "Nepavyko pervadinti failo \"{fileName}\"",
+ "Could not create file \"{file}\"" : "Nepavyko sukurti failo \"{file}\"",
+ "Could not create file \"{file}\" because it already exists" : "Nepavyko sukurti failo \"{file}\" - failas su tokiu pavadinimu jau egzistuoja",
+ "Could not create folder \"{dir}\"" : "Nepavyko sukurti aplanko \"{dir}\"",
+ "Could not create folder \"{dir}\" because it already exists" : "Nepavyko sukurti aplanko \"{dir}\"- aplankas su tokiu pavadinimu jau egzistuoja",
+ "Error deleting file \"{fileName}\"." : "Klaida trinant failą \"{fileName}\".",
"No entries in this folder match '{filter}'" : "Nėra įrašų šiame aplanko atitikmeniui „{filter}“",
"Name" : "Pavadinimas",
"Size" : "Dydis",
diff --git a/apps/files_external/appinfo/database.xml b/apps/files_external/appinfo/database.xml
index 27918bf9819..2c3615a4d4c 100644
--- a/apps/files_external/appinfo/database.xml
+++ b/apps/files_external/appinfo/database.xml
@@ -80,14 +80,12 @@
<length>64</length>
</field>
<index>
- <name>mount_id_app_index</name>
<field>
<name>mount_id</name>
<sorting>ascending</sorting>
</field>
</index>
<index>
- <name>applicable_value_index</name>
<field>
<name>type</name>
<sorting>ascending</sorting>
@@ -98,7 +96,6 @@
</field>
</index>
<index>
- <name>applicable_value_mount_index</name>
<unique>true</unique>
<field>
<name>type</name>
@@ -147,14 +144,12 @@
</field>
<index>
- <name>config_mount_id</name>
<field>
<name>mount_id</name>
<sorting>ascending</sorting>
</field>
</index>
<index>
- <name>config_mount_key</name>
<unique>true</unique>
<field>
<name>mount_id</name>
@@ -199,7 +194,6 @@
</field>
<index>
- <name>option_mount_id</name>
<field>
<name>mount_id</name>
<sorting>ascending</sorting>
diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml
index f6d583d0a5a..355d9feb4b9 100644
--- a/apps/files_external/appinfo/info.xml
+++ b/apps/files_external/appinfo/info.xml
@@ -14,7 +14,7 @@
<admin>admin-external-storage</admin>
</documentation>
<rememberlogin>false</rememberlogin>
- <version>0.5.0</version>
+ <version>0.5.1</version>
<types>
<filesystem/>
</types>
diff --git a/apps/files_external/l10n/fr.js b/apps/files_external/l10n/fr.js
index 8d5f7e911b7..9ca35b46b4c 100644
--- a/apps/files_external/l10n/fr.js
+++ b/apps/files_external/l10n/fr.js
@@ -36,6 +36,12 @@ OC.L10N.register(
"(group)" : "(groupe)",
"Admin defined" : "Défini par l'administrateur",
"Saved" : "Sauvegardé",
+ "Empty response from the server" : "Réponse vide du serveur",
+ "Couldn't access. Please logout and login to activate this mount point" : "Impossible d'accéder. Veuillez vous déconnecter et vous reconnecter pour activer ce point de montage.",
+ "Couldn't get the information from the ownCloud server: {code} {type}" : "Impossible d'obtenir l'information depuis le serveur ownCloud : {code} {type}",
+ "Couldn't get the list of external mount points: {type}" : "Impossible de récupérer la liste des points de montage externes : {type}",
+ "There was an error with message: " : "Il y a eu une erreur avec le message :",
+ "External mount error" : "Erreur de montage externe",
"Access key" : "Clé d'accès",
"Secret key" : "Clé secrète",
"Builtin" : "Intégré",
diff --git a/apps/files_external/l10n/fr.json b/apps/files_external/l10n/fr.json
index cae66119a4f..8be075113a8 100644
--- a/apps/files_external/l10n/fr.json
+++ b/apps/files_external/l10n/fr.json
@@ -34,6 +34,12 @@
"(group)" : "(groupe)",
"Admin defined" : "Défini par l'administrateur",
"Saved" : "Sauvegardé",
+ "Empty response from the server" : "Réponse vide du serveur",
+ "Couldn't access. Please logout and login to activate this mount point" : "Impossible d'accéder. Veuillez vous déconnecter et vous reconnecter pour activer ce point de montage.",
+ "Couldn't get the information from the ownCloud server: {code} {type}" : "Impossible d'obtenir l'information depuis le serveur ownCloud : {code} {type}",
+ "Couldn't get the list of external mount points: {type}" : "Impossible de récupérer la liste des points de montage externes : {type}",
+ "There was an error with message: " : "Il y a eu une erreur avec le message :",
+ "External mount error" : "Erreur de montage externe",
"Access key" : "Clé d'accès",
"Secret key" : "Clé secrète",
"Builtin" : "Intégré",
diff --git a/apps/files_external/l10n/ja.js b/apps/files_external/l10n/ja.js
index 5518f6afa78..8d50794e5b6 100644
--- a/apps/files_external/l10n/ja.js
+++ b/apps/files_external/l10n/ja.js
@@ -17,6 +17,7 @@ OC.L10N.register(
"Unsatisfied backend parameters" : "バックエンドのためのパラメーターが不十分です。",
"Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です",
"Insufficient data: %s" : "データが不足しています: %s",
+ "%s" : "%s",
"Personal" : "個人",
"System" : "システム",
"Grant access" : "アクセスを許可",
diff --git a/apps/files_external/l10n/ja.json b/apps/files_external/l10n/ja.json
index 8134ed16cd5..b704504b088 100644
--- a/apps/files_external/l10n/ja.json
+++ b/apps/files_external/l10n/ja.json
@@ -15,6 +15,7 @@
"Unsatisfied backend parameters" : "バックエンドのためのパラメーターが不十分です。",
"Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です",
"Insufficient data: %s" : "データが不足しています: %s",
+ "%s" : "%s",
"Personal" : "個人",
"System" : "システム",
"Grant access" : "アクセスを許可",
diff --git a/apps/files_external/l10n/nl.js b/apps/files_external/l10n/nl.js
index 5051689216f..3200988ec2b 100644
--- a/apps/files_external/l10n/nl.js
+++ b/apps/files_external/l10n/nl.js
@@ -36,6 +36,15 @@ OC.L10N.register(
"(group)" : "(groep)",
"Admin defined" : "Beheerder gedefinieerd",
"Saved" : "Bewaard",
+ "Empty response from the server" : "Lege reactie van de server",
+ "Couldn't access. Please logout and login to activate this mount point" : "Geen toegang. Log uit en opnieuw in om dit koppelpunt te activeren",
+ "Couldn't get the information from the ownCloud server: {code} {type}" : "Kon geen informatie van de ownCloud server krijgen: {code} {type}",
+ "Couldn't get the list of external mount points: {type}" : "Kon geen overzicht met externe koppelpunten krijgen: {type}",
+ "There was an error with message: " : "Er was een fout met de volgende melding:",
+ "External mount error" : "Extern koppelpunt fout",
+ "goto-external-storage" : "goto-external-storage",
+ "Couldn't get the list of Windows network drive mount points: empty response from the server" : "Kon geen overzicht met Windows netwerk koppelpunten krijgen: lege reactie van de server",
+ "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Sommige van de geconfigureerde koppelpunten zijn niet verbonden. Klok op de rode rij(en) voor meer informatie",
"Access key" : "Access Key",
"Secret key" : "Geheime sleutel",
"Builtin" : "Ingebouwd",
diff --git a/apps/files_external/l10n/nl.json b/apps/files_external/l10n/nl.json
index d8a254bad1b..09b912445c1 100644
--- a/apps/files_external/l10n/nl.json
+++ b/apps/files_external/l10n/nl.json
@@ -34,6 +34,15 @@
"(group)" : "(groep)",
"Admin defined" : "Beheerder gedefinieerd",
"Saved" : "Bewaard",
+ "Empty response from the server" : "Lege reactie van de server",
+ "Couldn't access. Please logout and login to activate this mount point" : "Geen toegang. Log uit en opnieuw in om dit koppelpunt te activeren",
+ "Couldn't get the information from the ownCloud server: {code} {type}" : "Kon geen informatie van de ownCloud server krijgen: {code} {type}",
+ "Couldn't get the list of external mount points: {type}" : "Kon geen overzicht met externe koppelpunten krijgen: {type}",
+ "There was an error with message: " : "Er was een fout met de volgende melding:",
+ "External mount error" : "Extern koppelpunt fout",
+ "goto-external-storage" : "goto-external-storage",
+ "Couldn't get the list of Windows network drive mount points: empty response from the server" : "Kon geen overzicht met Windows netwerk koppelpunten krijgen: lege reactie van de server",
+ "Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Sommige van de geconfigureerde koppelpunten zijn niet verbonden. Klok op de rode rij(en) voor meer informatie",
"Access key" : "Access Key",
"Secret key" : "Geheime sleutel",
"Builtin" : "Ingebouwd",
diff --git a/apps/files_external/tests/env/entrypoint.sh b/apps/files_external/tests/env/entrypoint.sh
new file mode 100755
index 00000000000..6dd6ef23fb6
--- /dev/null
+++ b/apps/files_external/tests/env/entrypoint.sh
@@ -0,0 +1,274 @@
+#!/bin/bash
+set -e
+
+: ${CLUSTER:=ceph}
+: ${RGW_NAME:=$(hostname -s)}
+: ${MON_NAME:=$(hostname -s)}
+: ${RGW_CIVETWEB_PORT:=80}
+: ${OSD_SIZE:=100}
+
+: ${KEYSTONE_ADMIN_TOKEN:=admin}
+: ${KEYSTONE_ADMIN_PORT:=35357}
+: ${KEYSTONE_PUBLIC_PORT:=5001}
+
+: ${KEYSTONE_SERVICE:=${CLUSTER}}
+: ${KEYSTONE_ENDPOINT_REGION:=region}
+
+: ${KEYSTONE_ADMIN_USER:=admin}
+: ${KEYSTONE_ADMIN_TENANT:=admin}
+: ${KEYSTONE_ADMIN_PASS:=admin}
+
+ip_address=$(head -n1 /etc/hosts | cut -d" " -f1)
+: ${MON_IP:=${ip_address}}
+subnet=$(ip route | grep "src ${ip_address}" | cut -d" " -f1)
+: ${CEPH_NETWORK:=${subnet}}
+
+#######
+# MON #
+#######
+
+if [ ! -n "$CEPH_NETWORK" ]; then
+ echo "ERROR- CEPH_NETWORK must be defined as the name of the network for the OSDs"
+ exit 1
+fi
+
+if [ ! -n "$MON_IP" ]; then
+ echo "ERROR- MON_IP must be defined as the IP address of the monitor"
+ exit 1
+fi
+
+# bootstrap MON
+if [ ! -e /etc/ceph/ceph.conf ]; then
+ fsid=$(uuidgen)
+ cat <<ENDHERE >/etc/ceph/${CLUSTER}.conf
+[global]
+fsid = $fsid
+mon initial members = ${MON_NAME}
+mon host = ${MON_IP}
+auth cluster required = cephx
+auth service required = cephx
+auth client required = cephx
+osd crush chooseleaf type = 0
+osd journal size = 100
+osd pool default pg num = 8
+osd pool default pgp num = 8
+osd pool default size = 1
+public network = ${CEPH_NETWORK}
+cluster network = ${CEPH_NETWORK}
+debug ms = 1
+
+[mon]
+debug mon = 20
+debug paxos = 20
+debug auth = 20
+
+[osd]
+debug osd = 20
+debug filestore = 20
+debug journal = 20
+debug monc = 20
+
+[mds]
+debug mds = 20
+debug mds balancer = 20
+debug mds log = 20
+debug mds migrator = 20
+
+[client.radosgw.gateway]
+rgw keystone url = http://${MON_IP}:${KEYSTONE_ADMIN_PORT}
+rgw keystone admin token = ${KEYSTONE_ADMIN_TOKEN}
+rgw keystone accepted roles = _member_
+ENDHERE
+
+ # Generate administrator key
+ ceph-authtool /etc/ceph/${CLUSTER}.client.admin.keyring --create-keyring --gen-key -n client.admin --set-uid=0 --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow'
+
+ # Generate the mon. key
+ ceph-authtool /etc/ceph/${CLUSTER}.mon.keyring --create-keyring --gen-key -n mon. --cap mon 'allow *'
+
+ # Generate initial monitor map
+ monmaptool --create --add ${MON_NAME} ${MON_IP} --fsid ${fsid} /etc/ceph/monmap
+fi
+
+# If we don't have a monitor keyring, this is a new monitor
+if [ ! -e /var/lib/ceph/mon/${CLUSTER}-${MON_NAME}/keyring ]; then
+
+ if [ ! -e /etc/ceph/${CLUSTER}.client.admin.keyring ]; then
+ echo "ERROR- /etc/ceph/${CLUSTER}.client.admin.keyring must exist; get it from your existing mon"
+ exit 2
+ fi
+
+ if [ ! -e /etc/ceph/${CLUSTER}.mon.keyring ]; then
+ echo "ERROR- /etc/ceph/${CLUSTER}.mon.keyring must exist. You can extract it from your current monitor by running 'ceph auth get mon. -o /tmp/${CLUSTER}.mon.keyring'"
+ exit 3
+ fi
+
+ if [ ! -e /etc/ceph/monmap ]; then
+ echo "ERROR- /etc/ceph/monmap must exist. You can extract it from your current monitor by running 'ceph mon getmap -o /tmp/monmap'"
+ exit 4
+ fi
+
+ # Import the client.admin keyring and the monitor keyring into a new, temporary one
+ ceph-authtool /tmp/${CLUSTER}.mon.keyring --create-keyring --import-keyring /etc/ceph/${CLUSTER}.client.admin.keyring
+ ceph-authtool /tmp/${CLUSTER}.mon.keyring --import-keyring /etc/ceph/${CLUSTER}.mon.keyring
+
+ # Make the monitor directory
+ mkdir -p /var/lib/ceph/mon/${CLUSTER}-${MON_NAME}
+
+ # Prepare the monitor daemon's directory with the map and keyring
+ ceph-mon --mkfs -i ${MON_NAME} --monmap /etc/ceph/monmap --keyring /tmp/${CLUSTER}.mon.keyring
+
+ # Clean up the temporary key
+ rm /tmp/${CLUSTER}.mon.keyring
+fi
+
+# start MON
+ceph-mon -i ${MON_NAME} --public-addr ${MON_IP}:6789
+
+# change replica size
+ceph osd pool set rbd size 1
+
+
+#######
+# OSD #
+#######
+
+if [ ! -e /var/lib/ceph/osd/${CLUSTER}-0/keyring ]; then
+ # bootstrap OSD
+ mkdir -p /var/lib/ceph/osd/${CLUSTER}-0
+ # skip btrfs HACK if btrfs is already in place
+ if [ "$(stat -f /var/lib/ceph/osd/${CLUSTER}-0 2>/dev/null | grep btrfs | wc -l)" == "0" ]; then
+ # HACK create btrfs loopback device
+ echo "creating osd storage image"
+ dd if=/dev/zero of=/tmp/osddata bs=1M count=${OSD_SIZE}
+ mkfs.btrfs /tmp/osddata
+ echo "mounting via loopback"
+ mount -o loop /tmp/osddata /var/lib/ceph/osd/${CLUSTER}-0
+ echo "now mounted:"
+ mount
+ # end HACK
+ fi
+ echo "creating osd"
+ ceph osd create
+ echo "creating osd filesystem"
+ ceph-osd -i 0 --mkfs
+ echo "creating osd keyring"
+ ceph auth get-or-create osd.0 osd 'allow *' mon 'allow profile osd' -o /var/lib/ceph/osd/${CLUSTER}-0/keyring
+ echo "configuring osd crush"
+ ceph osd crush add 0 1 root=default host=$(hostname -s)
+ echo "adding osd keyring"
+ ceph-osd -i 0 -k /var/lib/ceph/osd/${CLUSTER}-0/keyring
+fi
+
+# start OSD
+echo "starting osd"
+ceph-osd --cluster=${CLUSTER} -i 0
+
+#sleep 10
+
+#######
+# MDS #
+#######
+
+if [ ! -e /var/lib/ceph/mds/${CLUSTER}-0/keyring ]; then
+ # create ceph filesystem
+ echo "creating osd pool"
+ ceph osd pool create cephfs_data 8
+ echo "creating osd pool metadata"
+ ceph osd pool create cephfs_metadata 8
+ echo "creating cephfs"
+ ceph fs new cephfs cephfs_metadata cephfs_data
+
+ # bootstrap MDS
+ mkdir -p /var/lib/ceph/mds/${CLUSTER}-0
+ echo "creating mds auth"
+ ceph auth get-or-create mds.0 mds 'allow' osd 'allow *' mon 'allow profile mds' > /var/lib/ceph/mds/${CLUSTER}-0/keyring
+fi
+
+# start MDS
+echo "starting mds"
+ceph-mds --cluster=${CLUSTER} -i 0
+
+#sleep 10
+
+
+#######
+# RGW #
+#######
+
+if [ ! -e /var/lib/ceph/radosgw/${RGW_NAME}/keyring ]; then
+ # bootstrap RGW
+ mkdir -p /var/lib/ceph/radosgw/${RGW_NAME}
+ echo "creating rgw auth"
+ ceph auth get-or-create client.radosgw.gateway osd 'allow rwx' mon 'allow rw' -o /var/lib/ceph/radosgw/${RGW_NAME}/keyring
+fi
+
+# start RGW
+echo "starting rgw"
+radosgw -c /etc/ceph/ceph.conf -n client.radosgw.gateway -k /var/lib/ceph/radosgw/${RGW_NAME}/keyring --rgw-socket-path="" --rgw-frontends="civetweb port=${RGW_CIVETWEB_PORT}"
+
+
+#######
+# API #
+#######
+
+# start ceph-rest-api
+echo "starting rest api"
+ceph-rest-api -n client.admin &
+
+############
+# Keystone #
+############
+
+if [ ! -e /etc/keystone/${CLUSTER}.conf ]; then
+ cat <<ENDHERE > /etc/keystone/${CLUSTER}.conf
+[DEFAULT]
+admin_token=${KEYSTONE_ADMIN_TOKEN}
+admin_port=${KEYSTONE_ADMIN_PORT}
+public_port=${KEYSTONE_PUBLIC_PORT}
+
+[database]
+connection = sqlite:////var/lib/keystone/keystone.db
+ENDHERE
+
+ # start Keystone
+ echo "starting keystone"
+ keystone-all --config-file /etc/keystone/${CLUSTER}.conf &
+
+ # wait until up
+ while ! nc ${MON_IP} ${KEYSTONE_ADMIN_PORT} </dev/null; do
+ sleep 1
+ done
+
+ export OS_SERVICE_TOKEN=${KEYSTONE_ADMIN_TOKEN}
+ export OS_SERVICE_ENDPOINT=http://${MON_IP}:${KEYSTONE_ADMIN_PORT}/v2.0
+
+ echo "creating keystone service ${KEYSTONE_SERVICE}"
+ keystone service-create --name ${KEYSTONE_SERVICE} --type object-store
+ echo "creating keystone endpoint ${KEYSTONE_SERVICE}"
+ keystone endpoint-create --service ${KEYSTONE_SERVICE} \
+ --region ${KEYSTONE_ENDPOINT_REGION} \
+ --publicurl http://${MON_IP}:${RGW_CIVETWEB_PORT}/swift/v1 \
+ --internalurl http://${MON_IP}:${RGW_CIVETWEB_PORT}/swift/v1 \
+ --adminurl http://${MON_IP}:${RGW_CIVETWEB_PORT}/swift/v1
+
+ echo "creating keystone user ${KEYSTONE_ADMIN_USER}"
+ keystone user-create --name=${KEYSTONE_ADMIN_USER} --pass=${KEYSTONE_ADMIN_PASS} --email=dev@null.com
+ echo "creating keystone tenant ${KEYSTONE_ADMIN_TENANT}"
+ keystone tenant-create --name=${KEYSTONE_ADMIN_TENANT} --description=admin
+ echo "adding keystone role _member_"
+ keystone user-role-add --user=${KEYSTONE_ADMIN_USER} --tenant=${KEYSTONE_ADMIN_TENANT} --role=_member_
+
+ echo "creating keystone role admin"
+ keystone role-create --name=admin
+ echo "adding keystone role admin"
+ keystone user-role-add --user=${KEYSTONE_ADMIN_USER} --tenant=${KEYSTONE_ADMIN_TENANT} --role=admin
+fi
+
+
+#########
+# WATCH #
+#########
+
+echo "watching ceph"
+exec ceph -w
diff --git a/apps/files_external/tests/env/start-swift-ceph.sh b/apps/files_external/tests/env/start-swift-ceph.sh
index 936bb667e94..f3707aa39f6 100755
--- a/apps/files_external/tests/env/start-swift-ceph.sh
+++ b/apps/files_external/tests/env/start-swift-ceph.sh
@@ -25,7 +25,7 @@ echo "Fetch recent ${docker_image} docker image"
docker pull ${docker_image}
# retrieve current folder to place the config in the parent folder
-thisFolder=`echo $0 | replace "env/start-swift-ceph.sh" ""`
+thisFolder=`echo $0 | sed 's#env/start-swift-ceph\.sh##'`
if [ -z "$thisFolder" ]; then
thisFolder="."
@@ -38,6 +38,7 @@ pass=testing
tenant=testenant
region=testregion
service=testceph
+endpointFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
container=`docker run -d \
-e KEYSTONE_PUBLIC_PORT=${port} \
@@ -46,7 +47,10 @@ container=`docker run -d \
-e KEYSTONE_ADMIN_TENANT=${tenant} \
-e KEYSTONE_ENDPOINT_REGION=${region} \
-e KEYSTONE_SERVICE=${service} \
- ${docker_image}`
+ -e OSD_SIZE=300 \
+ -v ${endpointFolder}/entrypoint.sh:/entrypoint.sh \
+ --privileged \
+ --entrypoint /entrypoint.sh ${docker_image}`
host=`docker inspect --format="{{.NetworkSettings.IPAddress}}" $container`
@@ -57,8 +61,9 @@ echo "${docker_image} container: $container"
echo $container >> $thisFolder/dockerContainerCeph.$EXECUTOR_NUMBER.swift
echo -n "Waiting for ceph initialization"
-if ! "$thisFolder"/env/wait-for-connection ${host} 80 60; then
- echo "[ERROR] Waited 60 seconds, no response" >&2
+if ! "$thisFolder"/env/wait-for-connection ${host} 80 600; then
+ echo "[ERROR] Waited 600 seconds, no response" >&2
+ docker logs $container
exit 1
fi
sleep 1
diff --git a/apps/files_sharing/tests/etagpropagation.php b/apps/files_sharing/tests/etagpropagation.php
index de9ce565394..5a917fd1c67 100644
--- a/apps/files_sharing/tests/etagpropagation.php
+++ b/apps/files_sharing/tests/etagpropagation.php
@@ -34,31 +34,7 @@ use OC\Files\View;
*
* @package OCA\Files_sharing\Tests
*/
-class EtagPropagation extends TestCase {
- /**
- * @var \OC\Files\View
- */
- private $rootView;
- protected $fileIds = []; // [$user=>[$path=>$id]]
- protected $fileEtags = []; // [$id=>$etag]
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- \OCA\Files_Sharing\Helper::registerHooks();
- }
-
- protected function setUp() {
- parent::setUp();
- $this->setUpShares();
- }
-
- protected function tearDown() {
- \OC_Hook::clear('OC_Filesystem', 'post_write');
- \OC_Hook::clear('OC_Filesystem', 'post_delete');
- \OC_Hook::clear('OC_Filesystem', 'post_rename');
- \OC_Hook::clear('OCP\Share', 'post_update_permissions');
- parent::tearDown();
- }
+class EtagPropagation extends PropagationTestCase {
/**
* "user1" is the admin who shares a folder "sub1/sub2/folder" with "user2" and "user3"
@@ -67,7 +43,7 @@ class EtagPropagation extends TestCase {
* "user2" reshares the subdir "sub1/sub2/folder/inside" with "user4"
* "user4" puts the received "inside" folder into "sub1/sub2/inside" (this is to check if it propagates across multiple subfolders)
*/
- private function setUpShares() {
+ protected function setUpShares() {
$this->fileIds[self::TEST_FILES_SHARING_API_USER1] = [];
$this->fileIds[self::TEST_FILES_SHARING_API_USER2] = [];
$this->fileIds[self::TEST_FILES_SHARING_API_USER3] = [];
@@ -136,58 +112,6 @@ class EtagPropagation extends TestCase {
}
}
- /**
- * @param string[] $users
- * @param string $subPath
- */
- private function assertEtagsChanged($users, $subPath = '') {
- $oldUser = \OC::$server->getUserSession()->getUser();
- foreach ($users as $user) {
- $this->loginAsUser($user);
- $id = $this->fileIds[$user][$subPath];
- $path = $this->rootView->getPath($id);
- $etag = $this->rootView->getFileInfo($path)->getEtag();
- $this->assertNotEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has changed');
- $this->fileEtags[$id] = $etag;
- }
- $this->loginAsUser($oldUser->getUID());
- }
-
- /**
- * @param string[] $users
- * @param string $subPath
- */
- private function assertEtagsNotChanged($users, $subPath = '') {
- $oldUser = \OC::$server->getUserSession()->getUser();
- foreach ($users as $user) {
- $this->loginAsUser($user);
- $id = $this->fileIds[$user][$subPath];
- $path = $this->rootView->getPath($id);
- $etag = $this->rootView->getFileInfo($path)->getEtag();
- $this->assertEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has not changed');
- $this->fileEtags[$id] = $etag;
- }
- $this->loginAsUser($oldUser->getUID());
- }
-
- /**
- * Assert that the etags for the root, /sub1 and /sub1/sub2 have changed
- *
- * @param string[] $users
- */
- private function assertEtagsForFoldersChanged($users) {
- $this->assertEtagsChanged($users);
-
- $this->assertEtagsChanged($users, 'sub1');
- $this->assertEtagsChanged($users, 'sub1/sub2');
- }
-
- private function assertAllUnchanged() {
- $users = [self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2,
- self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4];
- $this->assertEtagsNotChanged($users);
- }
-
public function testOwnerWritesToShare() {
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
Filesystem::file_put_contents('/sub1/sub2/folder/asd.txt', 'bar');
diff --git a/apps/files_sharing/tests/groupetagpropagation.php b/apps/files_sharing/tests/groupetagpropagation.php
new file mode 100644
index 00000000000..804d064eadb
--- /dev/null
+++ b/apps/files_sharing/tests/groupetagpropagation.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_sharing\Tests;
+
+use OC\Files\Filesystem;
+use OC\Files\View;
+
+/**
+ * @group DB
+ *
+ * @package OCA\Files_sharing\Tests
+ */
+class GroupEtagPropagation extends PropagationTestCase {
+ /**
+ * "user1" creates /test, /test/sub and shares with group1
+ * "user2" (in group1) reshares /test with group2 and reshared /test/sub with group3
+ * "user3" (in group 2)
+ * "user4" (in group 3)
+ */
+ protected function setUpShares() {
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER1] = [];
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER2] = [];
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER3] = [];
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER4] = [];
+
+ $this->rootView = new View('');
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
+ $view1 = new View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files');
+ $view1->mkdir('/test/sub');
+ $folderInfo = $view1->getFileInfo('/test');
+ \OCP\Share::shareItem('folder', $folderInfo->getId(), \OCP\Share::SHARE_TYPE_GROUP, 'group1', 31);
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER1][''] = $view1->getFileInfo('')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER1]['test'] = $view1->getFileInfo('test')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER1]['test/sub'] = $view1->getFileInfo('test/sub')->getId();
+
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
+ $view2 = new View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+ $folderInfo = $view2->getFileInfo('/test');
+ $subFolderInfo = $view2->getFileInfo('/test/sub');
+ \OCP\Share::shareItem('folder', $folderInfo->getId(), \OCP\Share::SHARE_TYPE_GROUP, 'group2', 31);
+ \OCP\Share::shareItem('folder', $subFolderInfo->getId(), \OCP\Share::SHARE_TYPE_GROUP, 'group3', 31);
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER2][''] = $view2->getFileInfo('')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER2]['test'] = $view2->getFileInfo('test')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER2]['test/sub'] = $view2->getFileInfo('test/sub')->getId();
+
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER3);
+ $view3 = new View('/' . self::TEST_FILES_SHARING_API_USER3 . '/files');
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER3][''] = $view3->getFileInfo('')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER3]['test'] = $view3->getFileInfo('test')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER3]['test/sub'] = $view3->getFileInfo('test/sub')->getId();
+
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER4);
+ $view4 = new View('/' . self::TEST_FILES_SHARING_API_USER4 . '/files');
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER4][''] = $view4->getFileInfo('')->getId();
+ $this->fileIds[self::TEST_FILES_SHARING_API_USER4]['sub'] = $view4->getFileInfo('sub')->getId();
+
+ foreach ($this->fileIds as $user => $ids) {
+ $this->loginAsUser($user);
+ foreach ($ids as $id) {
+ $path = $this->rootView->getPath($id);
+ $this->fileEtags[$id] = $this->rootView->getFileInfo($path)->getEtag();
+ }
+ }
+ }
+
+ public function testGroupReShareRecipientWrites() {
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER3);
+
+ Filesystem::file_put_contents('/test/sub/file.txt', 'asd');
+
+ $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]);
+
+ $this->assertAllUnchanged();
+ }
+
+ public function testGroupReShareSubFolderRecipientWrites() {
+ $this->loginAsUser(self::TEST_FILES_SHARING_API_USER4);
+
+ Filesystem::file_put_contents('/sub/file.txt', 'asd');
+
+ $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]);
+
+ $this->assertAllUnchanged();
+ }
+}
diff --git a/apps/files_sharing/tests/propagationtestcase.php b/apps/files_sharing/tests/propagationtestcase.php
new file mode 100644
index 00000000000..f397c1fb7a0
--- /dev/null
+++ b/apps/files_sharing/tests/propagationtestcase.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_sharing\Tests;
+
+abstract class PropagationTestCase extends TestCase {
+ /**
+ * @var \OC\Files\View
+ */
+ protected $rootView;
+ protected $fileIds = []; // [$user=>[$path=>$id]]
+ protected $fileEtags = []; // [$id=>$etag]
+
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+ \OCA\Files_Sharing\Helper::registerHooks();
+ }
+
+ protected function setUp() {
+ parent::setUp();
+ $this->setUpShares();
+ }
+
+ protected function tearDown() {
+ \OC_Hook::clear('OC_Filesystem', 'post_write');
+ \OC_Hook::clear('OC_Filesystem', 'post_delete');
+ \OC_Hook::clear('OC_Filesystem', 'post_rename');
+ \OC_Hook::clear('OCP\Share', 'post_update_permissions');
+ parent::tearDown();
+ }
+
+ abstract protected function setUpShares();
+
+ /**
+ * @param string[] $users
+ * @param string $subPath
+ */
+ protected function assertEtagsChanged($users, $subPath = '') {
+ $oldUser = \OC::$server->getUserSession()->getUser();
+ foreach ($users as $user) {
+ $this->loginAsUser($user);
+ $id = $this->fileIds[$user][$subPath];
+ $path = $this->rootView->getPath($id);
+ $etag = $this->rootView->getFileInfo($path)->getEtag();
+ $this->assertNotEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has changed');
+ $this->fileEtags[$id] = $etag;
+ }
+ $this->loginAsUser($oldUser->getUID());
+ }
+
+ /**
+ * @param string[] $users
+ * @param string $subPath
+ */
+ protected function assertEtagsNotChanged($users, $subPath = '') {
+ $oldUser = \OC::$server->getUserSession()->getUser();
+ foreach ($users as $user) {
+ $this->loginAsUser($user);
+ $id = $this->fileIds[$user][$subPath];
+ $path = $this->rootView->getPath($id);
+ $etag = $this->rootView->getFileInfo($path)->getEtag();
+ $this->assertEquals($this->fileEtags[$id], $etag, 'Failed asserting that the etag for "' . $subPath . '" of user ' . $user . ' has not changed');
+ $this->fileEtags[$id] = $etag;
+ }
+ $this->loginAsUser($oldUser->getUID());
+ }
+
+ /**
+ * Assert that the etags for the root, /sub1 and /sub1/sub2 have changed
+ *
+ * @param string[] $users
+ */
+ protected function assertEtagsForFoldersChanged($users) {
+ $this->assertEtagsChanged($users);
+
+ $this->assertEtagsChanged($users, 'sub1');
+ $this->assertEtagsChanged($users, 'sub1/sub2');
+ }
+
+ protected function assertAllUnchanged() {
+ $users = [self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2,
+ self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4];
+ $this->assertEtagsNotChanged($users);
+ }
+}
diff --git a/apps/files_sharing/tests/testcase.php b/apps/files_sharing/tests/testcase.php
index dc5b8ed79d9..a74ee83c25b 100644
--- a/apps/files_sharing/tests/testcase.php
+++ b/apps/files_sharing/tests/testcase.php
@@ -84,9 +84,15 @@ abstract class TestCase extends \Test\TestCase {
$groupBackend = new \OC_Group_Dummy();
$groupBackend->createGroup(self::TEST_FILES_SHARING_API_GROUP1);
$groupBackend->createGroup('group');
+ $groupBackend->createGroup('group1');
+ $groupBackend->createGroup('group2');
+ $groupBackend->createGroup('group3');
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER1, 'group');
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER2, 'group');
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER3, 'group');
+ $groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER2, 'group1');
+ $groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER3, 'group2');
+ $groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER4, 'group3');
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_GROUP1);
\OC_Group::useBackend($groupBackend);
diff --git a/apps/files_versions/command/expire.php b/apps/files_versions/command/expire.php
index 5db33f38772..88723690603 100644
--- a/apps/files_versions/command/expire.php
+++ b/apps/files_versions/command/expire.php
@@ -35,16 +35,6 @@ class Expire implements ICommand {
private $fileName;
/**
- * @var int|null
- */
- private $versionsSize;
-
- /**
- * @var int
- */
- private $neededSpace = 0;
-
- /**
* @var string
*/
private $user;
@@ -52,14 +42,10 @@ class Expire implements ICommand {
/**
* @param string $user
* @param string $fileName
- * @param int|null $versionsSize
- * @param int $neededSpace
*/
- function __construct($user, $fileName, $versionsSize = null, $neededSpace = 0) {
+ function __construct($user, $fileName) {
$this->user = $user;
$this->fileName = $fileName;
- $this->versionsSize = $versionsSize;
- $this->neededSpace = $neededSpace;
}
@@ -71,7 +57,7 @@ class Expire implements ICommand {
}
\OC_Util::setupFS($this->user);
- Storage::expire($this->fileName, $this->versionsSize, $this->neededSpace);
+ Storage::expire($this->fileName);
\OC_Util::tearDownFS();
}
}
diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php
index 29876b3e38a..21b5e9e0e7b 100644
--- a/apps/files_versions/lib/storage.php
+++ b/apps/files_versions/lib/storage.php
@@ -168,14 +168,7 @@ class Storage {
// create all parent folders
self::createMissingDirectories($filename, $users_view);
- $versionsSize = self::getVersionsSize($uid);
-
- // assumption: we need filesize($filename) for the new version +
- // some more free space for the modified file which might be
- // 1.5 times as large as the current version -> 2.5
- $neededSpace = $files_view->filesize($filename) * 2.5;
-
- self::scheduleExpire($uid, $filename, $versionsSize, $neededSpace);
+ self::scheduleExpire($uid, $filename);
// store a new version of a file
$mtime = $users_view->filemtime('files/' . $filename);
@@ -634,14 +627,12 @@ class Storage {
*
* @param string $uid owner of the file
* @param string $fileName file/folder for which to schedule expiration
- * @param int|null $versionsSize current versions size
- * @param int $neededSpace requested versions size
*/
- private static function scheduleExpire($uid, $fileName, $versionsSize = null, $neededSpace = 0) {
+ private static function scheduleExpire($uid, $fileName) {
// let the admin disable auto expire
$expiration = self::getExpiration();
if ($expiration->isEnabled()) {
- $command = new Expire($uid, $fileName, $versionsSize, $neededSpace);
+ $command = new Expire($uid, $fileName);
\OC::$server->getCommandBus()->push($command);
}
}
@@ -650,11 +641,9 @@ class Storage {
* Expire versions which exceed the quota
*
* @param string $filename
- * @param int|null $versionsSize
- * @param int $offset
* @return bool|int|null
*/
- public static function expire($filename, $versionsSize = null, $offset = 0) {
+ public static function expire($filename) {
$config = \OC::$server->getConfig();
$expiration = self::getExpiration();
@@ -680,9 +669,7 @@ class Storage {
}
// make sure that we have the current size of the version history
- if ( $versionsSize === null ) {
- $versionsSize = self::getVersionsSize($uid);
- }
+ $versionsSize = self::getVersionsSize($uid);
// calculate available space for version history
// subtract size of files and current versions size from quota
@@ -692,12 +679,12 @@ class Storage {
$rootInfo = $files_view->getFileInfo('/', false);
$free = $quota - $rootInfo['size']; // remaining free space for user
if ($free > 0) {
- $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - ($versionsSize + $offset); // how much space can be used for versions
+ $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
} else {
- $availableSpace = $free - $versionsSize - $offset;
+ $availableSpace = $free - $versionsSize;
}
} else {
- $availableSpace = $quota - $offset;
+ $availableSpace = $quota;
}
} else {
$availableSpace = PHP_INT_MAX;
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index a682467f52d..49cd565cf26 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -97,6 +97,15 @@ trait WebDav{
PHPUnit_Framework_Assert::assertEquals($content, (string)$this->response->getBody());
}
+ /**
+ * @Then /^Downloaded content when downloading file "([^"]*)" with range "([^"]*)" should be "([^"]*)"$/
+ */
+ public function downloadedContentWhenDownloadindShouldBe($fileSource, $range, $content){
+ $this->downloadFileWithRange($fileSource, $range);
+ $this->downloadedContentShouldBe($content);
+ }
+
+
/*Returns the elements of a propfind, $folderDepth requires 1 to see elements without children*/
public function listFolder($user, $path, $folderDepth){
$fullUrl = substr($this->baseUrl, 0, -4);
@@ -126,7 +135,7 @@ trait WebDav{
* @param \Behat\Gherkin\Node\TableNode|null $expectedElements
*/
public function checkElementList($user, $expectedElements){
- $elementList = $this->listFolder($user, '/', 2);
+ $elementList = $this->listFolder($user, '/', 3);
if ($expectedElements instanceof \Behat\Gherkin\Node\TableNode) {
$elementRows = $expectedElements->getRows();
$elementsSimplified = $this->simplifyArray($elementRows);
@@ -153,5 +162,17 @@ trait WebDav{
}
}
+ /**
+ * @Given User :user created a folder :destination
+ */
+ public function userCreatedAFolder($user, $destination){
+ try {
+ $this->response = $this->makeDavRequest($user, "MKCOL", $destination, []);
+ } catch (\GuzzleHttp\Exception\ServerException $e) {
+ // 4xx and 5xx responses cause an exception
+ $this->response = $e->getResponse();
+ }
+ }
+
}
diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature
index e00fd47baeb..31ba0d4ad7f 100644
--- a/build/integration/features/sharing-v1.feature
+++ b/build/integration/features/sharing-v1.feature
@@ -379,6 +379,47 @@ Feature: sharing
| /CHILD/child.txt |
And the HTTP status code should be "200"
+ Scenario: Share a file by multiple channels
+ Given As an "admin"
+ And user "user0" exists
+ And user "user1" exists
+ And user "user2" exists
+ And group "group0" exists
+ And user "user1" belongs to group "group0"
+ And user "user2" belongs to group "group0"
+ And user "user0" created a folder "/common"
+ And user "user0" created a folder "/common/sub"
+ And file "common" of user "user0" is shared with group "group0"
+ And file "textfile0.txt" of user "user1" is shared with user "user2"
+ And User "user1" moved file "/textfile0.txt" to "/common/textfile0.txt"
+ And User "user1" moved file "/common/textfile0.txt" to "/common/sub/textfile0.txt"
+ And As an "user2"
+ When Downloading file "/common/sub/textfile0.txt" with range "bytes=9-17"
+ Then Downloaded content should be "test text"
+ And Downloaded content when downloading file "/textfile0.txt" with range "bytes=9-17" should be "test text"
+ And user "user2" should see following elements
+ | /common/sub/textfile0.txt |
+
+ Scenario: Share a file by multiple channels
+ Given As an "admin"
+ And user "user0" exists
+ And user "user1" exists
+ And user "user2" exists
+ And group "group0" exists
+ And user "user1" belongs to group "group0"
+ And user "user2" belongs to group "group0"
+ And user "user0" created a folder "/common"
+ And user "user0" created a folder "/common/sub"
+ And file "common" of user "user0" is shared with group "group0"
+ And file "textfile0.txt" of user "user1" is shared with user "user2"
+ And User "user1" moved file "/textfile0.txt" to "/common/textfile0.txt"
+ And User "user1" moved file "/common/textfile0.txt" to "/common/sub/textfile0.txt"
+ And As an "user2"
+ When Downloading file "/textfile0.txt" with range "bytes=9-17"
+ Then Downloaded content should be "test text"
+ And user "user2" should see following elements
+ | /common/sub/textfile0.txt |
+
Scenario: Delete all group shares
Given As an "admin"
And user "user0" exists
diff --git a/config/config.sample.php b/config/config.sample.php
index c3abe3a2b87..9b10792944f 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -815,6 +815,19 @@ $CONFIG = array(
'enforce_home_folder_naming_rule' => true,
/**
+ * Comments
+ *
+ * Global settings for the Comments infrastructure
+ */
+
+/**
+ * Replaces the default Comments Manager Factory. This can be utilized if an
+ * own or 3rdParty CommentsManager should be used that – for instance – uses the
+ * filesystem instead of the database to keep the comments.
+ */
+'comments.managerFactory' => '\OC\Comments\ManagerFactory',
+
+/**
* Maintenance
*
* These options are for halting user activity when you are performing server
diff --git a/core/l10n/fr.js b/core/l10n/fr.js
index 4bb7aa82c97..f845185c525 100644
--- a/core/l10n/fr.js
+++ b/core/l10n/fr.js
@@ -266,6 +266,7 @@ OC.L10N.register(
"Please try again or contact your administrator." : "Veuillez réessayer ou contacter votre administrateur.",
"Log in" : "Se connecter",
"Wrong password. Reset it?" : "Mot de passe incorrect. Réinitialiser ?",
+ "Wrong password." : "Mot de passe incorrect.",
"Stay logged in" : "Rester connecté",
"Alternative Logins" : "Identifiants alternatifs",
"This ownCloud instance is currently in single user mode." : "Cette instance de ownCloud est actuellement en mode utilisateur unique.",
diff --git a/core/l10n/fr.json b/core/l10n/fr.json
index 02347de1707..070e03a9587 100644
--- a/core/l10n/fr.json
+++ b/core/l10n/fr.json
@@ -264,6 +264,7 @@
"Please try again or contact your administrator." : "Veuillez réessayer ou contacter votre administrateur.",
"Log in" : "Se connecter",
"Wrong password. Reset it?" : "Mot de passe incorrect. Réinitialiser ?",
+ "Wrong password." : "Mot de passe incorrect.",
"Stay logged in" : "Rester connecté",
"Alternative Logins" : "Identifiants alternatifs",
"This ownCloud instance is currently in single user mode." : "Cette instance de ownCloud est actuellement en mode utilisateur unique.",
diff --git a/core/l10n/ja.js b/core/l10n/ja.js
index 375b8c8a789..9d700b71d25 100644
--- a/core/l10n/ja.js
+++ b/core/l10n/ja.js
@@ -114,7 +114,7 @@ OC.L10N.register(
"No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "メモリキャッシュが設定されていません。パフォーマンスを向上するために、可能であれば memcache を設定してください。 より詳しい情報については、<a href=\"{docLink}\">documentation</a> を参照してください。",
"/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "/dev/urandom は PHP から読み取ることができず、この状態はセキュリティの観点からおすすめできません。より詳しい情報については、<a href=\"{docLink}\">documentation</a> を参照ください。",
"Your PHP version ({version}) is no longer <a href=\"{phpLink}\">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "ご利用のPHPのバージョン ({version}) は、<a href=\"{phpLink}\">PHPでサポート</a> されていません。 我々は、PHPから提供されている新しいバージョンにアップグレードし、それによるセキュリティの確保とパフォーマンスのメリットを受けられることを強くお勧めします。",
- "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "リバースプロキシーのヘッダー設定が間違っているか、または信頼されたプロキシーからownCloudにアクセスしていません。もし、信頼されたプロキシーからアクセスしているのでないなら、セキュリティに問題があり、ownCloudを詐称したIPアドレスから攻撃者に対して見えるよう許可していることになります。詳細な情報は、<a href=\"{docLink}\">ドキュメント</a>を確認してください。",
+ "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "リバースプロキシのヘッダー設定が間違っているか、または信頼されたプロキシからownCloudにアクセスしていません。もし、信頼されたプロキシからアクセスしているのでないなら、セキュリティに問題があり、ownCloudを詐称したIPアドレスから攻撃者に対して見えるよう許可していることになります。詳細な情報は、<a href=\"{docLink}\">ドキュメント</a>を確認してください。",
"Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the <a href=\"{wikiLink}\">memcached wiki about both modules</a>." : "Memcached は分散キャッシュとして設定されていますが、間違った\"memcache\"のPHPモジュールがインストールされています。 \\OC\\Memcache\\Memcached は、\"memcached\" のみをサポートしていますが、\"memcache\" ではありません。<a href=\"{wikiLink}\">memcached wiki で両方のモジュール</a> を見てください。",
"Error occurred while checking server setup" : "サーバー設定のチェック中にエラーが発生しました",
"The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "\"{header}\" HTTP ヘッダは \"{expected}\" に設定されていません。これは潜在的なセキュリティリスクもしくはプライバシーリスクとなる可能性があるため、この設定を見直すことをおすすめします。",
@@ -261,6 +261,7 @@ OC.L10N.register(
"Please try again or contact your administrator." : "もう一度試してみるか、管理者に問い合わせてください。",
"Log in" : "ログイン",
"Wrong password. Reset it?" : "パスワードが間違っています。リセットしますか?",
+ "Wrong password." : "パスワードが間違っています。",
"Stay logged in" : "ログインしたままにする",
"Alternative Logins" : "代替ログイン",
"This ownCloud instance is currently in single user mode." : "このownCloudインスタンスは、現在シングルユーザーモードです。",
diff --git a/core/l10n/ja.json b/core/l10n/ja.json
index 337358d57f7..68930ad01c3 100644
--- a/core/l10n/ja.json
+++ b/core/l10n/ja.json
@@ -112,7 +112,7 @@
"No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "メモリキャッシュが設定されていません。パフォーマンスを向上するために、可能であれば memcache を設定してください。 より詳しい情報については、<a href=\"{docLink}\">documentation</a> を参照してください。",
"/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "/dev/urandom は PHP から読み取ることができず、この状態はセキュリティの観点からおすすめできません。より詳しい情報については、<a href=\"{docLink}\">documentation</a> を参照ください。",
"Your PHP version ({version}) is no longer <a href=\"{phpLink}\">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "ご利用のPHPのバージョン ({version}) は、<a href=\"{phpLink}\">PHPでサポート</a> されていません。 我々は、PHPから提供されている新しいバージョンにアップグレードし、それによるセキュリティの確保とパフォーマンスのメリットを受けられることを強くお勧めします。",
- "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "リバースプロキシーのヘッダー設定が間違っているか、または信頼されたプロキシーからownCloudにアクセスしていません。もし、信頼されたプロキシーからアクセスしているのでないなら、セキュリティに問題があり、ownCloudを詐称したIPアドレスから攻撃者に対して見えるよう許可していることになります。詳細な情報は、<a href=\"{docLink}\">ドキュメント</a>を確認してください。",
+ "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href=\"{docLink}\">documentation</a>." : "リバースプロキシのヘッダー設定が間違っているか、または信頼されたプロキシからownCloudにアクセスしていません。もし、信頼されたプロキシからアクセスしているのでないなら、セキュリティに問題があり、ownCloudを詐称したIPアドレスから攻撃者に対して見えるよう許可していることになります。詳細な情報は、<a href=\"{docLink}\">ドキュメント</a>を確認してください。",
"Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the <a href=\"{wikiLink}\">memcached wiki about both modules</a>." : "Memcached は分散キャッシュとして設定されていますが、間違った\"memcache\"のPHPモジュールがインストールされています。 \\OC\\Memcache\\Memcached は、\"memcached\" のみをサポートしていますが、\"memcache\" ではありません。<a href=\"{wikiLink}\">memcached wiki で両方のモジュール</a> を見てください。",
"Error occurred while checking server setup" : "サーバー設定のチェック中にエラーが発生しました",
"The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "\"{header}\" HTTP ヘッダは \"{expected}\" に設定されていません。これは潜在的なセキュリティリスクもしくはプライバシーリスクとなる可能性があるため、この設定を見直すことをおすすめします。",
@@ -259,6 +259,7 @@
"Please try again or contact your administrator." : "もう一度試してみるか、管理者に問い合わせてください。",
"Log in" : "ログイン",
"Wrong password. Reset it?" : "パスワードが間違っています。リセットしますか?",
+ "Wrong password." : "パスワードが間違っています。",
"Stay logged in" : "ログインしたままにする",
"Alternative Logins" : "代替ログイン",
"This ownCloud instance is currently in single user mode." : "このownCloudインスタンスは、現在シングルユーザーモードです。",
diff --git a/core/l10n/nl.js b/core/l10n/nl.js
index 9e2bc2d345e..563a10ff409 100644
--- a/core/l10n/nl.js
+++ b/core/l10n/nl.js
@@ -266,6 +266,7 @@ OC.L10N.register(
"Please try again or contact your administrator." : "Probeer het opnieuw of neem contact op met uw beheerder.",
"Log in" : "Meld u aan",
"Wrong password. Reset it?" : "Onjuist wachtwoord. Resetten?",
+ "Wrong password." : "Onjuist wachtwoord.",
"Stay logged in" : "Ingelogd blijven",
"Alternative Logins" : "Alternatieve inlogs",
"This ownCloud instance is currently in single user mode." : "Deze ownCloud werkt momenteel in enkele gebruiker modus.",
diff --git a/core/l10n/nl.json b/core/l10n/nl.json
index d565545ce38..012fd33ba90 100644
--- a/core/l10n/nl.json
+++ b/core/l10n/nl.json
@@ -264,6 +264,7 @@
"Please try again or contact your administrator." : "Probeer het opnieuw of neem contact op met uw beheerder.",
"Log in" : "Meld u aan",
"Wrong password. Reset it?" : "Onjuist wachtwoord. Resetten?",
+ "Wrong password." : "Onjuist wachtwoord.",
"Stay logged in" : "Ingelogd blijven",
"Alternative Logins" : "Alternatieve inlogs",
"This ownCloud instance is currently in single user mode." : "Deze ownCloud werkt momenteel in enkele gebruiker modus.",
diff --git a/lib/private/app/appmanager.php b/lib/private/app/appmanager.php
index f826c8ba0c7..8ae93f98832 100644
--- a/lib/private/app/appmanager.php
+++ b/lib/private/app/appmanager.php
@@ -148,6 +148,13 @@ class AppManager implements IAppManager {
return false;
} else {
$groupIds = json_decode($enabled);
+
+ if (!is_array($groupIds)) {
+ $jsonError = json_last_error();
+ \OC::$server->getLogger()->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
+ return false;
+ }
+
$userGroups = $this->groupManager->getUserGroupIds($user);
foreach ($userGroups as $groupId) {
if (array_search($groupId, $groupIds) !== false) {
diff --git a/lib/private/comments/comment.php b/lib/private/comments/comment.php
new file mode 100644
index 00000000000..219e7ec8e4b
--- /dev/null
+++ b/lib/private/comments/comment.php
@@ -0,0 +1,357 @@
+<?php
+
+namespace OC\Comments;
+
+use OCP\Comments\IComment;
+use OCP\Comments\IllegalIDChangeException;
+
+class Comment implements IComment {
+
+ protected $data = [
+ 'id' => '',
+ 'parentId' => '0',
+ 'topmostParentId' => '0',
+ 'childrenCount' => '0',
+ 'message' => '',
+ 'verb' => '',
+ 'actorType' => '',
+ 'actorId' => '',
+ 'objectType' => '',
+ 'objectId' => '',
+ 'creationDT' => null,
+ 'latestChildDT' => null,
+ ];
+
+ /**
+ * Comment constructor.
+ *
+ * @param [] $data optional, array with keys according to column names from
+ * the comments database scheme
+ */
+ public function __construct(array $data = null) {
+ if(is_array($data)) {
+ $this->fromArray($data);
+ }
+ }
+
+ /**
+ * returns the ID of the comment
+ *
+ * It may return an empty string, if the comment was not stored.
+ * It is expected that the concrete Comment implementation gives an ID
+ * by itself (e.g. after saving).
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getId() {
+ return $this->data['id'];
+ }
+
+ /**
+ * sets the ID of the comment and returns itself
+ *
+ * It is only allowed to set the ID only, if the current id is an empty
+ * string (which means it is not stored in a database, storage or whatever
+ * the concrete implementation does), or vice versa. Changing a given ID is
+ * not permitted and must result in an IllegalIDChangeException.
+ *
+ * @param string $id
+ * @return IComment
+ * @throws IllegalIDChangeException
+ * @since 9.0.0
+ */
+ public function setId($id) {
+ if(!is_string($id)) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+
+ $id = trim($id);
+ if($this->data['id'] === '' || ($this->data['id'] !== '' && $id === '')) {
+ $this->data['id'] = $id;
+ return $this;
+ }
+
+ throw new IllegalIDChangeException('Not allowed to assign a new ID to an already saved comment.');
+ }
+
+ /**
+ * returns the parent ID of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getParentId() {
+ return $this->data['parentId'];
+ }
+
+ /**
+ * sets the parent ID and returns itself
+ *
+ * @param string $parentId
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setParentId($parentId) {
+ if(!is_string($parentId)) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['parentId'] = trim($parentId);
+ return $this;
+ }
+
+ /**
+ * returns the topmost parent ID of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getTopmostParentId() {
+ return $this->data['topmostParentId'];
+ }
+
+
+ /**
+ * sets the topmost parent ID and returns itself
+ *
+ * @param string $id
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setTopmostParentId($id) {
+ if(!is_string($id)) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['topmostParentId'] = trim($id);
+ return $this;
+ }
+
+ /**
+ * returns the number of children
+ *
+ * @return int
+ * @since 9.0.0
+ */
+ public function getChildrenCount() {
+ return $this->data['childrenCount'];
+ }
+
+ /**
+ * sets the number of children
+ *
+ * @param int $count
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setChildrenCount($count) {
+ if(!is_int($count)) {
+ throw new \InvalidArgumentException('Integer expected.');
+ }
+ $this->data['childrenCount'] = $count;
+ return $this;
+ }
+
+ /**
+ * returns the message of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getMessage() {
+ return $this->data['message'];
+ }
+
+ /**
+ * sets the message of the comment and returns itself
+ *
+ * @param string $message
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setMessage($message) {
+ if(!is_string($message)) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['message'] = trim($message);
+ return $this;
+ }
+
+ /**
+ * returns the verb of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getVerb() {
+ return $this->data['verb'];
+ }
+
+ /**
+ * sets the verb of the comment, e.g. 'comment' or 'like'
+ *
+ * @param string $verb
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setVerb($verb) {
+ if(!is_string($verb) || !trim($verb)) {
+ throw new \InvalidArgumentException('Non-empty String expected.');
+ }
+ $this->data['verb'] = trim($verb);
+ return $this;
+ }
+
+ /**
+ * returns the actor type
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getActorType() {
+ return $this->data['actorType'];
+ }
+
+ /**
+ * returns the actor ID
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getActorId() {
+ return $this->data['actorId'];
+ }
+
+ /**
+ * sets (overwrites) the actor type and id
+ *
+ * @param string $actorType e.g. 'user'
+ * @param string $actorId e.g. 'zombie234'
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setActor($actorType, $actorId) {
+ if(
+ !is_string($actorType) || !trim($actorType)
+ || !is_string($actorId) || !trim($actorId)
+ ) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['actorType'] = trim($actorType);
+ $this->data['actorId'] = trim($actorId);
+ return $this;
+ }
+
+ /**
+ * returns the creation date of the comment.
+ *
+ * If not explicitly set, it shall default to the time of initialization.
+ *
+ * @return \DateTime
+ * @since 9.0.0
+ */
+ public function getCreationDateTime() {
+ return $this->data['creationDT'];
+ }
+
+ /**
+ * sets the creation date of the comment and returns itself
+ *
+ * @param \DateTime $timestamp
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setCreationDateTime(\DateTime $timestamp) {
+ $this->data['creationDT'] = $timestamp;
+ return $this;
+ }
+
+ /**
+ * returns the timestamp of the most recent child
+ *
+ * @return int
+ * @since 9.0.0
+ */
+ public function getLatestChildDateTime() {
+ return $this->data['latestChildDT'];
+ }
+
+ /**
+ * sets the date of the most recent child
+ *
+ * @param \DateTime $dateTime
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setLatestChildDateTime(\DateTime $dateTime = null) {
+ $this->data['latestChildDT'] = $dateTime;
+ return $this;
+ }
+
+ /**
+ * returns the object type the comment is attached to
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getObjectType() {
+ return $this->data['objectType'];
+ }
+
+ /**
+ * returns the object id the comment is attached to
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getObjectId() {
+ return $this->data['objectId'];
+ }
+
+ /**
+ * sets (overwrites) the object of the comment
+ *
+ * @param string $objectType e.g. 'file'
+ * @param string $objectId e.g. '16435'
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setObject($objectType, $objectId) {
+ if(
+ !is_string($objectType) || !trim($objectType)
+ || !is_string($objectId) || !trim($objectId)
+ ) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['objectType'] = trim($objectType);
+ $this->data['objectId'] = trim($objectId);
+ return $this;
+ }
+
+ /**
+ * sets the comment data based on an array with keys as taken from the
+ * database.
+ *
+ * @param [] $data
+ * @return IComment
+ */
+ protected function fromArray($data) {
+ foreach(array_keys($data) as $key) {
+ // translate DB keys to internal setter names
+ $setter = 'set' . str_replace('_', '', ucwords($key,'_'));
+ $setter = str_replace('Timestamp', 'DateTime', $setter);
+
+ if(method_exists($this, $setter)) {
+ $this->$setter($data[$key]);
+ }
+ }
+
+ foreach(['actor', 'object'] as $role) {
+ if(isset($data[$role . '_type']) && isset($data[$role . '_id'])) {
+ $setter = 'set' . ucfirst($role);
+ $this->$setter($data[$role . '_type'], $data[$role . '_id']);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/lib/private/comments/manager.php b/lib/private/comments/manager.php
new file mode 100644
index 00000000000..09e59f28370
--- /dev/null
+++ b/lib/private/comments/manager.php
@@ -0,0 +1,549 @@
+<?php
+
+namespace OC\Comments;
+
+use Doctrine\DBAL\Exception\DriverException;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\NotFoundException;
+use OCP\IDBConnection;
+use OCP\ILogger;
+
+class Manager implements ICommentsManager {
+
+ /** @var IDBConnection */
+ protected $dbConn;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IComment[] */
+ protected $commentsCache = [];
+
+ public function __construct(
+ IDBConnection $dbConn,
+ ILogger $logger
+ ) {
+ $this->dbConn = $dbConn;
+ $this->logger = $logger;
+ }
+
+ /**
+ * converts data base data into PHP native, proper types as defined by
+ * IComment interface.
+ *
+ * @param array $data
+ * @return array
+ */
+ protected function normalizeDatabaseData(array $data) {
+ $data['id'] = strval($data['id']);
+ $data['parent_id'] = strval($data['parent_id']);
+ $data['topmost_parent_id'] = strval($data['topmost_parent_id']);
+ $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
+ $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
+ $data['children_count'] = intval($data['children_count']);
+ return $data;
+ }
+
+ /**
+ * prepares a comment for an insert or update operation after making sure
+ * all necessary fields have a value assigned.
+ *
+ * @param IComment $comment
+ * @return IComment returns the same updated IComment instance as provided
+ * by parameter for convenience
+ * @throws \UnexpectedValueException
+ */
+ protected function prepareCommentForDatabaseWrite(IComment $comment) {
+ if( !$comment->getActorType()
+ || !$comment->getActorId()
+ || !$comment->getObjectType()
+ || !$comment->getObjectId()
+ || !$comment->getVerb()
+ ) {
+ throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving');
+ }
+
+ if($comment->getId() === '') {
+ $comment->setChildrenCount(0);
+ $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC')));
+ $comment->setLatestChildDateTime(null);
+ }
+
+ if(is_null($comment->getCreationDateTime())) {
+ $comment->setCreationDateTime(new \DateTime());
+ }
+
+ if($comment->getParentId() !== '0') {
+ $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId()));
+ } else {
+ $comment->setTopmostParentId('0');
+ }
+
+ $this->cache($comment);
+
+ return $comment;
+ }
+
+ /**
+ * returns the topmost parent id of a given comment identified by ID
+ *
+ * @param string $id
+ * @return string
+ * @throws NotFoundException
+ */
+ protected function determineTopmostParentId($id) {
+ $comment = $this->get($id);
+ if($comment->getParentId() === '0') {
+ return $comment->getId();
+ } else {
+ return $this->determineTopmostParentId($comment->getId());
+ }
+ }
+
+ /**
+ * updates child information of a comment
+ *
+ * @param string $id
+ * @param \DateTime $cDateTime the date time of the most recent child
+ * @throws NotFoundException
+ */
+ protected function updateChildrenInformation($id, \DateTime $cDateTime) {
+ $qb = $this->dbConn->getQueryBuilder();
+ $query = $qb->select($qb->createFunction('COUNT(`id`)'))
+ ->from('comments')
+ ->where($qb->expr()->eq('parent_id', $qb->createParameter('id')))
+ ->setParameter('id', $id);
+
+ $resultStatement = $query->execute();
+ $data = $resultStatement->fetch(\PDO::FETCH_NUM);
+ $resultStatement->closeCursor();
+ $children = intval($data[0]);
+
+ $comment = $this->get($id);
+ $comment->setChildrenCount($children);
+ $comment->setLatestChildDateTime($cDateTime);
+ $this->save($comment);
+ }
+
+ /**
+ * Tests whether actor or object type and id parameters are acceptable.
+ * Throws exception if not.
+ *
+ * @param string $role
+ * @param string $type
+ * @param string $id
+ * @throws \InvalidArgumentException
+ */
+ protected function checkRoleParameters($role, $type, $id) {
+ if(
+ !is_string($type) || empty($type)
+ || !is_string($id) || empty($id)
+ ) {
+ throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
+ }
+ }
+
+ /**
+ * run-time caches a comment
+ *
+ * @param IComment $comment
+ */
+ protected function cache(IComment $comment) {
+ $id = $comment->getId();
+ if(empty($id)) {
+ return;
+ }
+ $this->commentsCache[strval($id)] = $comment;
+ }
+
+ /**
+ * removes an entry from the comments run time cache
+ *
+ * @param mixed $id the comment's id
+ */
+ protected function uncache($id) {
+ $id = strval($id);
+ if (isset($this->commentsCache[$id])) {
+ unset($this->commentsCache[$id]);
+ }
+ }
+
+ /**
+ * returns a comment instance
+ *
+ * @param string $id the ID of the comment
+ * @return IComment
+ * @throws NotFoundException
+ * @throws \InvalidArgumentException
+ * @since 9.0.0
+ */
+ public function get($id) {
+ if(intval($id) === 0) {
+ throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
+ }
+
+ if(isset($this->commentsCache[$id])) {
+ return $this->commentsCache[$id];
+ }
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $resultStatement = $qb->select('*')
+ ->from('comments')
+ ->where($qb->expr()->eq('id', $qb->createParameter('id')))
+ ->setParameter('id', $id, \PDO::PARAM_INT)
+ ->execute();
+
+ $data = $resultStatement->fetch();
+ $resultStatement->closeCursor();
+ if(!$data) {
+ throw new NotFoundException();
+ }
+
+ $comment = new Comment($this->normalizeDatabaseData($data));
+ $this->cache($comment);
+ return $comment;
+ }
+
+ /**
+ * returns the comment specified by the id and all it's child comments.
+ * At this point of time, we do only support one level depth.
+ *
+ * @param string $id
+ * @param int $limit max number of entries to return, 0 returns all
+ * @param int $offset the start entry
+ * @return array
+ * @since 9.0.0
+ *
+ * The return array looks like this
+ * [
+ * 'comment' => IComment, // root comment
+ * 'replies' =>
+ * [
+ * 0 =>
+ * [
+ * 'comment' => IComment,
+ * 'replies' => []
+ * ]
+ * 1 =>
+ * [
+ * 'comment' => IComment,
+ * 'replies'=> []
+ * ],
+ * …
+ * ]
+ * ]
+ */
+ public function getTree($id, $limit = 0, $offset = 0) {
+ $tree = [];
+ $tree['comment'] = $this->get($id);
+ $tree['replies'] = [];
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $query = $qb->select('*')
+ ->from('comments')
+ ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id')))
+ ->orderBy('creation_timestamp', 'DESC')
+ ->setParameter('id', $id);
+
+ if($limit > 0) {
+ $query->setMaxResults($limit);
+ }
+ if($offset > 0) {
+ $query->setFirstResult($offset);
+ }
+
+ $resultStatement = $query->execute();
+ while($data = $resultStatement->fetch()) {
+ $comment = new Comment($this->normalizeDatabaseData($data));
+ $this->cache($comment);
+ $tree['replies'][] = [
+ 'comment' => $comment,
+ 'replies' => []
+ ];
+ }
+ $resultStatement->closeCursor();
+
+ return $tree;
+ }
+
+ /**
+ * returns comments for a specific object (e.g. a file).
+ *
+ * The sort order is always newest to oldest.
+ *
+ * @param string $objectType the object type, e.g. 'files'
+ * @param string $objectId the id of the object
+ * @param int $limit optional, number of maximum comments to be returned. if
+ * not specified, all comments are returned.
+ * @param int $offset optional, starting point
+ * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
+ * that may be returned
+ * @return IComment[]
+ * @since 9.0.0
+ */
+ public function getForObject(
+ $objectType,
+ $objectId,
+ $limit = 0,
+ $offset = 0,
+ \DateTime $notOlderThan = null
+ ) {
+ $comments = [];
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $query = $qb->select('*')
+ ->from('comments')
+ ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
+ ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
+ ->orderBy('creation_timestamp', 'DESC')
+ ->setParameter('type', $objectType)
+ ->setParameter('id', $objectId);
+
+ if($limit > 0) {
+ $query->setMaxResults($limit);
+ }
+ if($offset > 0) {
+ $query->setFirstResult($offset);
+ }
+ if(!is_null($notOlderThan)) {
+ $query
+ ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
+ ->setParameter('notOlderThan', $notOlderThan, 'datetime');
+ }
+
+ $resultStatement = $query->execute();
+ while($data = $resultStatement->fetch()) {
+ $comment = new Comment($this->normalizeDatabaseData($data));
+ $this->cache($comment);
+ $comments[] = $comment;
+ }
+ $resultStatement->closeCursor();
+
+ return $comments;
+ }
+
+ /**
+ * @param $objectType string the object type, e.g. 'files'
+ * @param $objectId string the id of the object
+ * @return Int
+ * @since 9.0.0
+ */
+ public function getNumberOfCommentsForObject($objectType, $objectId) {
+ $qb = $this->dbConn->getQueryBuilder();
+ $query = $qb->select($qb->createFunction('COUNT(`id`)'))
+ ->from('comments')
+ ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
+ ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
+ ->setParameter('type', $objectType)
+ ->setParameter('id', $objectId);
+
+ $resultStatement = $query->execute();
+ $data = $resultStatement->fetch(\PDO::FETCH_NUM);
+ $resultStatement->closeCursor();
+ return intval($data[0]);
+ }
+
+ /**
+ * creates a new comment and returns it. At this point of time, it is not
+ * saved in the used data storage. Use save() after setting other fields
+ * of the comment (e.g. message or verb).
+ *
+ * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorId a user id
+ * @param string $objectType the object type the comment is attached to
+ * @param string $objectId the object id the comment is attached to
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function create($actorType, $actorId, $objectType, $objectId) {
+ $comment = new Comment();
+ $comment
+ ->setActor($actorType, $actorId)
+ ->setObject($objectType, $objectId);
+ return $comment;
+ }
+
+ /**
+ * permanently deletes the comment specified by the ID
+ *
+ * When the comment has child comments, their parent ID will be changed to
+ * the parent ID of the item that is to be deleted.
+ *
+ * @param string $id
+ * @return bool
+ * @throws \InvalidArgumentException
+ * @since 9.0.0
+ */
+ public function delete($id) {
+ if(!is_string($id)) {
+ throw new \InvalidArgumentException('Parameter must be string');
+ }
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $query = $qb->delete('comments')
+ ->where($qb->expr()->eq('id', $qb->createParameter('id')))
+ ->setParameter('id', $id);
+
+ try {
+ $affectedRows = $query->execute();
+ $this->uncache($id);
+ } catch (DriverException $e) {
+ $this->logger->logException($e, ['app' => 'core_comments']);
+ return false;
+ }
+ return ($affectedRows > 0);
+ }
+
+ /**
+ * saves the comment permanently and returns it
+ *
+ * if the supplied comment has an empty ID, a new entry comment will be
+ * saved and the instance updated with the new ID.
+ *
+ * Otherwise, an existing comment will be updated.
+ *
+ * Throws NotFoundException when a comment that is to be updated does not
+ * exist anymore at this point of time.
+ *
+ * @param IComment $comment
+ * @return bool
+ * @throws NotFoundException
+ * @since 9.0.0
+ */
+ public function save(IComment $comment) {
+ if($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
+ $result = $this->insert($comment);
+ } else {
+ $result = $this->update($comment);
+ }
+
+ if($result && !!$comment->getParentId()) {
+ $this->updateChildrenInformation(
+ $comment->getParentId(),
+ $comment->getCreationDateTime()
+ );
+ $this->cache($comment);
+ }
+
+ return $result;
+ }
+
+ /**
+ * inserts the provided comment in the database
+ *
+ * @param IComment $comment
+ * @return bool
+ */
+ protected function insert(IComment &$comment) {
+ $qb = $this->dbConn->getQueryBuilder();
+ $affectedRows = $qb
+ ->insert('comments')
+ ->values([
+ 'parent_id' => $qb->createNamedParameter($comment->getParentId()),
+ 'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
+ 'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
+ 'actor_type' => $qb->createNamedParameter($comment->getActorType()),
+ 'actor_id' => $qb->createNamedParameter($comment->getActorId()),
+ 'message' => $qb->createNamedParameter($comment->getMessage()),
+ 'verb' => $qb->createNamedParameter($comment->getVerb()),
+ 'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
+ 'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
+ 'object_type' => $qb->createNamedParameter($comment->getObjectType()),
+ 'object_id' => $qb->createNamedParameter($comment->getObjectId()),
+ ])
+ ->execute();
+
+ if ($affectedRows > 0) {
+ $comment->setId(strval($qb->getLastInsertId()));
+ }
+
+ return $affectedRows > 0;
+ }
+
+ /**
+ * updates a Comment data row
+ *
+ * @param IComment $comment
+ * @return bool
+ * @throws NotFoundException
+ */
+ protected function update(IComment $comment) {
+ $qb = $this->dbConn->getQueryBuilder();
+ $affectedRows = $qb
+ ->update('comments')
+ ->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
+ ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
+ ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
+ ->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
+ ->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
+ ->set('message', $qb->createNamedParameter($comment->getMessage()))
+ ->set('verb', $qb->createNamedParameter($comment->getVerb()))
+ ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
+ ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
+ ->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
+ ->set('object_id', $qb->createNamedParameter($comment->getObjectId()))
+ ->where($qb->expr()->eq('id', $qb->createParameter('id')))
+ ->setParameter('id', $comment->getId())
+ ->execute();
+
+ if($affectedRows === 0) {
+ throw new NotFoundException('Comment to update does ceased to exist');
+ }
+
+ return $affectedRows > 0;
+ }
+
+ /**
+ * removes references to specific actor (e.g. on user delete) of a comment.
+ * The comment itself must not get lost/deleted.
+ *
+ * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorId a user id
+ * @return boolean
+ * @since 9.0.0
+ */
+ public function deleteReferencesOfActor($actorType, $actorId) {
+ $this->checkRoleParameters('Actor', $actorType, $actorId);
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $affectedRows = $qb
+ ->update('comments')
+ ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
+ ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
+ ->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
+ ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
+ ->setParameter('type', $actorType)
+ ->setParameter('id', $actorId)
+ ->execute();
+
+ $this->commentsCache = [];
+
+ return is_int($affectedRows);
+ }
+
+ /**
+ * deletes all comments made of a specific object (e.g. on file delete)
+ *
+ * @param string $objectType the object type (e.g. 'file')
+ * @param string $objectId e.g. the file id
+ * @return boolean
+ * @since 9.0.0
+ */
+ public function deleteCommentsAtObject($objectType, $objectId) {
+ $this->checkRoleParameters('Object', $objectType, $objectId);
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $affectedRows = $qb
+ ->delete('comments')
+ ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
+ ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
+ ->setParameter('type', $objectType)
+ ->setParameter('id', $objectId)
+ ->execute();
+
+ $this->commentsCache = [];
+
+ return is_int($affectedRows);
+ }
+}
diff --git a/lib/private/comments/managerfactory.php b/lib/private/comments/managerfactory.php
new file mode 100644
index 00000000000..41978d0cf4b
--- /dev/null
+++ b/lib/private/comments/managerfactory.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace OC\Comments;
+
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\ICommentsManagerFactory;
+
+
+class ManagerFactory implements ICommentsManagerFactory {
+
+ /**
+ * creates and returns an instance of the ICommentsManager
+ *
+ * @return ICommentsManager
+ * @since 9.0.0
+ */
+ public function getManager() {
+ return new Manager(
+ \OC::$server->getDatabaseConnection(),
+ \OC::$server->getLogger()
+ );
+ }
+}
diff --git a/lib/private/files/storage/wrapper/availability.php b/lib/private/files/storage/wrapper/availability.php
index d6ce78f6e44..d2fbbbacf75 100644
--- a/lib/private/files/storage/wrapper/availability.php
+++ b/lib/private/files/storage/wrapper/availability.php
@@ -399,7 +399,6 @@ class Availability extends Wrapper {
/** {@inheritdoc} */
public function getOwner($path) {
- $this->checkAvailability();
try {
return parent::getOwner($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
diff --git a/lib/private/lock/abstractlockingprovider.php b/lib/private/lock/abstractlockingprovider.php
index c7a29380efe..db5f1c72dd7 100644
--- a/lib/private/lock/abstractlockingprovider.php
+++ b/lib/private/lock/abstractlockingprovider.php
@@ -28,6 +28,8 @@ use OCP\Lock\ILockingProvider;
* to release any left over locks at the end of the request
*/
abstract class AbstractLockingProvider implements ILockingProvider {
+ const TTL = 3600; // how long until we clear stray locks in seconds
+
protected $acquiredLocks = [
'shared' => [],
'exclusive' => []
diff --git a/lib/private/lock/dblockingprovider.php b/lib/private/lock/dblockingprovider.php
index 90657e6725f..1b5142a90d2 100644
--- a/lib/private/lock/dblockingprovider.php
+++ b/lib/private/lock/dblockingprovider.php
@@ -51,8 +51,6 @@ class DBLockingProvider extends AbstractLockingProvider {
private $sharedLocks = [];
- const TTL = 3600; // how long until we clear stray locks in seconds
-
/**
* Check if we have an open shared lock for a path
*
@@ -235,10 +233,10 @@ class DBLockingProvider extends AbstractLockingProvider {
/**
* cleanup empty locks
*/
- public function cleanEmptyLocks() {
+ public function cleanExpiredLocks() {
$expire = $this->timeFactory->getTime();
$this->connection->executeUpdate(
- 'DELETE FROM `*PREFIX*file_locks` WHERE `lock` = 0 AND `ttl` < ?',
+ 'DELETE FROM `*PREFIX*file_locks` WHERE `ttl` < ?',
[$expire]
);
}
@@ -262,7 +260,7 @@ class DBLockingProvider extends AbstractLockingProvider {
public function __destruct() {
try {
- $this->cleanEmptyLocks();
+ $this->cleanExpiredLocks();
} catch (\Exception $e) {
// If the table is missing, the clean up was successful
if ($this->connection->tableExists('file_locks')) {
diff --git a/lib/private/lock/memcachelockingprovider.php b/lib/private/lock/memcachelockingprovider.php
index e4158dcdfdf..af95200d159 100644
--- a/lib/private/lock/memcachelockingprovider.php
+++ b/lib/private/lock/memcachelockingprovider.php
@@ -21,6 +21,7 @@
namespace OC\Lock;
+use OCP\IMemcacheTTL;
use OCP\Lock\LockedException;
use OCP\IMemcache;
@@ -37,6 +38,12 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
$this->memcache = $memcache;
}
+ private function setTTL($path) {
+ if ($this->memcache instanceof IMemcacheTTL) {
+ $this->memcache->setTTL($path, self::TTL);
+ }
+ }
+
/**
* @param string $path
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
@@ -69,6 +76,7 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
throw new LockedException($path);
}
}
+ $this->setTTL($path);
$this->markAcquire($path, $type);
}
@@ -106,6 +114,7 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
throw new LockedException($path);
}
}
+ $this->setTTL($path);
$this->markChange($path, $targetType);
}
}
diff --git a/lib/private/memcache/redis.php b/lib/private/memcache/redis.php
index 83be662eabf..68b62e7534a 100644
--- a/lib/private/memcache/redis.php
+++ b/lib/private/memcache/redis.php
@@ -25,9 +25,9 @@
namespace OC\Memcache;
-use OCP\IMemcache;
+use OCP\IMemcacheTTL;
-class Redis extends Cache implements IMemcache {
+class Redis extends Cache implements IMemcacheTTL {
/**
* @var \Redis $cache
*/
@@ -195,6 +195,10 @@ class Redis extends Cache implements IMemcache {
return false;
}
+ public function setTTL($key, $ttl) {
+ self::$cache->expire($this->getNamespace() . $key, $ttl);
+ }
+
static public function isAvailable() {
return extension_loaded('redis')
&& version_compare(phpversion('redis'), '2.2.5', '>=');
diff --git a/lib/private/server.php b/lib/private/server.php
index 6692e6f6bbf..8439500706d 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -528,6 +528,13 @@ class Server extends SimpleContainer implements IServerContainer {
});
return $manager;
});
+ $this->registerService('CommentsManager', function(Server $c) {
+ $config = $c->getConfig();
+ $factoryClass = $config->getSystemValue('comments.managerFactory', '\OC\Comments\ManagerFactory');
+ /** @var \OCP\Comments\ICommentsManagerFactory $factory */
+ $factory = new $factoryClass();
+ return $factory->getManager();
+ });
$this->registerService('EventDispatcher', function() {
return new EventDispatcher();
});
@@ -1122,6 +1129,13 @@ class Server extends SimpleContainer implements IServerContainer {
}
/**
+ * @return \OCP\Comments\ICommentsManager
+ */
+ public function getCommentsManager() {
+ return $this->query('CommentsManager');
+ }
+
+ /**
* @return \OC\IntegrityCheck\Checker
*/
public function getIntegrityCodeChecker() {
diff --git a/lib/private/setup/mysql.php b/lib/private/setup/mysql.php
index f2d2b15cd90..e8b88eb3489 100644
--- a/lib/private/setup/mysql.php
+++ b/lib/private/setup/mysql.php
@@ -89,15 +89,28 @@ class MySQL extends AbstractDatabase {
* @throws \OC\DatabaseSetupException
*/
private function connect() {
- $type = 'mysql';
+
$connectionParams = array(
- 'host' => $this->dbHost,
- 'user' => $this->dbUser,
- 'password' => $this->dbPassword,
- 'tablePrefix' => $this->tablePrefix,
+ 'host' => $this->dbHost,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'tablePrefix' => $this->tablePrefix,
);
+
+ // adding port support
+ if (strpos($this->dbHost, ':')) {
+ // Host variable may carry a port or socket.
+ list($host, $portOrSocket) = explode(':', $this->dbHost, 2);
+ if (ctype_digit($portOrSocket)) {
+ $connectionParams['port'] = $portOrSocket;
+ } else {
+ $connectionParams['unix_socket'] = $portOrSocket;
+ }
+ $connectionParams['host'] = $host;
+ }
+
$cf = new ConnectionFactory();
- return $cf->getConnection($type, $connectionParams);
+ return $cf->getConnection('mysql', $connectionParams);
}
/**
diff --git a/lib/private/user/user.php b/lib/private/user/user.php
index d827097ee39..6c89dd06f77 100644
--- a/lib/private/user/user.php
+++ b/lib/private/user/user.php
@@ -189,6 +189,8 @@ class User implements IUser {
// Delete the users entry in the storage table
\OC\Files\Cache\Storage::remove('home::' . $this->uid);
+
+ \OC::$server->getCommentsManager()->deleteReferencesOfActor('user', $this->uid);
}
if ($this->emitter) {
diff --git a/lib/public/comments/icomment.php b/lib/public/comments/icomment.php
index c8f407624a0..7924ec8d5f6 100644
--- a/lib/public/comments/icomment.php
+++ b/lib/public/comments/icomment.php
@@ -5,7 +5,7 @@ namespace OCP\Comments;
/**
* Interface IComment
*
- * This class represents a comment and offers methods for modification.
+ * This class represents a comment
*
* @package OCP\Comments
* @since 9.0.0
@@ -49,7 +49,6 @@ interface IComment {
/**
* sets the parent ID and returns itself
- *
* @param string $parentId
* @return IComment
* @since 9.0.0
@@ -57,6 +56,24 @@ interface IComment {
public function setParentId($parentId);
/**
+ * returns the topmost parent ID of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getTopmostParentId();
+
+
+ /**
+ * sets the topmost parent ID and returns itself
+ *
+ * @param string $id
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setTopmostParentId($id);
+
+ /**
* returns the number of children
*
* @return int
diff --git a/lib/public/comments/icommentsmanager.php b/lib/public/comments/icommentsmanager.php
index ebf7a34bf27..7626ffd6351 100644
--- a/lib/public/comments/icommentsmanager.php
+++ b/lib/public/comments/icommentsmanager.php
@@ -13,6 +13,17 @@ namespace OCP\Comments;
interface ICommentsManager {
/**
+ * @const DELETED_USER type and id for a user that has been deleted
+ * @see deleteReferencesOfActor
+ * @since 9.0.0
+ *
+ * To be used as replacement for user type actors in deleteReferencesOfActor().
+ *
+ * User interfaces shall show "Deleted user" as display name, if needed.
+ */
+ const DELETED_USER = 'deleted_user';
+
+ /**
* returns a comment instance
*
* @param string $id the ID of the comment
@@ -28,7 +39,7 @@ interface ICommentsManager {
* @param string $id
* @param int $limit max number of entries to return, 0 returns all
* @param int $offset the start entry
- * @return []
+ * @return array
* @since 9.0.0
*
* The return array looks like this
@@ -73,7 +84,6 @@ interface ICommentsManager {
* @param \DateTime $notOlderThan optional, timestamp of the oldest comments
* that may be returned
* @return IComment[]
- * @throws NotFoundException in case the requested type or id is not present
* @since 9.0.0
*/
public function getForObject(
@@ -88,7 +98,6 @@ interface ICommentsManager {
* @param $objectType string the object type, e.g. 'files'
* @param $objectId string the id of the object
* @return Int
- * @throws NotFoundException in case the requested type or id is not present
* @since 9.0.0
*/
public function getNumberOfCommentsForObject($objectType, $objectId);
@@ -130,17 +139,20 @@ interface ICommentsManager {
* Throws NotFoundException when a comment that is to be updated does not
* exist anymore at this point of time.
*
- * @param IComment
+ * @param IComment $comment
* @return bool
* @throws NotFoundException
* @since 9.0.0
*/
- public function save(&$comment);
+ public function save(IComment $comment);
/**
* removes references to specific actor (e.g. on user delete) of a comment.
* The comment itself must not get lost/deleted.
*
+ * A 'user' type actor (type and id) should get replaced by the
+ * value of the DELETED_USER constant of this interface.
+ *
* @param string $actorType the actor type (e.g. 'user')
* @param string $actorId a user id
* @return boolean
diff --git a/lib/public/comments/icommentsmanagerfactory.php b/lib/public/comments/icommentsmanagerfactory.php
new file mode 100644
index 00000000000..6718dd39ba0
--- /dev/null
+++ b/lib/public/comments/icommentsmanagerfactory.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace OCP\Comments;
+
+/**
+ * Interface ICommentsManagerFactory
+ *
+ * This class is responsible for instantiating and returning an ICommentsManager
+ * instance.
+ *
+ * @package OCP\Comments
+ * @since 9.0.0
+ */
+interface ICommentsManagerFactory {
+
+ /**
+ * creates and returns an instance of the ICommentsManager
+ *
+ * @return ICommentsManager
+ * @since 9.0.0
+ */
+ public function getManager();
+}
diff --git a/lib/public/imemcachettl.php b/lib/public/imemcachettl.php
new file mode 100644
index 00000000000..a4a7a530549
--- /dev/null
+++ b/lib/public/imemcachettl.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCP;
+
+/**
+ * Interface for memcache backends that support setting ttl after the value is set
+ *
+ * @since 9.0.0
+ */
+interface IMemcacheTTL extends IMemcache {
+ /**
+ * Set the ttl for an existing value
+ *
+ * @param string $key
+ * @param int $ttl time to live in seconds
+ * @since 9.0.0
+ */
+ public function setTTL($key, $ttl);
+}
diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php
index 7cb2672254b..267e5dc4d31 100644
--- a/lib/public/iservercontainer.php
+++ b/lib/public/iservercontainer.php
@@ -472,6 +472,12 @@ interface IServerContainer {
public function getNotificationManager();
/**
+ * @return \OCP\Comments\ICommentsManager
+ * @since 9.0.0
+ */
+ public function getCommentsManager();
+
+ /**
* Returns the system-tag manager
*
* @return \OCP\SystemTag\ISystemTagManager
diff --git a/settings/l10n/ja.js b/settings/l10n/ja.js
index 9d6705aff3f..2d317695ea5 100644
--- a/settings/l10n/ja.js
+++ b/settings/l10n/ja.js
@@ -20,7 +20,7 @@ OC.L10N.register(
"Unable to add user to group %s" : "ユーザーをグループ %s に追加できません",
"Unable to remove user from group %s" : "ユーザーをグループ %s から削除できません",
"Couldn't update app." : "アプリをアップデートできませんでした。",
- "Wrong password" : "無効なパスワード",
+ "Wrong password" : "パスワードが間違っています",
"No user supplied" : "ユーザーが指定されていません",
"Please provide an admin recovery password, otherwise all user data will be lost" : "リカバリ用の管理者パスワードを入力してください。そうでない場合は、全ユーザーのデータが失われます。",
"Wrong admin recovery password. Please check the password and try again." : "リカバリ用の管理者パスワードが間違っています。パスワードを確認して再度実行してください。",
diff --git a/settings/l10n/ja.json b/settings/l10n/ja.json
index aa22a5ce9b3..d7138dbf3be 100644
--- a/settings/l10n/ja.json
+++ b/settings/l10n/ja.json
@@ -18,7 +18,7 @@
"Unable to add user to group %s" : "ユーザーをグループ %s に追加できません",
"Unable to remove user from group %s" : "ユーザーをグループ %s から削除できません",
"Couldn't update app." : "アプリをアップデートできませんでした。",
- "Wrong password" : "無効なパスワード",
+ "Wrong password" : "パスワードが間違っています",
"No user supplied" : "ユーザーが指定されていません",
"Please provide an admin recovery password, otherwise all user data will be lost" : "リカバリ用の管理者パスワードを入力してください。そうでない場合は、全ユーザーのデータが失われます。",
"Wrong admin recovery password. Please check the password and try again." : "リカバリ用の管理者パスワードが間違っています。パスワードを確認して再度実行してください。",
diff --git a/tests/lib/comments/comment.php b/tests/lib/comments/comment.php
new file mode 100644
index 00000000000..02adea8729e
--- /dev/null
+++ b/tests/lib/comments/comment.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Test\Comments;
+
+use Test\TestCase;
+
+class Test_Comments_Comment extends TestCase
+{
+
+ public function testSettersValidInput() {
+ $comment = new \OC\Comments\Comment();
+
+ $id = 'comment23';
+ $parentId = 'comment11.5';
+ $childrenCount = 6;
+ $message = 'I like to comment comment';
+ $verb = 'comment';
+ $actor = ['type' => 'user', 'id' => 'alice'];
+ $creationDT = new \DateTime();
+ $latestChildDT = new \DateTime('yesterday');
+ $object = ['type' => 'file', 'id' => 'file64'];
+
+ $comment
+ ->setId($id)
+ ->setParentId($parentId)
+ ->setChildrenCount($childrenCount)
+ ->setMessage($message)
+ ->setVerb($verb)
+ ->setActor($actor['type'], $actor['id'])
+ ->setCreationDateTime($creationDT)
+ ->setLatestChildDateTime($latestChildDT)
+ ->setObject($object['type'], $object['id']);
+
+ $this->assertSame($id, $comment->getId());
+ $this->assertSame($parentId, $comment->getParentId());
+ $this->assertSame($childrenCount, $comment->getChildrenCount());
+ $this->assertSame($message, $comment->getMessage());
+ $this->assertSame($verb, $comment->getVerb());
+ $this->assertSame($actor['type'], $comment->getActorType());
+ $this->assertSame($actor['id'], $comment->getActorId());
+ $this->assertSame($creationDT, $comment->getCreationDateTime());
+ $this->assertSame($latestChildDT, $comment->getLatestChildDateTime());
+ $this->assertSame($object['type'], $comment->getObjectType());
+ $this->assertSame($object['id'], $comment->getObjectId());
+ }
+
+ /**
+ * @expectedException \OCP\Comments\IllegalIDChangeException
+ */
+ public function testSetIdIllegalInput() {
+ $comment = new \OC\Comments\Comment();
+
+ $comment->setId('c23');
+ $comment->setId('c17');
+ }
+
+ public function testResetId() {
+ $comment = new \OC\Comments\Comment();
+ $comment->setId('c23');
+ $comment->setId('');
+
+ $this->assertSame('', $comment->getId());
+ }
+
+ public function simpleSetterProvider() {
+ return [
+ ['Id', true],
+ ['ParentId', true],
+ ['Message', true],
+ ['Verb', true],
+ ['Verb', ''],
+ ['ChildrenCount', true],
+ ];
+ }
+
+ /**
+ * @dataProvider simpleSetterProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSimpleSetterInvalidInput($field, $input) {
+ $comment = new \OC\Comments\Comment();
+ $setter = 'set' . $field;
+
+ $comment->$setter($input);
+ }
+
+ public function roleSetterProvider() {
+ return [
+ ['Actor', true, true],
+ ['Actor', 'user', true],
+ ['Actor', true, 'alice'],
+ ['Actor', ' ', ' '],
+ ['Object', true, true],
+ ['Object', 'file', true],
+ ['Object', true, 'file64'],
+ ['Object', ' ', ' '],
+ ];
+ }
+
+ /**
+ * @dataProvider roleSetterProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetRoleInvalidInput($role, $type, $id){
+ $comment = new \OC\Comments\Comment();
+ $setter = 'set' . $role;
+ $comment->$setter($type, $id);
+ }
+
+
+
+}
diff --git a/tests/lib/comments/fakefactory.php b/tests/lib/comments/fakefactory.php
new file mode 100644
index 00000000000..837bcb10585
--- /dev/null
+++ b/tests/lib/comments/fakefactory.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Test\Comments;
+
+/**
+ * Class FakeFactory
+ */
+class FakeFactory implements \OCP\Comments\ICommentsManagerFactory {
+
+ public function getManager() {
+ return new FakeManager();
+ }
+}
diff --git a/tests/lib/comments/fakemanager.php b/tests/lib/comments/fakemanager.php
new file mode 100644
index 00000000000..e5cf58dda4f
--- /dev/null
+++ b/tests/lib/comments/fakemanager.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Test\Comments;
+
+/**
+ * Class FakeManager
+ */
+class FakeManager implements \OCP\Comments\ICommentsManager {
+
+ public function get($id) {}
+
+ public function getTree($id, $limit = 0, $offset = 0) {}
+
+ public function getForObject(
+ $objectType,
+ $objectId,
+ $limit = 0,
+ $offset = 0,
+ \DateTime $notOlderThan = null
+ ) {}
+
+ public function getNumberOfCommentsForObject($objectType, $objectId) {}
+
+ public function create($actorType, $actorId, $objectType, $objectId) {}
+
+ public function delete($id) {}
+
+ public function save(\OCP\Comments\IComment $comment) {}
+
+ public function deleteReferencesOfActor($actorType, $actorId) {}
+
+ public function deleteCommentsAtObject($objectType, $objectId) {}
+}
diff --git a/tests/lib/comments/manager.php b/tests/lib/comments/manager.php
new file mode 100644
index 00000000000..248de683253
--- /dev/null
+++ b/tests/lib/comments/manager.php
@@ -0,0 +1,564 @@
+<?php
+
+namespace Test\Comments;
+
+use OCP\Comments\ICommentsManager;
+use Test\TestCase;
+
+/**
+ * Class Test_Comments_Manager
+ *
+ * @group DB
+ */
+class Test_Comments_Manager extends TestCase
+{
+
+ public function setUp() {
+ parent::setUp();
+
+ $sql = \OC::$server->getDatabaseConnection()->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`');
+ \OC::$server->getDatabaseConnection()->prepare($sql)->execute();
+ }
+
+ protected function addDatabaseEntry($parentId, $topmostParentId, $creationDT = null, $latestChildDT = null) {
+ if(is_null($creationDT)) {
+ $creationDT = new \DateTime();
+ }
+ if(is_null($latestChildDT)) {
+ $latestChildDT = new \DateTime('yesterday');
+ }
+
+ $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
+ $qb
+ ->insert('comments')
+ ->values([
+ 'parent_id' => $qb->createNamedParameter($parentId),
+ 'topmost_parent_id' => $qb->createNamedParameter($topmostParentId),
+ 'children_count' => $qb->createNamedParameter(2),
+ 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_id' => $qb->createNamedParameter('alice'),
+ 'message' => $qb->createNamedParameter('nice one'),
+ 'verb' => $qb->createNamedParameter('comment'),
+ 'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
+ 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
+ 'object_type' => $qb->createNamedParameter('file'),
+ 'object_id' => $qb->createNamedParameter('file64'),
+ ])
+ ->execute();
+
+ return $qb->getLastInsertId();
+ }
+
+ protected function getManager() {
+ $factory = new \OC\Comments\ManagerFactory();
+ return $factory->getManager();
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testGetCommentNotFound() {
+ $manager = $this->getManager();
+ $manager->get('22');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetCommentNotFoundInvalidInput() {
+ $manager = $this->getManager();
+ $manager->get('unexisting22');
+ }
+
+ public function testGetComment() {
+ $manager = $this->getManager();
+
+ $creationDT = new \DateTime();
+ $latestChildDT = new \DateTime('yesterday');
+
+ $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
+ $qb
+ ->insert('comments')
+ ->values([
+ 'parent_id' => $qb->createNamedParameter('2'),
+ 'topmost_parent_id' => $qb->createNamedParameter('1'),
+ 'children_count' => $qb->createNamedParameter(2),
+ 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_id' => $qb->createNamedParameter('alice'),
+ 'message' => $qb->createNamedParameter('nice one'),
+ 'verb' => $qb->createNamedParameter('comment'),
+ 'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
+ 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
+ 'object_type' => $qb->createNamedParameter('file'),
+ 'object_id' => $qb->createNamedParameter('file64'),
+ ])
+ ->execute();
+
+ $id = strval($qb->getLastInsertId());
+
+ $comment = $manager->get($id);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getId(), $id);
+ $this->assertSame($comment->getParentId(), '2');
+ $this->assertSame($comment->getTopmostParentId(), '1');
+ $this->assertSame($comment->getChildrenCount(), 2);
+ $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorId(), 'alice');
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getVerb(), 'comment');
+ $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectId(), 'file64');
+ $this->assertEquals($comment->getCreationDateTime(), $creationDT);
+ $this->assertEquals($comment->getLatestChildDateTime(), $latestChildDT);
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testGetTreeNotFound() {
+ $manager = $this->getManager();
+ $manager->getTree('22');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetTreeNotFoundInvalidIpnut() {
+ $manager = $this->getManager();
+ $manager->getTree('unexisting22');
+ }
+
+ public function testGetTree() {
+ $headId = $this->addDatabaseEntry(0, 0);
+
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
+ $id = $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
+
+ $manager = $this->getManager();
+ $tree = $manager->getTree($headId);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($headId));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 3);
+
+ // one level deep
+ foreach($tree['replies'] as $reply) {
+ $this->assertTrue($reply['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($reply['comment']->getId(), strval($id));
+ $this->assertSame(count($reply['replies']), 0);
+ $id--;
+ }
+ }
+
+ public function testGetTreeNoReplies() {
+ $id = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+ $tree = $manager->getTree($id);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($id));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 0);
+
+ // one level deep
+ foreach($tree['replies'] as $reply) {
+ throw new \Exception('This ain`t happen');
+ }
+ }
+
+ public function testGetTreeWithLimitAndOffset() {
+ $headId = $this->addDatabaseEntry(0, 0);
+
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
+ $idToVerify = $this->addDatabaseEntry($headId, $headId, new \DateTime());
+
+ $manager = $this->getManager();
+
+ for ($offset = 0; $offset < 3; $offset += 2) {
+ $tree = $manager->getTree(strval($headId), 2, $offset);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($headId));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 2);
+
+ // one level deep
+ foreach ($tree['replies'] as $reply) {
+ $this->assertTrue($reply['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($reply['comment']->getId(), strval($idToVerify));
+ $this->assertSame(count($reply['replies']), 0);
+ $idToVerify--;
+ }
+ }
+ }
+
+ public function testGetForObject() {
+ $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+ $comments = $manager->getForObject('file', 'file64');
+
+ $this->assertTrue(is_array($comments));
+ $this->assertSame(count($comments), 1);
+ $this->assertTrue($comments[0] instanceof \OCP\Comments\IComment);
+ $this->assertSame($comments[0]->getMessage(), 'nice one');
+ }
+
+ public function testGetForObjectWithLimitAndOffset() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours'));
+ $this->addDatabaseEntry(1, 1, new \DateTime('-4 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours'));
+ $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime());
+
+ $manager = $this->getManager();
+ $offset = 0;
+ do {
+ $comments = $manager->getForObject('file', 'file64', 3, $offset);
+
+ $this->assertTrue(is_array($comments));
+ foreach($comments as $comment) {
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getId(), strval($idToVerify));
+ $idToVerify--;
+ }
+ $offset += 3;
+ } while(count($comments) > 0);
+ }
+
+ public function testGetForObjectWithDateTimeConstraint() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours'));
+ $id1 = $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $id2 = $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+
+ $manager = $this->getManager();
+ $comments = $manager->getForObject('file', 'file64', 0, 0, new \DateTime('-4 hours'));
+
+ $this->assertSame(count($comments), 2);
+ $this->assertSame($comments[0]->getId(), strval($id2));
+ $this->assertSame($comments[1]->getId(), strval($id1));
+ }
+
+ public function testGetForObjectWithLimitAndOffsetAndDateTimeConstraint() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-7 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(1, 1, new \DateTime('-5 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours'));
+ $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime());
+
+ $manager = $this->getManager();
+ $offset = 0;
+ do {
+ $comments = $manager->getForObject('file', 'file64', 3, $offset, new \DateTime('-4 hours'));
+
+ $this->assertTrue(is_array($comments));
+ foreach($comments as $comment) {
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getId(), strval($idToVerify));
+ $this->assertTrue(intval($comment->getId()) >= 4);
+ $idToVerify--;
+ }
+ $offset += 3;
+ } while(count($comments) > 0);
+ }
+
+ public function testGetNumberOfCommentsForObject() {
+ for($i = 1; $i < 5; $i++) {
+ $this->addDatabaseEntry(0, 0);
+ }
+
+ $manager = $this->getManager();
+
+ $amount = $manager->getNumberOfCommentsForObject('untype', '00');
+ $this->assertSame($amount, 0);
+
+ $amount = $manager->getNumberOfCommentsForObject('file', 'file64');
+ $this->assertSame($amount, 4);
+ }
+
+ public function invalidCreateArgsProvider() {
+ return [
+ ['', 'aId-1', 'oType-1', 'oId-1'],
+ ['aType-1', '', 'oType-1', 'oId-1'],
+ ['aType-1', 'aId-1', '', 'oId-1'],
+ ['aType-1', 'aId-1', 'oType-1', ''],
+ [1, 'aId-1', 'oType-1', 'oId-1'],
+ ['aType-1', 1, 'oType-1', 'oId-1'],
+ ['aType-1', 'aId-1', 1, 'oId-1'],
+ ['aType-1', 'aId-1', 'oType-1', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidCreateArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testCreateCommentInvalidArguments($aType, $aId, $oType, $oId) {
+ $manager = $this->getManager();
+ $manager->create($aType, $aId, $oType, $oId);
+ }
+
+ public function testCreateComment() {
+ $actorType = 'bot';
+ $actorId = 'bob';
+ $objectType = 'weather';
+ $objectId = 'bielefeld';
+
+ $comment = $this->getManager()->create($actorType, $actorId, $objectType, $objectId);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getActorType(), $actorType);
+ $this->assertSame($comment->getActorId(), $actorId);
+ $this->assertSame($comment->getObjectType(), $objectType);
+ $this->assertSame($comment->getObjectId(), $objectId);
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testDelete() {
+ $manager = $this->getManager();
+
+ $done = $manager->delete('404');
+ $this->assertFalse($done);
+
+ $done = $manager->delete('%');
+ $this->assertFalse($done);
+
+ $done = $manager->delete('');
+ $this->assertFalse($done);
+
+ $id = strval($this->addDatabaseEntry(0, 0));
+ $comment = $manager->get($id);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $done = $manager->delete($id);
+ $this->assertTrue($done);
+ $manager->get($id);
+ }
+
+ public function testSaveNew() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $saveSuccessful = $manager->save($comment);
+ $this->assertTrue($saveSuccessful);
+ $this->assertTrue($comment->getId() !== '');
+ $this->assertTrue($comment->getId() !== '0');
+ $this->assertTrue(!is_null($comment->getCreationDateTime()));
+
+ $loadedComment = $manager->get($comment->getId());
+ $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
+ $this->assertEquals($comment->getCreationDateTime(), $loadedComment->getCreationDateTime());
+ }
+
+ public function testSaveUpdate() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $manager->save($comment);
+
+ $comment->setMessage('very beautiful, I am really so much impressed!');
+ $manager->save($comment);
+
+ $loadedComment = $manager->get($comment->getId());
+ $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testSaveUpdateException() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $manager->save($comment);
+
+ $manager->delete($comment->getId());
+ $comment->setMessage('very beautiful, I am really so much impressed!');
+ $manager->save($comment);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testSaveIncomplete() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment->setMessage('from no one to nothing');
+ $manager->save($comment);
+ }
+
+ public function testSaveAsChild() {
+ $id = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ for($i = 0; $i < 3; $i++) {
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setParentId(strval($id))
+ ->setMessage('full ack')
+ ->setVerb('comment')
+ // setting the creation time avoids using sleep() while making sure to test with different timestamps
+ ->setCreationDateTime(new \DateTime('+' . $i . ' minutes'));
+
+ $manager->save($comment);
+
+ $this->assertSame($comment->getTopmostParentId(), strval($id));
+ $parentComment = $manager->get(strval($id));
+ $this->assertSame($parentComment->getChildrenCount(), $i + 1);
+ $this->assertEquals($parentComment->getLatestChildDateTime(), $comment->getCreationDateTime());
+ }
+ }
+
+ public function invalidActorArgsProvider() {
+ return
+ [
+ ['', ''],
+ [1, 'alice'],
+ ['user', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidActorArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDeleteReferencesOfActorInvalidInput($type, $id) {
+ $manager = $this->getManager();
+ $manager->deleteReferencesOfActor($type, $id);
+ }
+
+ public function testDeleteReferencesOfActor() {
+ $ids = [];
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ // just to make sure they are really set, with correct actor data
+ $comment = $manager->get(strval($ids[1]));
+ $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorId(), 'alice');
+
+ $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $this->assertTrue($wasSuccessful);
+
+ foreach($ids as $id) {
+ $comment = $manager->get(strval($id));
+ $this->assertSame($comment->getActorType(), ICommentsManager::DELETED_USER);
+ $this->assertSame($comment->getActorId(), ICommentsManager::DELETED_USER);
+ }
+
+ // actor info is gone from DB, but when database interaction is alright,
+ // we still expect to get true back
+ $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $this->assertTrue($wasSuccessful);
+ }
+
+ public function testDeleteReferencesOfActorWithUserManagement() {
+ $user = \OC::$server->getUserManager()->createUser('xenia', '123456');
+ $this->assertTrue($user instanceof \OCP\IUser);
+
+ $manager = \OC::$server->getCommentsManager();
+ $comment = $manager->create('user', $user->getUID(), 'file', 'file64');
+ $comment
+ ->setMessage('Most important comment I ever left on the Internet.')
+ ->setVerb('comment');
+ $status = $manager->save($comment);
+ $this->assertTrue($status);
+
+ $commentID = $comment->getId();
+ $user->delete();
+
+ $comment = $manager->get($commentID);
+ $this->assertSame($comment->getActorType(), \OCP\Comments\ICommentsManager::DELETED_USER);
+ $this->assertSame($comment->getActorId(), \OCP\Comments\ICommentsManager::DELETED_USER);
+ }
+
+ public function invalidObjectArgsProvider() {
+ return
+ [
+ ['', ''],
+ [1, 'file64'],
+ ['file', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidObjectArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDeleteCommentsAtObjectInvalidInput($type, $id) {
+ $manager = $this->getManager();
+ $manager->deleteCommentsAtObject($type, $id);
+ }
+
+ public function testDeleteCommentsAtObject() {
+ $ids = [];
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ // just to make sure they are really set, with correct actor data
+ $comment = $manager->get(strval($ids[1]));
+ $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectId(), 'file64');
+
+ $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $this->assertTrue($wasSuccessful);
+
+ $verified = 0;
+ foreach($ids as $id) {
+ try {
+ $manager->get(strval($id));
+ } catch (\OCP\Comments\NotFoundException $e) {
+ $verified++;
+ }
+ }
+ $this->assertSame($verified, 3);
+
+ // actor info is gone from DB, but when database interaction is alright,
+ // we still expect to get true back
+ $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $this->assertTrue($wasSuccessful);
+ }
+
+}
diff --git a/tests/lib/lock/dblockingprovider.php b/tests/lib/lock/dblockingprovider.php
index d679b1ea677..2032110f4f0 100644
--- a/tests/lib/lock/dblockingprovider.php
+++ b/tests/lib/lock/dblockingprovider.php
@@ -85,13 +85,7 @@ class DBLockingProvider extends LockingProvider {
$this->assertEquals(3, $this->getLockEntryCount());
- $this->instance->cleanEmptyLocks();
-
- $this->assertEquals(3, $this->getLockEntryCount());
-
- $this->instance->releaseAll();
-
- $this->instance->cleanEmptyLocks();
+ $this->instance->cleanExpiredLocks();
$this->assertEquals(2, $this->getLockEntryCount());
}
diff --git a/tests/lib/server.php b/tests/lib/server.php
index b72bef82036..6b569e77dd9 100644
--- a/tests/lib/server.php
+++ b/tests/lib/server.php
@@ -61,6 +61,7 @@ class Server extends \Test\TestCase {
['CapabilitiesManager', '\OC\CapabilitiesManager'],
['ContactsManager', '\OC\ContactsManager'],
['ContactsManager', '\OCP\Contacts\IManager'],
+ ['CommentsManager', '\OCP\Comments\ICommentsManager'],
['Crypto', '\OC\Security\Crypto'],
['Crypto', '\OCP\Security\ICrypto'],
['CryptoWrapper', '\OC\Session\CryptoWrapper'],
@@ -173,4 +174,16 @@ class Server extends \Test\TestCase {
$this->assertInstanceOf('\OC_EventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class');
$this->assertInstanceOf('\OCP\IEventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class');
}
+
+ public function testOverwriteDefaultCommentsManager() {
+ $config = $this->server->getConfig();
+ $defaultManagerFactory = $config->getSystemValue('comments.managerFactory', '\OC\Comments\ManagerFactory');
+
+ $config->setSystemValue('comments.managerFactory', '\Test\Comments\FakeFactory');
+
+ $manager = $this->server->getCommentsManager();
+ $this->assertInstanceOf('\OCP\Comments\ICommentsManager', $manager);
+
+ $config->setSystemValue('comments.managerFactory', $defaultManagerFactory);
+ }
}
diff --git a/tests/startsessionlistener.php b/tests/startsessionlistener.php
index 1f3573555ca..88544cc6ce9 100644
--- a/tests/startsessionlistener.php
+++ b/tests/startsessionlistener.php
@@ -44,4 +44,7 @@ class StartSessionListener implements PHPUnit_Framework_TestListener {
public function endTestSuite(PHPUnit_Framework_TestSuite $suite) {
}
+ public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) {
+ }
+
}