file.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /*
  2. * Rufs - Remote Union File System
  3. *
  4. * Copyright 2017 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package v1
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "mime/multipart"
  15. "net/http"
  16. "path"
  17. "time"
  18. "devt.de/krotik/common/cryptutil"
  19. "devt.de/krotik/common/datautil"
  20. "devt.de/krotik/common/errorutil"
  21. "devt.de/krotik/common/httputil"
  22. "devt.de/krotik/rufs"
  23. "devt.de/krotik/rufs/api"
  24. )
  25. // Progress endpoint
  26. // =================
  27. /*
  28. Progress is a persisted data structure which contains the current
  29. progress of an ongoing operation.
  30. */
  31. type Progress struct {
  32. Op string // Operation which we show progress of
  33. Subject string // Subject on which the operation is performed
  34. Progress int64 // Current progress of the ongoing operation (this is reset for each item)
  35. TotalProgress int64 // Total progress required until current operation is finished
  36. Item int64 // Current processing item
  37. TotalItems int64 // Total number of items to process
  38. Errors []string // Any error messages
  39. }
  40. /*
  41. JSONString returns the progress object as a JSON string.
  42. */
  43. func (p *Progress) JSONString() []byte {
  44. ret, err := json.MarshalIndent(map[string]interface{}{
  45. "operation": p.Op,
  46. "subject": p.Subject,
  47. "progress": p.Progress,
  48. "total_progress": p.TotalProgress,
  49. "item": p.Item,
  50. "total_items": p.TotalItems,
  51. "errors": p.Errors,
  52. }, "", " ")
  53. errorutil.AssertOk(err)
  54. return ret
  55. }
  56. /*
  57. ProgressMap contains information about copy progress.
  58. */
  59. var ProgressMap = datautil.NewMapCache(100, 0)
  60. /*
  61. EndpointProgress is the progress endpoint URL (rooted). Handles everything
  62. under progress/...
  63. */
  64. const EndpointProgress = api.APIRoot + APIv1 + "/progress/"
  65. /*
  66. ProgressEndpointInst creates a new endpoint handler.
  67. */
  68. func ProgressEndpointInst() api.RestEndpointHandler {
  69. return &progressEndpoint{}
  70. }
  71. /*
  72. Handler object for progress operations.
  73. */
  74. type progressEndpoint struct {
  75. *api.DefaultEndpointHandler
  76. }
  77. /*
  78. HandleGET handles a progress query REST call.
  79. */
  80. func (f *progressEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) {
  81. var ok bool
  82. var err error
  83. if len(resources) < 2 {
  84. http.Error(w, "Need a tree name and a progress ID",
  85. http.StatusBadRequest)
  86. return
  87. }
  88. if _, ok, err = api.GetTree(resources[0]); err == nil && !ok {
  89. err = fmt.Errorf("Unknown tree: %v", resources[0])
  90. }
  91. if err != nil {
  92. http.Error(w, err.Error(), http.StatusBadRequest)
  93. return
  94. }
  95. p, ok := ProgressMap.Get(resources[0] + "#" + resources[1])
  96. if !ok {
  97. http.Error(w, fmt.Sprintf("Unknown progress ID: %v", resources[1]),
  98. http.StatusBadRequest)
  99. return
  100. }
  101. w.Header().Set("content-type", "application/octet-stream")
  102. w.Write(p.(*Progress).JSONString())
  103. }
  104. /*
  105. SwaggerDefs is used to describe the endpoint in swagger.
  106. */
  107. func (f *progressEndpoint) SwaggerDefs(s map[string]interface{}) {
  108. s["paths"].(map[string]interface{})["/v1/progress/{tree}/{progress_id}"] = map[string]interface{}{
  109. "get": map[string]interface{}{
  110. "summary": "Request progress update.",
  111. "description": "Return a progress object showing the progress of an ongoing operation.",
  112. "produces": []string{
  113. "text/plain",
  114. "application/json",
  115. },
  116. "parameters": []map[string]interface{}{
  117. {
  118. "name": "tree",
  119. "in": "path",
  120. "description": "Name of the tree.",
  121. "required": true,
  122. "type": "string",
  123. },
  124. {
  125. "name": "progress_id",
  126. "in": "path",
  127. "description": "Id of progress object.",
  128. "required": true,
  129. "type": "string",
  130. },
  131. },
  132. "responses": map[string]interface{}{
  133. "200": map[string]interface{}{
  134. "description": "Returns the requested progress object.",
  135. },
  136. "default": map[string]interface{}{
  137. "description": "Error response",
  138. "schema": map[string]interface{}{
  139. "$ref": "#/definitions/Error",
  140. },
  141. },
  142. },
  143. },
  144. }
  145. // Add generic error object to definition
  146. s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
  147. "description": "A human readable error mesage.",
  148. "type": "string",
  149. }
  150. }
  151. // File endpoint
  152. // =============
  153. /*
  154. EndpointFile is the file endpoint URL (rooted). Handles everything
  155. under file/...
  156. */
  157. const EndpointFile = api.APIRoot + APIv1 + "/file/"
  158. /*
  159. FileEndpointInst creates a new endpoint handler.
  160. */
  161. func FileEndpointInst() api.RestEndpointHandler {
  162. return &fileEndpoint{}
  163. }
  164. /*
  165. Handler object for file operations.
  166. */
  167. type fileEndpoint struct {
  168. *api.DefaultEndpointHandler
  169. }
  170. /*
  171. HandleGET handles a file query REST call.
  172. */
  173. func (f *fileEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) {
  174. var tree *rufs.Tree
  175. var ok bool
  176. var err error
  177. if len(resources) < 2 {
  178. http.Error(w, "Need a tree name and a file path",
  179. http.StatusBadRequest)
  180. return
  181. }
  182. if tree, ok, err = api.GetTree(resources[0]); err == nil && !ok {
  183. err = fmt.Errorf("Unknown tree: %v", resources[0])
  184. }
  185. if err != nil {
  186. http.Error(w, err.Error(), http.StatusBadRequest)
  187. return
  188. }
  189. w.Header().Set("content-type", "application/octet-stream")
  190. if err := tree.ReadFileToBuffer(path.Join(resources[1:]...), w); err != nil {
  191. http.Error(w, fmt.Sprintf("Could not read file %v: %v", path.Join(resources[1:]...), err.Error()),
  192. http.StatusBadRequest)
  193. return
  194. }
  195. }
  196. /*
  197. HandlePUT handles REST calls to modify / copy existing files.
  198. */
  199. func (f *fileEndpoint) HandlePUT(w http.ResponseWriter, r *http.Request, resources []string) {
  200. f.handleFileOp("PUT", w, r, resources)
  201. }
  202. /*
  203. HandleDELETE handles REST calls to delete existing files.
  204. */
  205. func (f *fileEndpoint) HandleDELETE(w http.ResponseWriter, r *http.Request, resources []string) {
  206. f.handleFileOp("DELETE", w, r, resources)
  207. }
  208. func (f *fileEndpoint) handleFileOp(requestType string, w http.ResponseWriter, r *http.Request, resources []string) {
  209. var action string
  210. var data, ret map[string]interface{}
  211. var tree *rufs.Tree
  212. var ok bool
  213. var err error
  214. var files []string
  215. if len(resources) < 1 {
  216. http.Error(w, "Need a tree name and a file path",
  217. http.StatusBadRequest)
  218. return
  219. } else if len(resources) == 1 {
  220. resources = append(resources, "/")
  221. }
  222. if tree, ok, err = api.GetTree(resources[0]); err == nil && !ok {
  223. err = fmt.Errorf("Unknown tree: %v", resources[0])
  224. }
  225. if err != nil {
  226. http.Error(w, err.Error(), http.StatusBadRequest)
  227. return
  228. }
  229. ret = make(map[string]interface{})
  230. if requestType == "DELETE" {
  231. // See if the request contains a body with a list of files
  232. err = json.NewDecoder(r.Body).Decode(&files)
  233. } else {
  234. // Unless it is a delete request we need an action command
  235. err = json.NewDecoder(r.Body).Decode(&data)
  236. if err != nil {
  237. http.Error(w, fmt.Sprintf("Could not decode request body: %v", err.Error()),
  238. http.StatusBadRequest)
  239. return
  240. }
  241. actionObj, ok := data["action"]
  242. if !ok {
  243. http.Error(w, fmt.Sprintf("Action command is missing from request body"),
  244. http.StatusBadRequest)
  245. return
  246. }
  247. action = fmt.Sprint(actionObj)
  248. }
  249. fullPath := path.Join(resources[1:]...)
  250. if fullPath != "/" {
  251. fullPath = "/" + fullPath
  252. }
  253. dir, file := path.Split(fullPath)
  254. if requestType == "DELETE" {
  255. if len(files) == 0 {
  256. _, err = tree.ItemOp(dir, map[string]string{
  257. rufs.ItemOpAction: rufs.ItemOpActDelete,
  258. rufs.ItemOpName: file,
  259. })
  260. } else {
  261. // Delete the files given in the body
  262. for _, f := range files {
  263. dir, file := path.Split(f)
  264. if err == nil {
  265. _, err = tree.ItemOp(dir, map[string]string{
  266. rufs.ItemOpAction: rufs.ItemOpActDelete,
  267. rufs.ItemOpName: file,
  268. })
  269. }
  270. }
  271. }
  272. } else if action == "rename" {
  273. if newNamesParam, ok := data["newnames"]; ok {
  274. if newNames, ok := newNamesParam.([]interface{}); !ok {
  275. err = fmt.Errorf("Parameter newnames must be a list of filenames")
  276. } else {
  277. if filesParam, ok := data["files"]; !ok {
  278. err = fmt.Errorf("Parameter files is missing from request body")
  279. } else {
  280. if filesList, ok := filesParam.([]interface{}); !ok {
  281. err = fmt.Errorf("Parameter files must be a list of files")
  282. } else {
  283. for i, f := range filesList {
  284. dir, file := path.Split(fmt.Sprint(f))
  285. if err == nil {
  286. _, err = tree.ItemOp(dir, map[string]string{
  287. rufs.ItemOpAction: rufs.ItemOpActRename,
  288. rufs.ItemOpName: file,
  289. rufs.ItemOpNewName: fmt.Sprint(newNames[i]),
  290. })
  291. }
  292. }
  293. }
  294. }
  295. }
  296. } else {
  297. newName, ok := data["newname"]
  298. if !ok {
  299. err = fmt.Errorf("Parameter newname is missing from request body")
  300. } else {
  301. _, err = tree.ItemOp(dir, map[string]string{
  302. rufs.ItemOpAction: rufs.ItemOpActRename,
  303. rufs.ItemOpName: file,
  304. rufs.ItemOpNewName: fmt.Sprint(newName),
  305. })
  306. }
  307. }
  308. } else if action == "mkdir" {
  309. _, err = tree.ItemOp(dir, map[string]string{
  310. rufs.ItemOpAction: rufs.ItemOpActMkDir,
  311. rufs.ItemOpName: file,
  312. })
  313. } else if action == "copy" {
  314. dest, ok := data["destination"]
  315. if !ok {
  316. err = fmt.Errorf("Parameter destination is missing from request body")
  317. } else {
  318. // Create file list
  319. filesParam, hasFilesParam := data["files"]
  320. if hasFilesParam {
  321. if lf, ok := filesParam.([]interface{}); !ok {
  322. err = fmt.Errorf("Parameter files must be a list of files")
  323. } else {
  324. files = make([]string, len(lf))
  325. for i, f := range lf {
  326. files[i] = fmt.Sprint(f)
  327. }
  328. }
  329. } else {
  330. files = []string{fullPath}
  331. }
  332. if err == nil {
  333. // Create progress object
  334. uuid := fmt.Sprintf("%x", cryptutil.GenerateUUID())
  335. ret["progress_id"] = uuid
  336. mapLookup := resources[0] + "#" + uuid
  337. ProgressMap.Put(mapLookup, &Progress{
  338. Op: "Copy",
  339. Subject: "",
  340. Progress: 0,
  341. TotalProgress: 0,
  342. Item: 0,
  343. TotalItems: int64(len(files)),
  344. Errors: []string{},
  345. })
  346. go func() {
  347. err = tree.Copy(files, fmt.Sprint(dest),
  348. func(file string, writtenBytes, totalBytes, currentFile, totalFiles int64) {
  349. if p, ok := ProgressMap.Get(mapLookup); ok && writtenBytes > 0 {
  350. p.(*Progress).Subject = file
  351. p.(*Progress).Progress = writtenBytes
  352. p.(*Progress).TotalProgress = totalBytes
  353. p.(*Progress).Item = currentFile
  354. p.(*Progress).TotalItems = totalFiles
  355. }
  356. })
  357. if err != nil {
  358. if p, ok := ProgressMap.Get(mapLookup); ok {
  359. p.(*Progress).Errors = append(p.(*Progress).Errors, err.Error())
  360. }
  361. }
  362. }()
  363. // Wait a little bit so immediate errors are directly reported
  364. time.Sleep(10 * time.Millisecond)
  365. }
  366. }
  367. } else if action == "sync" {
  368. dest, ok := data["destination"]
  369. if !ok {
  370. err = fmt.Errorf("Parameter destination is missing from request body")
  371. } else {
  372. uuid := fmt.Sprintf("%x", cryptutil.GenerateUUID())
  373. ret["progress_id"] = uuid
  374. mapLookup := resources[0] + "#" + uuid
  375. ProgressMap.Put(mapLookup, &Progress{
  376. Op: "Sync",
  377. Subject: "",
  378. Progress: 0,
  379. TotalProgress: -1,
  380. Item: 0,
  381. TotalItems: -1,
  382. Errors: []string{},
  383. })
  384. go func() {
  385. err = tree.Sync(fullPath, fmt.Sprint(dest), true,
  386. func(op, srcFile, dstFile string, writtenBytes, totalBytes, currentFile, totalFiles int64) {
  387. if p, ok := ProgressMap.Get(mapLookup); ok && writtenBytes > 0 {
  388. p.(*Progress).Op = op
  389. p.(*Progress).Subject = srcFile
  390. p.(*Progress).Progress = writtenBytes
  391. p.(*Progress).TotalProgress = totalBytes
  392. p.(*Progress).Item = currentFile
  393. p.(*Progress).TotalItems = totalFiles
  394. }
  395. })
  396. if err != nil {
  397. if p, ok := ProgressMap.Get(mapLookup); ok {
  398. p.(*Progress).Errors = append(p.(*Progress).Errors, err.Error())
  399. }
  400. }
  401. }()
  402. // Wait a little bit so immediate errors are directly reported
  403. time.Sleep(10 * time.Millisecond)
  404. }
  405. } else {
  406. err = fmt.Errorf("Unknown action: %v", action)
  407. }
  408. if err != nil {
  409. http.Error(w, err.Error(), http.StatusBadRequest)
  410. return
  411. }
  412. // Write data
  413. w.Header().Set("content-type", "application/json; charset=utf-8")
  414. json.NewEncoder(w).Encode(ret)
  415. }
  416. /*
  417. HandlePOST handles REST calls to create or overwrite a new file.
  418. */
  419. func (f *fileEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
  420. var err error
  421. var tree *rufs.Tree
  422. var ok bool
  423. if len(resources) < 1 {
  424. http.Error(w, "Need a tree name and a file path",
  425. http.StatusBadRequest)
  426. return
  427. }
  428. if tree, ok, err = api.GetTree(resources[0]); err == nil && !ok {
  429. err = fmt.Errorf("Unknown tree: %v", resources[0])
  430. }
  431. if err != nil {
  432. http.Error(w, err.Error(), http.StatusBadRequest)
  433. return
  434. }
  435. // Check we have the right request type
  436. if r.MultipartForm == nil {
  437. if err = r.ParseMultipartForm(32 << 20); err != nil {
  438. http.Error(w, fmt.Sprintf("Could not read request body: %v", err.Error()),
  439. http.StatusBadRequest)
  440. return
  441. }
  442. }
  443. if r.MultipartForm != nil && r.MultipartForm.File != nil {
  444. // Check the files are in the form field uploadfile
  445. files, ok := r.MultipartForm.File["uploadfile"]
  446. if !ok {
  447. http.Error(w, "Could not find 'uploadfile' form field",
  448. http.StatusBadRequest)
  449. return
  450. }
  451. for _, file := range files {
  452. var f multipart.File
  453. // Write out all send files
  454. if f, err = file.Open(); err == nil {
  455. err = tree.WriteFileFromBuffer(path.Join(path.Join(resources[1:]...), file.Filename), f)
  456. }
  457. if err != nil {
  458. http.Error(w, fmt.Sprintf("Could not write file %v: %v", path.Join(resources[1:]...)+file.Filename, err.Error()),
  459. http.StatusBadRequest)
  460. return
  461. }
  462. }
  463. }
  464. if redirect := r.PostFormValue("redirect"); redirect != "" {
  465. // Do the redirect - make sure it is a local redirect
  466. if err = httputil.CheckLocalRedirect(redirect); err != nil {
  467. http.Error(w, fmt.Sprintf("Could not redirect: %v", err.Error()),
  468. http.StatusBadRequest)
  469. return
  470. }
  471. http.Redirect(w, r, redirect, http.StatusFound)
  472. }
  473. }
  474. /*
  475. SwaggerDefs is used to describe the endpoint in swagger.
  476. */
  477. func (f *fileEndpoint) SwaggerDefs(s map[string]interface{}) {
  478. s["paths"].(map[string]interface{})["/v1/file/{tree}/{path}"] = map[string]interface{}{
  479. "get": map[string]interface{}{
  480. "summary": "Read a file.",
  481. "description": "Return the contents of a file.",
  482. "produces": []string{
  483. "text/plain",
  484. "application/octet-stream",
  485. },
  486. "parameters": []map[string]interface{}{
  487. {
  488. "name": "tree",
  489. "in": "path",
  490. "description": "Name of the tree.",
  491. "required": true,
  492. "type": "string",
  493. },
  494. {
  495. "name": "path",
  496. "in": "path",
  497. "description": "File path.",
  498. "required": true,
  499. "type": "string",
  500. },
  501. },
  502. "responses": map[string]interface{}{
  503. "200": map[string]interface{}{
  504. "description": "Returns the content of the requested file.",
  505. },
  506. "default": map[string]interface{}{
  507. "description": "Error response",
  508. "schema": map[string]interface{}{
  509. "$ref": "#/definitions/Error",
  510. },
  511. },
  512. },
  513. },
  514. "put": map[string]interface{}{
  515. "summary": "Perform a file operation.",
  516. "description": "Perform a file operation like rename or copy.",
  517. "consumes": []string{
  518. "application/json",
  519. },
  520. "produces": []string{
  521. "text/plain",
  522. "application/json",
  523. },
  524. "parameters": []map[string]interface{}{
  525. {
  526. "name": "tree",
  527. "in": "path",
  528. "description": "Name of the tree.",
  529. "required": true,
  530. "type": "string",
  531. },
  532. {
  533. "name": "path",
  534. "in": "path",
  535. "description": "File path.",
  536. "required": true,
  537. "type": "string",
  538. },
  539. {
  540. "name": "operation",
  541. "in": "body",
  542. "description": "Operation which should be executes",
  543. "required": true,
  544. "schema": map[string]interface{}{
  545. "type": "object",
  546. "properties": map[string]interface{}{
  547. "action": map[string]interface{}{
  548. "description": "Action to perform.",
  549. "type": "string",
  550. "enum": []string{
  551. "rename",
  552. "mkdir",
  553. "copy",
  554. "sync",
  555. },
  556. },
  557. "newname": map[string]interface{}{
  558. "description": "New filename when renaming a single file.",
  559. "type": "string",
  560. },
  561. "newnames": map[string]interface{}{
  562. "description": "List of new file names when renaming multiple files using the files parameter.",
  563. "type": "array",
  564. "items": map[string]interface{}{
  565. "description": "New filename.",
  566. "type": "string",
  567. },
  568. },
  569. "destination": map[string]interface{}{
  570. "description": "Destination directory when copying files.",
  571. "type": "string",
  572. },
  573. "files": map[string]interface{}{
  574. "description": "List of (full path) files which should be copied / renamed.",
  575. "type": "array",
  576. "items": map[string]interface{}{
  577. "description": "File (with full path) which should be copied / renamed.",
  578. "type": "string",
  579. },
  580. },
  581. },
  582. },
  583. },
  584. },
  585. "responses": map[string]interface{}{
  586. "200": map[string]interface{}{
  587. "description": "Returns the content of the requested file.",
  588. },
  589. "default": map[string]interface{}{
  590. "description": "Error response",
  591. "schema": map[string]interface{}{
  592. "$ref": "#/definitions/Error",
  593. },
  594. },
  595. },
  596. },
  597. "post": map[string]interface{}{
  598. "summary": "Upload a file.",
  599. "description": "Upload or overwrite a file.",
  600. "produces": []string{
  601. "text/plain",
  602. },
  603. "consumes": []string{
  604. "multipart/form-data",
  605. },
  606. "parameters": []map[string]interface{}{
  607. {
  608. "name": "tree",
  609. "in": "path",
  610. "description": "Name of the tree.",
  611. "required": true,
  612. "type": "string",
  613. },
  614. {
  615. "name": "path",
  616. "in": "path",
  617. "description": "File path.",
  618. "required": true,
  619. "type": "string",
  620. },
  621. {
  622. "name": "redirect",
  623. "in": "formData",
  624. "description": "Page to redirect to after processing the request.",
  625. "required": false,
  626. "type": "string",
  627. },
  628. {
  629. "name": "uploadfile",
  630. "in": "formData",
  631. "description": "File(s) to create / overwrite.",
  632. "required": true,
  633. "type": "file",
  634. },
  635. },
  636. "responses": map[string]interface{}{
  637. "200": map[string]interface{}{
  638. "description": "Successful upload no redirect parameter given.",
  639. },
  640. "302": map[string]interface{}{
  641. "description": "Successful upload - redirect according to the given redirect parameter.",
  642. },
  643. "default": map[string]interface{}{
  644. "description": "Error response",
  645. "schema": map[string]interface{}{
  646. "$ref": "#/definitions/Error",
  647. },
  648. },
  649. },
  650. },
  651. "delete": map[string]interface{}{
  652. "summary": "Delete a file or directory.",
  653. "description": "Delete a file or directory.",
  654. "produces": []string{
  655. "text/plain",
  656. },
  657. "parameters": []map[string]interface{}{
  658. {
  659. "name": "tree",
  660. "in": "path",
  661. "description": "Name of the tree.",
  662. "required": true,
  663. "type": "string",
  664. },
  665. {
  666. "name": "path",
  667. "in": "path",
  668. "description": "File or directory path.",
  669. "required": true,
  670. "type": "string",
  671. },
  672. {
  673. "name": "filelist",
  674. "in": "body",
  675. "description": "List of (full path) files which should be deleted",
  676. "required": false,
  677. "schema": map[string]interface{}{
  678. "type": "array",
  679. "items": map[string]interface{}{
  680. "description": "File (with full path) which should be deleted.",
  681. "type": "string",
  682. },
  683. },
  684. },
  685. },
  686. "responses": map[string]interface{}{
  687. "200": map[string]interface{}{
  688. "description": "Returns the content of the requested file.",
  689. },
  690. "default": map[string]interface{}{
  691. "description": "Error response",
  692. "schema": map[string]interface{}{
  693. "$ref": "#/definitions/Error",
  694. },
  695. },
  696. },
  697. },
  698. }
  699. // Add generic error object to definition
  700. s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
  701. "description": "A human readable error mesage.",
  702. "type": "string",
  703. }
  704. }