summaryrefslogtreecommitdiffstats
path: root/core/l10n/fi_FI.json
blob: e919f677f84047f5bfaa31d4fceea739f0e6c392 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
{ "translations": {
    "Couldn't send mail to following users: %s " : "Sähköpostin lähetys seuraaville käyttäjille epäonnistui: %s",
    "Turned on maintenance mode" : "Siirrytty ylläpitotilaan",
    "Turned off maintenance mode" : "Ylläpitotila laitettu pois päältä",
    "Updated database" : "Tietokanta ajan tasalla",
    "Checked database schema update" : "Tarkistettu tietokannan skeemapäivitys",
    "Checked database schema update for apps" : "Tarkistettu tietokannan skeemapäivitys sovelluksille",
    "Updated \"%s\" to %s" : "Päivitetty \"%s\" versioon %s",
    "Disabled incompatible apps: %s" : "Yhteensopimattomat sovellukset poistettiin käytöstä: %s",
    "No image or file provided" : "Kuvaa tai tiedostoa ei määritelty",
    "Unknown filetype" : "Tuntematon tiedostotyyppi",
    "Invalid image" : "Virhellinen kuva",
    "No temporary profile picture available, try again" : "Väliaikaista profiilikuvaa ei ole käytettävissä, yritä uudelleen",
    "No crop data provided" : "Puutteellinen tieto",
    "Sunday" : "sunnuntai",
    "Monday" : "maanantai",
    "Tuesday" : "tiistai",
    "Wednesday" : "keskiviikko",
    "Thursday" : "torstai",
    "Friday" : "perjantai",
    "Saturday" : "lauantai",
    "January" : "tammikuu",
    "February" : "helmikuu",
    "March" : "maaliskuu",
    "April" : "huhtikuu",
    "May" : "toukokuu",
    "June" : "kesäkuu",
    "July" : "heinäkuu",
    "August" : "elokuu",
    "September" : "syyskuu",
    "October" : "lokakuu",
    "November" : "marraskuu",
    "December" : "joulukuu",
    "Settings" : "Asetukset",
    "Saving..." : "Tallennetaan...",
    "Couldn't send reset email. Please contact your administrator." : "Palautussähköpostin lähettäminen ei onnistunut. Ota yhteys ylläpitäjään.",
    "The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator." : "Linkki salasanan palauttamista varten on lähetetty sähköpostitse. Jos et saa sähköpostiviestiä kohtuullisessa ajassa, tarkista roskapostikansiot.<br>Jos et saa sähköpostiviestiä, ota yhteys paikalliseen ylläpitäjään.",
    "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?" : "Tiedostosi on salattu. Jos et ole ottanut käyttöön palautusavainta, tietojasi ei ole mahdollista palauttaa salasanan nollaamisen jälkeen.<br />Jos et ole varma mitä tehdä, ota yhteys ylläpitäjään.<br />Haluatko varmasti jatkaa?",
    "I know what I'm doing" : "Tiedän mitä teen",
    "Password can not be changed. Please contact your administrator." : "Salasanan vaihtaminen ei onnistunut. Ota yhteys ylläpitäjään.",
    "No" : "Ei",
    "Yes" : "Kyllä",
    "Choose" : "Valitse",
    "Error loading file picker template: {error}" : "Virhe ladatessa tiedostopohjia: {error}",
    "Ok" : "Ok",
    "Error loading message template: {error}" : "Virhe ladatessa viestipohjaa: {error}",
    "read-only" : "vain luku",
    "_{count} file conflict_::_{count} file conflicts_" : ["{count} tiedoston ristiriita","{count} tiedoston ristiriita"],
    "One file conflict" : "Yhden tiedoston ristiriita",
    "New Files" : "Uudet tiedostot",
    "Already existing files" : "Jo olemassa olevat tiedostot",
    "Which files do you want to keep?" : "Mitkä tiedostot haluat säilyttää?",
    "If you select both versions, the copied file will have a number added to its name." : "Jos valitset kummatkin versiot, kopioidun tiedoston nimeen lisätään numero.",
    "Cancel" : "Peru",
    "Continue" : "Jatka",
    "(all selected)" : "(kaikki valittu)",
    "({count} selected)" : "({count} valittu)",
    "Error loading file exists template" : "Virhe ladatessa mallipohjaa",
    "Very weak password" : "Erittäin heikko salasana",
    "Weak password" : "Heikko salasana",
    "So-so password" : "Kohtalainen salasana",
    "Good password" : "Hyvä salasana",
    "Strong password" : "Vahva salasana",
    "Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." : "Web-palvelimen asetukset eivät ole kelvolliset tiedostojen synkronointia varten, koska WebDAV-liitäntä vaikuttaa olevan rikki.",
    "This server has no working internet connection. This means that some of the features like mounting of external storage, notifications about updates or installation of 3rd party apps don´t work. Accessing files from remote and sending of notification emails might also not work. We suggest to enable internet connection for this server if you want to have all features." : "Tällä palvelimella ei ole toimivaa internetyhteyttä. Sen seurauksena jotkin ominaisuudet, kuten erillisten tallennustilojen liittäminen, ilmoitukset päivityksistä tai kolmansien osapuolten sovellusten asentaminen eivät toimi. Tiedostojen käyttäminen etäältä ja ilmoitusten lähettäminen sähköpostitse eivät myöskään välttämättä toimi. Jos haluat käyttää kaikkia palvelimen ominaisuuksia, kytke palvelin internetiin.",
    "Your data directory and your files are probably accessible from the internet. The .htaccess file is not working. We strongly suggest that you configure your webserver in a way that the data directory is no longer accessible or you move the data directory outside the webserver document root." : "Datahakemistosi ja kaikki tiedostosi ovat luultavasti käytettävissä suoraan internetistä. .htaccess-tiedosto ei toimi tällä hetkellä. Määritä verkkopalvelimen asetukset siten, ettei datahakemistosi ole suoraan käytettävissä tai siirrä kyseinen hakemisto pois verkkopalvelimen dokumenttijuuresta.",
    "Error occurred while checking server setup" : "Virhe palvelimen määrityksiä tarkistaessa",
    "Shared" : "Jaettu",
    "Shared with {recipients}" : "Jaettu henkilöiden {recipients} kanssa",
    "Share" : "Jaa",
    "Error" : "Virhe",
    "Error while sharing" : "Virhe jaettaessa",
    "Error while unsharing" : "Virhe jakoa peruttaessa",
    "Error while changing permissions" : "Virhe oikeuksia muuttaessa",
    "Shared with you and the group {group} by {owner}" : "Jaettu sinun ja ryhmän {group} kanssa käyttäjän {owner} toimesta",
    "Shared with you by {owner}" : "Jaettu kanssasi käyttäjän {owner} toimesta",
    "Share with user or group …" : "Jaa käyttäjän tai ryhmän kanssa…",
    "Share link" : "Jaa linkki",
    "The public link will expire no later than {days} days after it is created" : "Julkinen linkki vanhenee {days} päivän jälkeen sen luomisesta",
    "Link" : "Linkki",
    "Password protect" : "Suojaa salasanalla",
    "Password" : "Salasana",
    "Choose a password for the public link" : "Valitse salasana julkiselle linkille",
    "Allow editing" : "Salli muokkaus",
    "Email link to person" : "Lähetä linkki sähköpostitse",
    "Send" : "Lähetä",
    "Set expiration date" : "Aseta päättymispäivä",
    "Expiration" : "Erääntyminen",
    "Expiration date" : "Päättymispäivä",
    "Adding user..." : "Lisätään käyttäjä...",
    "group" : "ryhmä",
    "remote" : "etä",
    "Resharing is not allowed" : "Jakaminen uudelleen ei ole salittu",
    "Shared in {item} with {user}" : "{item} on jaettu {user} kanssa",
    "Unshare" : "Peru jakaminen",
    "notify by email" : "ilmoita sähköpostitse",
    "can share" : "jaa",
    "can edit" : "voi muokata",
    "access control" : "Pääsyn hallinta",
    "create" : "luo",
    "change" : "muuta",
    "delete" : "poista",
    "Password protected" : "Salasanasuojattu",
    "Error unsetting expiration date" : "Virhe purettaessa eräpäivää",
    "Error setting expiration date" : "Virhe päättymispäivää asettaessa",
    "Sending ..." : "Lähetetään...",
    "Email sent" : "Sähköposti lähetetty",
    "Warning" : "Varoitus",
    "The object type is not specified." : "The object type is not specified.",
    "Enter new" : "Kirjoita uusi",
    "Delete" : "Poista",
    "Add" : "Lisää",
    "Edit tags" : "Muokkaa tunnisteita",
    "Error loading dialog template: {error}" : "Virhe ladatessa keskustelupohja: {error}",
    "No tags selected for deletion." : "Tunnisteita ei valittu poistettavaksi.",
    "unknown text" : "tuntematon teksti",
    "Hello world!" : "Hei maailma!",
    "sunny" : "aurinkoinen",
    "Hello {name}, the weather is {weather}" : "Hei {name}, sää on {weather}",
    "Hello {name}" : "Hei {name}",
    "_download %n file_::_download %n files_" : ["lataa %n tiedosto","lataa %n tiedostoa"],
    "Updating {productName} to version {version}, this may take a while." : "Päivitetään {productName} versioon {version}, tämä saattaa kestää hetken.",
    "Please reload the page." : "Päivitä sivu.",
    "The update was unsuccessful. " : "Päivitys epäonnistui.",
    "The update was successful. Redirecting you to ownCloud now." : "Päivitys onnistui. Selain ohjautuu nyt ownCloudiisi.",
    "Couldn't reset password because the token is invalid" : "Salasanaa ei voitu palauttaa koska valtuutus on virheellinen",
    "Couldn't send reset email. Please make sure your username is correct." : "Palautussähköpostin lähettäminen ei onnistunut. Varmista, että käyttäjätunnuksesi on oikein.",
    "Couldn't send reset email because there is no email address for this username. Please contact your administrator." : "Palautussähköpostin lähettäminen ei onnistunut, koska tälle käyttäjätunnukselle ei ole määritelty sähköpostiosoitetta. Ota yhteys ylläpitäjään.",
    "%s password reset" : "%s salasanan palautus",
    "Use the following link to reset your password: {link}" : "Voit palauttaa salasanasi seuraavassa osoitteessa: {link}",
    "New password" : "Uusi salasana",
    "New Password" : "Uusi salasana",
    "Reset password" : "Palauta salasana",
    "Searching other places" : "Etsitään muista paikoista",
    "No search result in other places" : "Ei hakutuloksia muista paikoista",
    "_{count} search result in other places_::_{count} search results in other places_" : ["{count} hakutulos muualla","{count} hakutulosta muualla"],
    "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X ei ole tuettu, joten %s ei toimi kunnolla tällä alustalla. Käytä omalla vastuulla!",
    "For the best results, please consider using a GNU/Linux server instead." : "Käytä parhaan lopputuloksen saamiseksi GNU/Linux-palvelinta.",
    "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4GB and is highly discouraged." : "Vaikuttaa siltä, että tämä %s-asennus toimii 32-bittisessä PHP-ympäristössä, ja asetus open_basedir on käytössä php.ini-tiedostossa. Tämä johtaa ongelmiin yli 4 gigatavun tiedostojen kanssa, eikä ole suositeltavaa.",
    "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Poista open_basedir-asetus php.ini-tiedostosta tai vaihda 64-bittiseen PHP:hen.",
    "It seems that this %s instance is running on a 32-bit PHP environment and cURL is not installed. This will lead to problems with files over 4GB and is highly discouraged." : "Vaikuttaa siltä, että tämä %s-asennus toimii 32-bittisessä PHP-ympäristössä, ja ettei cURL ole asennettuna. Tämä johtaa ongelmiin yli 4 gigatavun tiedostojen kanssa, eikä ole suositeltavaa.",
    "Please install the cURL extension and restart your webserver." : "Asenna cURL-laajennus ja käynnistä http-palvelin uudelleen.",
    "Personal" : "Henkilökohtainen",
    "Users" : "Käyttäjät",
    "Apps" : "Sovellukset",
    "Admin" : "Ylläpito",
    "Help" : "Ohje",
    "Error loading tags" : "Virhe tunnisteita ladattaessa",
    "Tag already exists" : "Tunniste on jo olemassa",
    "Error deleting tag(s)" : "Virhe tunnisteita poistaessa",
    "Error tagging" : "Tunnisteiden kirjoitusvirhe",
    "Error untagging" : "Tunisteiden poisto virhe",
    "Error favoriting" : "Suosituksen kirjoitusvirhe",
    "Error unfavoriting" : "Suosituksen poisto virhe",
    "Access forbidden" : "Pääsy estetty",
    "File not found" : "Tiedostoa ei löytynyt",
    "The specified document has not been found on the server." : "Määritettyä asiakirjaa ei löytynyt palvelimelta.",
    "You can click here to return to %s." : "Napsauta tästä palataksesi %siin.",
    "Hey there,\n\njust letting you know that %s shared %s with you.\nView it: %s\n\n" : "Hei sinä!\n\n%s jakoi kohteen %s kanssasi.\nTutustu siihen: %s\n\n",
    "The share will expire on %s." : "Jakaminen päättyy %s.",
    "Cheers!" : "Kiitos!",
    "Internal Server Error" : "Sisäinen palvelinvirhe",
    "The server encountered an internal error and was unable to complete your request." : "Palvelin kohtasi sisäisen virheen, eikä pystynyt viimeistelmään pyyntöäsi.",
    "Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report." : "Ota yhteys palvelimen ylläpitäjään, jos tämä virhe ilmenee useita kertoja. Lisää yhteydenottoosi alla olevat tekniset tiedot.",
    "More details can be found in the server log." : "Lisätietoja on palvelimen lokitiedostossa.",
    "Technical details" : "Tekniset tiedot",
    "Remote Address: %s" : "Etäosoite: %s",
    "Request ID: %s" : "Pyynnön tunniste: %s",
    "Code: %s" : "Koodi: %s",
    "Message: %s" : "Viesti: %s",
    "File: %s" : "Tiedosto: %s",
    "Line: %s" : "Rivi: %s",
    "Trace" : "Jälki",
    "Security Warning" : "Turvallisuusvaroitus",
    "Your data directory and files are probably accessible from the internet because the .htaccess file does not work." : "Datakansiosi ja tiedostosi ovat mitä luultavimmin muiden saavutettavissa internetistä, koska .htaccess-tiedosto ei toimi.",
    "For information how to properly configure your server, please see the <a href=\"%s\" target=\"_blank\">documentation</a>." : "Lisätietoja palvelimen asetuksien määrittämisestä on saatavilla <a href=\"%s\" target=\"_blank\">dokumentaatiosta</a>.",
    "Create an <strong>admin account</strong>" : "Luo <strong>ylläpitäjän tunnus</strong>",
    "Username" : "Käyttäjätunnus",
    "Storage & database" : "Tallennus ja tietokanta",
    "Data folder" : "Datakansio",
    "Configure the database" : "Muokkaa tietokantaa",
    "Only %s is available." : "Vain %s on käytettävissä.",
    "Database user" : "Tietokannan käyttäjä",
    "Database password" : "Tietokannan salasana",
    "Database name" : "Tietokannan nimi",
    "Database tablespace" : "Tietokannan taulukkotila",
    "Database host" : "Tietokantapalvelin",
    "Performance Warning" : "Suorituskykyvaroitus",
    "SQLite will be used as database." : "SQLitea käytetään tietokantana.",
    "For larger installations we recommend to choose a different database backend." : "Suuria asennuksia varten suositellaan muun tietokannan käyttöä.",
    "Especially when using the desktop client for file syncing the use of SQLite is discouraged." : "Varsinkin työpöytäsovelluksen tiedostosynkronointia käyttäessä SQLiten käyttö ei ole suositeltavaa.",
    "Finish setup" : "Viimeistele asennus",
    "Finishing …" : "Valmistellaan…",
    "This application requires JavaScript for correct operation. Please {linkstart}enable JavaScript{linkend} and reload the page." : "Tämä sovellus vaatii toimiakseen JavaScript-tuen. {linkstart}Ota JavaScript käyttöön{linkend} ja päivitä sivu.",
    "%s is available. Get more information on how to update." : "%s on saatavilla. Lue lisätietoja, miten päivitys asennetaan.",
    "Log out" : "Kirjaudu ulos",
    "Search" : "Etsi",
    "Server side authentication failed!" : "Palvelimen puoleinen tunnistautuminen epäonnistui!",
    "Please contact your administrator." : "Ota yhteys ylläpitäjään.",
    "Forgot your password? Reset it!" : "Unohditko salasanasi? Palauta se!",
    "remember" : "muista",
    "Log in" : "Kirjaudu sisään",
    "Alternative Logins" : "Vaihtoehtoiset kirjautumiset",
    "Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href=\"%s\">View it!</a><br><br>" : "Hei!<br><br>%s jakoi kanssasi kohteen <strong>%s</strong>.<br><a href=\"%s\">Tutustu siihen!</a><br><br>",
    "This ownCloud instance is currently in single user mode." : "Tämä ownCloud-asennus on parhaillaan single user -tilassa.",
    "This means only administrators can use the instance." : "Se tarkoittaa, että vain ylläpitäjät voivat nyt käyttää tätä ownCloudia.",
    "Contact your system administrator if this message persists or appeared unexpectedly." : "Ota yhteys järjestelmän ylläpitäjään, jos tämä viesti ilmenee uudelleen tai odottamatta.",
    "Thank you for your patience." : "Kiitos kärsivällisyydestäsi.",
    "You are accessing the server from an untrusted domain." : "Olet yhteydessä palvelimeen epäluotettavasta verkko-osoitteesta.",
    "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domain\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Ota yhteys ylläpitäjään. Jos olet tämän ownCloudin ylläpitäjä, määritä \"trusted_domain\"-asetus tiedostossa config/config.php. Esimerkkimääritys on nähtävillä tiedostossa config/config.sample.php.",
    "Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "Riippuen määrityksistä, ylläpitäjänä saatat kyetä käyttämään alla olevaa painiketta luodaksesi luottamussuhteen tähän toimialueeseen.",
    "Add \"%s\" as trusted domain" : "Lisää \"%s\" luotetuksi toimialueeksi",
    "%s will be updated to version %s." : "%s päivitetään versioon %s.",
    "The following apps will be disabled:" : "Seuraavat sovellukset poistetaan käytöstä:",
    "The theme %s has been disabled." : "Teema %s on poistettu käytöstä.",
    "Please make sure that the database, the config folder and the data folder have been backed up before proceeding." : "Varmista ennen jatkamista, että tietokanta, asetuskansio ja datakansio on varmuuskopioitu.",
    "Start update" : "Käynnistä päivitys",
    "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Välttääksesi aikakatkaisuja suurikokoisten asennusten kanssa, voit suorittaa vaihtoehtoisesti seuraavan komennon asennushakemistossa:",
    "This %s instance is currently being updated, which may take a while." : "Tätä %s-asennusta päivitetään parhaillaan, päivityksessä saattaa kestää hetki.",
    "This page will refresh itself when the %s instance is available again." : "Tämä sivu päivittää itsensä, kun %s on jälleen käytettävissä."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); foreach ($extensionValues as $column => $value) { $query->setValue($column, $query->createNamedParameter($value)); } $query->execute(); } $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId)); return $fileId; } } catch (UniqueConstraintViolationException $e) { // entry exists already if ($this->connection->inTransaction()) { $this->connection->commit(); $this->connection->beginTransaction(); } } // The file was created in the mean time if (($id = $this->getId($file)) > -1) { $this->update($id, $data); return $id; } else { throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.'); } } /** * update the metadata of an existing file or folder in the cache * * @param int $id the fileid of the existing file or folder * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged */ public function update($id, array $data) { if (isset($data['path'])) { // normalize path $data['path'] = $this->normalize($data['path']); } if (isset($data['name'])) { // normalize path $data['name'] = $this->normalize($data['name']); } [$values, $extensionValues] = $this->normalizeData($data); if (count($values)) { $query = $this->getQueryBuilder(); $query->update('filecache') ->whereFileId($id) ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) { return $query->expr()->orX( $query->expr()->neq($key, $query->createNamedParameter($value)), $query->expr()->isNull($key) ); }, array_keys($values), array_values($values)))); foreach ($values as $key => $value) { $query->set($key, $query->createNamedParameter($value)); } $query->execute(); } if (count($extensionValues)) { try { $query = $this->getQueryBuilder(); $query->insert('filecache_extended'); $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)); foreach ($extensionValues as $column => $value) { $query->setValue($column, $query->createNamedParameter($value)); } $query->execute(); } catch (UniqueConstraintViolationException $e) { $query = $this->getQueryBuilder(); $query->update('filecache_extended') ->whereFileId($id) ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) { return $query->expr()->orX( $query->expr()->neq($key, $query->createNamedParameter($value)), $query->expr()->isNull($key) ); }, array_keys($extensionValues), array_values($extensionValues)))); foreach ($extensionValues as $key => $value) { $query->set($key, $query->createNamedParameter($value)); } $query->execute(); } } $path = $this->getPathById($id); // path can still be null if the file doesn't exist if ($path !== null) { $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id)); } } /** * extract query parts and params array from data array * * @param array $data * @return array */ protected function normalizeData(array $data): array { $fields = [ 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'storage']; $extensionFields = ['metadata_etag', 'creation_time', 'upload_time']; $doNotCopyStorageMTime = false; if (array_key_exists('mtime', $data) && $data['mtime'] === null) { // this horrific magic tells it to not copy storage_mtime to mtime unset($data['mtime']); $doNotCopyStorageMTime = true; } $params = []; $extensionParams = []; foreach ($data as $name => $value) { if (array_search($name, $fields) !== false) { if ($name === 'path') { $params['path_hash'] = md5($value); } else if ($name === 'mimetype') { $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/'))); $value = $this->mimetypeLoader->getId($value); } else if ($name === 'storage_mtime') { if (!$doNotCopyStorageMTime && !isset($data['mtime'])) { $params['mtime'] = $value; } } else if ($name === 'encrypted') { if (isset($data['encryptedVersion'])) { $value = $data['encryptedVersion']; } else { // Boolean to integer conversion $value = $value ? 1 : 0; } } $params[$name] = $value; } if (array_search($name, $extensionFields) !== false) { $extensionParams[$name] = $value; } } return [$params, array_filter($extensionParams)]; } /** * get the file id for a file * * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file * * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing * * @param string $file * @return int */ public function getId($file) { // normalize file $file = $this->normalize($file); $query = $this->getQueryBuilder(); $query->select('fileid') ->from('filecache') ->whereStorageId() ->wherePath($file); $id = $query->execute()->fetchColumn(); return $id === false ? -1 : (int)$id; } /** * get the id of the parent folder of a file * * @param string $file * @return int */ public function getParentId($file) { if ($file === '') { return -1; } else { $parent = $this->getParentPath($file); return (int)$this->getId($parent); } } private function getParentPath($path) { $parent = dirname($path); if ($parent === '.') { $parent = ''; } return $parent; } /** * check if a file is available in the cache * * @param string $file * @return bool */ public function inCache($file) { return $this->getId($file) != -1; } /** * remove a file or folder from the cache * * when removing a folder from the cache all files and folders inside the folder will be removed as well * * @param string $file */ public function remove($file) { $entry = $this->get($file); if ($entry) { $query = $this->getQueryBuilder(); $query->delete('filecache') ->whereFileId($entry->getId()); $query->execute(); $query = $this->getQueryBuilder(); $query->delete('filecache_extended') ->whereFileId($entry->getId()); $query->execute(); if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) { $this->removeChildren($entry); } } } /** * Get all sub folders of a folder * * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for * @return ICacheEntry[] the cache entries for the subfolders */ private function getSubFolders(ICacheEntry $entry) { $children = $this->getFolderContentsById($entry->getId()); return array_filter($children, function ($child) { return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; }); } /** * Recursively remove all children of a folder * * @param ICacheEntry $entry the cache entry of the folder to remove the children of * @throws \OC\DatabaseException */ private function removeChildren(ICacheEntry $entry) { $children = $this->getFolderContentsById($entry->getId()); $childIds = array_map(function (ICacheEntry $cacheEntry) { return $cacheEntry->getId(); }, $children); $childFolders = array_filter($children, function ($child) { return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; }); foreach ($childFolders as $folder) { $this->removeChildren($folder); } $query = $this->getQueryBuilder(); $query->delete('filecache') ->whereParent($entry->getId()); $query->execute(); $query = $this->getQueryBuilder(); $query->delete('filecache_extended') ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY))); $query->execute(); } /** * Move a file or folder in the cache * * @param string $source * @param string $target */ public function move($source, $target) { $this->moveFromCache($this, $source, $target); } /** * Get the storage id and path needed for a move * * @param string $path * @return array [$storageId, $internalPath] */ protected function getMoveInfo($path) { return [$this->getNumericStorageId(), $path]; } /** * Move a file or folder in the cache * * @param \OCP\Files\Cache\ICache $sourceCache * @param string $sourcePath * @param string $targetPath * @throws \OC\DatabaseException * @throws \Exception if the given storages have an invalid id * @suppress SqlInjectionChecker */ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { if ($sourceCache instanceof Cache) { // normalize source and target $sourcePath = $this->normalize($sourcePath); $targetPath = $this->normalize($targetPath); $sourceData = $sourceCache->get($sourcePath); $sourceId = $sourceData['fileid']; $newParentId = $this->getParentId($targetPath); [$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath); [$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath); if (is_null($sourceStorageId) || $sourceStorageId === false) { throw new \Exception('Invalid source storage id: ' . $sourceStorageId); } if (is_null($targetStorageId) || $targetStorageId === false) { throw new \Exception('Invalid target storage id: ' . $targetStorageId); } $this->connection->beginTransaction(); if ($sourceData['mimetype'] === 'httpd/unix-directory') { //update all child entries $sourceLength = mb_strlen($sourcePath); $query = $this->connection->getQueryBuilder(); $fun = $query->func(); $newPathFunction = $fun->concat( $query->createNamedParameter($targetPath), $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash ); $query->update('filecache') ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT)) ->set('path_hash', $fun->md5($newPathFunction)) ->set('path', $newPathFunction) ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%'))); try { $query->execute(); } catch (\OC\DatabaseException $e) { $this->connection->rollBack(); throw $e; } } $query = $this->getQueryBuilder(); $query->update('filecache') ->set('storage', $query->createNamedParameter($targetStorageId)) ->set('path', $query->createNamedParameter($targetPath)) ->set('path_hash', $query->createNamedParameter(md5($targetPath))) ->set('name', $query->createNamedParameter(basename($targetPath))) ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT)) ->whereFileId($sourceId); $query->execute(); $this->connection->commit(); } else { $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath); } } /** * remove all entries for files that are stored on the storage from the cache */ public function clear() { $query = $this->getQueryBuilder(); $query->delete('filecache') ->whereStorageId(); $query->execute(); $query = $this->connection->getQueryBuilder(); $query->delete('storages') ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId))); $query->execute(); } /** * Get the scan status of a file * * - Cache::NOT_FOUND: File is not in the cache * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned * * @param string $file * * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE */ public function getStatus($file) { // normalize file $file = $this->normalize($file); $query = $this->getQueryBuilder(); $query->select('size') ->from('filecache') ->whereStorageId() ->wherePath($file); $size = $query->execute()->fetchColumn(); if ($size !== false) { if ((int)$size === -1) { return self::SHALLOW; } else { return self::COMPLETE; } } else { if (isset($this->partial[$file])) { return self::PARTIAL; } else { return self::NOT_FOUND; } } } /** * search for files matching $pattern * * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') * @return ICacheEntry[] an array of cache entries where the name matches the search pattern */ public function search($pattern) { // normalize pattern $pattern = $this->normalize($pattern); if ($pattern === '%%') { return []; } $query = $this->getQueryBuilder(); $query->selectFileCache() ->whereStorageId() ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern))); return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $query->execute()->fetchAll()); } /** * @param Statement $result * @return CacheEntry[] */ private function searchResultToCacheEntries(Statement $result) { $files = $result->fetchAll(); return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); } /** * search for files by mimetype * * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') * where it will search for all mimetypes in the group ('image/*') * @return ICacheEntry[] an array of cache entries where the mimetype matches the search */ public function searchByMime($mimetype) { $mimeId = $this->mimetypeLoader->getId($mimetype); $query = $this->getQueryBuilder(); $query->selectFileCache() ->whereStorageId(); if (strpos($mimetype, '/')) { $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT))); } else { $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT))); } return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $query->execute()->fetchAll()); } public function searchQuery(ISearchQuery $searchQuery) { $builder = $this->getQueryBuilder(); $query = $builder->selectFileCache('file'); $query->whereStorageId(); if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) { $query ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( $builder->expr()->eq('tagmap.type', 'tag.type'), $builder->expr()->eq('tagmap.categoryid', 'tag.id') )) ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files'))) ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID()))); } $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()); if ($searchExpr) { $query->andWhere($searchExpr); } if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) { $query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%'))); } $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder()); if ($searchQuery->getLimit()) { $query->setMaxResults($searchQuery->getLimit()); } if ($searchQuery->getOffset()) { $query->setFirstResult($searchQuery->getOffset()); } $result = $query->execute(); return $this->searchResultToCacheEntries($result); } /** * Re-calculate the folder size and the size of all parent folders * * @param string|boolean $path * @param array $data (optional) meta data of the folder */ public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { $this->calculateFolderSize($path, $data); if ($path !== '') { $parent = dirname($path); if ($parent === '.' or $parent === '/') { $parent = ''; } if ($isBackgroundScan) { $parentData = $this->get($parent); if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) { $this->correctFolderSize($parent, $parentData, $isBackgroundScan); } } else { $this->correctFolderSize($parent); } } } /** * get the incomplete count that shares parent $folder * * @param int $fileId the file id of the folder * @return int */ public function getIncompleteChildrenCount($fileId) { if ($fileId > -1) { $query = $this->getQueryBuilder(); $query->select($query->func()->count()) ->from('filecache') ->whereParent($fileId) ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))); return (int)$query->execute()->fetchColumn(); } return -1; } /** * calculate the size of a folder and set it in the cache * * @param string $path * @param array $entry (optional) meta data of the folder * @return int */ public function calculateFolderSize($path, $entry = null) { $totalSize = 0; if (is_null($entry) or !isset($entry['fileid'])) { $entry = $this->get($path); } if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) { $id = $entry['fileid']; $query = $this->getQueryBuilder(); $query->selectAlias($query->func()->sum('size'), 'f1') ->selectAlias($query->func()->min('size'), 'f2') ->from('filecache') ->whereStorageId() ->whereParent($id); if ($row = $query->execute()->fetch()) { [$sum, $min] = array_values($row); $sum = 0 + $sum; $min = 0 + $min; if ($min === -1) { $totalSize = $min; } else { $totalSize = $sum; } if ($entry['size'] !== $totalSize) { $this->update($id, ['size' => $totalSize]); } } } return $totalSize; } /** * get all file ids on the files on the storage * * @return int[] */ public function getAll() { $query = $this->getQueryBuilder(); $query->select('fileid') ->from('filecache') ->whereStorageId(); return array_map(function ($id) { return (int)$id; }, $query->execute()->fetchAll(\PDO::FETCH_COLUMN)); } /** * find a folder in the cache which has not been fully scanned * * If multiple incomplete folders are in the cache, the one with the highest id will be returned, * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * * @return string|bool the path of the folder or false when no folder matched */ public function getIncomplete() { $query = $this->getQueryBuilder(); $query->select('path') ->from('filecache') ->whereStorageId() ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))) ->orderBy('fileid', 'DESC'); return $query->execute()->fetchColumn(); } /** * get the path of a file on this storage by it's file id * * @param int $id the file id of the file or folder to search * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache */ public function getPathById($id) { $query = $this->getQueryBuilder(); $query->select('path') ->from('filecache') ->whereStorageId() ->whereFileId($id); $path = $query->execute()->fetchColumn(); return $path === false ? null : $path; } /** * get the storage id of the storage for a file and the internal path of the file * unlike getPathById this does not limit the search to files on this storage and * instead does a global search in the cache table * * @param int $id * @return array first element holding the storage id, second the path * @deprecated use getPathById() instead */ static public function getById($id) { $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); $query->select('path', 'storage') ->from('filecache') ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); if ($row = $query->execute()->fetch()) { $numericId = $row['storage']; $path = $row['path']; } else { return null; } if ($id = Storage::getStorageId($numericId)) { return [$id, $path]; } else { return null; } } /** * normalize the given path * * @param string $path * @return string */ public function normalize($path) { return trim(\OC_Util::normalizeUnicode($path), '/'); } }