Wiki source code of Filesystem Store
Last modified by Vincent Massol on 2025/11/17 17:19
Hide last authors
| author | version | line-number | content |
|---|---|---|---|
![]() |
14.1 | 1 | {{box cssClass="floatinginfobox" title="**Contents**"}} |
| 2 | {{toc start="2"/}} | ||
| 3 | {{/box}} | ||
![]() |
1.68 | 4 | |
![]() |
14.1 | 5 | By default, in XWiki the content of attachments and deleted attachments and documents are stored in what we call the "filesystem store". Its default location is ##<permanentdir>/store/file##. |
![]() |
1.1 | 6 | |
![]() |
14.1 | 7 | {{version since="17.10.0RC1"}} |
| 8 | Despite its name, this "filesystem" store uses whatever [[blob store is configured>>documentation.xs.admin.store.blob.WebHome]] and can thus store attachments and deleted attachments and documents also in other blob stores like S3. The naming of the blobs is independent of the configured blob store. | ||
| 9 | {{/version}} | ||
![]() |
1.1 | 10 | |
![]() |
14.1 | 11 | It's a simple file based storage in which documents and attachments references are (md5) hashed, to avoid problems with various limited (in terms of encoding and path size) file systems/blob stores. The filesystem store implements a two stage commit mechanism to maintain integrity even if the database fails to commit the attachment meta-data for example. |
| 12 | |||
![]() |
1.70 | 13 | For example the attachment ##XWikiLogo.png## in document ##Sandbox.WebHome## is stored in the following location: ##/1/0/5d42329a923e687f5dff4887d80098/attachments/9/2/0bc685fa0da28168319c0126def81b##. |
![]() |
1.1 | 14 | |
![]() |
1.26 | 15 | {{box title="And where is my entity located ?"}} |
![]() |
1.25 | 16 | {{velocity}} |
![]() |
1.19 | 17 | {{html}} |
| 18 | <form> | ||
![]() |
9.1 | 19 | <input type="text" name="reference"#if($request.reference) value="$escapetool.xml($request.reference)#end"/> |
![]() |
1.20 | 20 | <button name="serialize_document">Document</button> |
| 21 | <button name="serialize_attachment">Attachment</button> | ||
![]() |
1.19 | 22 | </form> |
| 23 | {{/html}} | ||
![]() |
1.25 | 24 | {{/velocity}} |
![]() |
1.19 | 25 | |
![]() |
1.42 | 26 | {{groovy}} |
![]() |
1.16 | 27 | def hash(str) |
| 28 | { | ||
![]() |
1.49 | 29 | md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(str) |
![]() |
1.71 | 30 | println "1. It's then md5 hashed (##${md5}##)." |
![]() |
1.32 | 31 | |
![]() |
1.49 | 32 | char0 = md5.charAt(0) |
![]() |
1.71 | 33 | println "1. A first folder is created with the first character of the hash (##${char0}##)." |
![]() |
1.32 | 34 | |
![]() |
1.49 | 35 | char1 = md5.charAt(1) |
![]() |
1.71 | 36 | println "1. A second folder is created with the second character of the hash (##${char1}##)." |
![]() |
1.32 | 37 | |
![]() |
1.49 | 38 | charn = md5.substring(2) |
![]() |
1.71 | 39 | println "1. A third folder is created with the remaining characters (##${charn}##)." |
![]() |
1.9 | 40 | |
![]() |
1.52 | 41 | return "**$char0**/**$char1**/**$charn**" |
![]() |
1.16 | 42 | } |
| 43 | |||
![]() |
1.17 | 44 | def encodeDocument(documentReference) { |
![]() |
1.24 | 45 | path = "" |
![]() |
1.29 | 46 | if (documentReference.root.type == org.xwiki.model.EntityType.WIKI) { |
![]() |
1.71 | 47 | println "1. A folder with the wiki name (##${documentReference.root.name}##) is created." |
![]() |
1.29 | 48 | path = documentReference.root.name |
![]() |
1.24 | 49 | } |
| 50 | |||
![]() |
1.49 | 51 | localuid = services.component.getInstance(org.xwiki.model.reference.EntityReferenceSerializer.TYPE_STRING, "local/uid").serialize(documentReference) |
![]() |
1.71 | 52 | println "1. The document reference is serialized using the local uid serializer (##${localuid}##)." |
![]() |
1.32 | 53 | |
![]() |
1.24 | 54 | path += "/" + hash(localuid) |
| 55 | |||
| 56 | return path; | ||
![]() |
1.16 | 57 | } |
| 58 | |||
![]() |
1.17 | 59 | def encodeAttachment(attachmentReference) { |
![]() |
1.16 | 60 | path = "" |
| 61 | if (reference.parent) { | ||
| 62 | path = encodeDocument(attachmentReference.parent) | ||
| 63 | } | ||
| 64 | |||
![]() |
1.49 | 65 | path += "/attachments" |
![]() |
1.71 | 66 | println "1. An ##attachments## folder is created." |
![]() |
1.16 | 67 | |
![]() |
1.49 | 68 | name = attachmentReference.name |
![]() |
1.71 | 69 | println "1. The attachment name (##$name##) is extracted from the reference." |
![]() |
1.49 | 70 | |
![]() |
1.51 | 71 | path += "/" + hash(name) + "" |
![]() |
1.16 | 72 | } |
| 73 | |||
![]() |
1.22 | 74 | if (request.serialize_document != null) { |
![]() |
1.28 | 75 | reference = services.component.getInstance(org.xwiki.model.reference.EntityReferenceResolver.TYPE_STRING, "relative").resolve(request.reference, org.xwiki.model.EntityType.DOCUMENT) |
![]() |
1.16 | 76 | |
![]() |
1.70 | 77 | println "{{info}}" |
| 78 | path = encodeDocument(reference) | ||
| 79 | println "{{/info}}" | ||
| 80 | |||
| 81 | println "" | ||
| 82 | print "The final path of the folder which contains the content of this attachment is ##$path##." | ||
![]() |
1.22 | 83 | } else if (request.serialize_attachment != null) { |
![]() |
1.23 | 84 | reference = services.component.getInstance(org.xwiki.model.reference.EntityReferenceResolver.TYPE_STRING, "relative").resolve(request.reference, org.xwiki.model.EntityType.ATTACHMENT) |
![]() |
1.16 | 85 | |
![]() |
1.41 | 86 | println "{{info}}" |
![]() |
1.36 | 87 | path = encodeAttachment(reference) |
![]() |
1.41 | 88 | println "{{/info}}" |
![]() |
1.36 | 89 | |
![]() |
1.33 | 90 | println "" |
![]() |
1.70 | 91 | print "The final path of the folder which contains the content of this document is ##$path##." |
![]() |
1.2 | 92 | } |
| 93 | {{/groovy}} | ||
![]() |
1.18 | 94 | {{/box}} |
![]() |
1.55 | 95 | |
![]() |
14.1 | 96 | == Attachments and deleted attachments == |
![]() |
1.60 | 97 | |
![]() |
1.65 | 98 | By default, inside the attachment folder, you will find both the current version of the attachment and its history. The file name is always ##f##, it's then optionally followed by the version (when it's a piece of the history) and the original file extension. |
![]() |
1.55 | 99 | |
![]() |
4.1 | 100 | For example for an image of type png: |
| 101 | |||
![]() |
1.59 | 102 | * ##f.png## |
![]() |
1.64 | 103 | * ##fv1.1.png## |
| 104 | * ##fv2.1.png## | ||
![]() |
1.59 | 105 | |
![]() |
1.55 | 106 | {{version since="16.4.0"}} |
![]() |
7.1 | 107 | Most of the time, instead of the current version of the attachment, a "link" is created. It's a file which name is suffixed with ##.lnk## which contains the relative path it's representing. In standard condition, it always points to the latest version of the attachment. |
![]() |
1.59 | 108 | |
![]() |
9.1 | 109 | For example for an image of type png: |
![]() |
4.1 | 110 | |
![]() |
2.1 | 111 | * ##f.png.lnk## {{info}}contains the string ##fv2.1.png##{{/info}} |
![]() |
1.63 | 112 | * ##fv1.1.png## |
| 113 | * ##fv2.1.png## | ||
![]() |
5.1 | 114 | {{/version}} |
![]() |
1.67 | 115 | |
![]() |
11.2 | 116 | The different between attachments and deleted attachment is the location of that folder inside the document folder: |
![]() |
14.1 | 117 | |
![]() |
11.2 | 118 | * an attachment is located in ##/attachments/<hash/based/on the attachment name>/## |
| 119 | * a deleted is located in ##/deleted-attachments/<hash/based/on the attachment name>/<index of the deleted attachment>/## | ||
![]() |
1.67 | 120 | |
![]() |
14.1 | 121 | == Deleted documents == |
![]() |
9.1 | 122 | |
![]() |
13.1 | 123 | {{todo/}} |

