asset.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package api
  17. import (
  18. "fmt"
  19. "net/http"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "github.com/88250/go-humanize"
  24. "github.com/88250/gulu"
  25. "github.com/djherbis/times"
  26. "github.com/gin-gonic/gin"
  27. "github.com/siyuan-note/filelock"
  28. "github.com/siyuan-note/siyuan/kernel/model"
  29. "github.com/siyuan-note/siyuan/kernel/sql"
  30. "github.com/siyuan-note/siyuan/kernel/util"
  31. )
  32. func statAsset(c *gin.Context) {
  33. ret := gulu.Ret.NewResult()
  34. defer c.JSON(http.StatusOK, ret)
  35. arg, ok := util.JsonArg(c, ret)
  36. if !ok {
  37. return
  38. }
  39. path := arg["path"].(string)
  40. var p string
  41. if strings.HasPrefix(path, "assets/") {
  42. var err error
  43. p, err = model.GetAssetAbsPath(path)
  44. if err != nil {
  45. ret.Code = 1
  46. return
  47. }
  48. } else if strings.HasPrefix(path, "file://") {
  49. p = strings.TrimPrefix(path, "file://")
  50. if strings.Contains(p, ":") {
  51. p = strings.TrimPrefix(p, "/")
  52. }
  53. } else {
  54. ret.Code = 1
  55. return
  56. }
  57. info, err := os.Stat(p)
  58. if err != nil {
  59. ret.Code = 1
  60. return
  61. }
  62. t, err := times.Stat(p)
  63. if err != nil {
  64. ret.Code = 1
  65. return
  66. }
  67. updated := t.ModTime().UnixMilli()
  68. hUpdated := t.ModTime().Format("2006-01-02 15:04:05")
  69. created := updated
  70. hCreated := hUpdated
  71. // Check birthtime before use
  72. if t.HasBirthTime() {
  73. created = t.BirthTime().UnixMilli()
  74. hCreated = t.BirthTime().Format("2006-01-02 15:04:05")
  75. }
  76. ret.Data = map[string]interface{}{
  77. "size": info.Size(),
  78. "hSize": humanize.IBytesCustomCeil(uint64(info.Size()), 2),
  79. "created": created,
  80. "hCreated": hCreated,
  81. "updated": updated,
  82. "hUpdated": hUpdated,
  83. }
  84. }
  85. func fullReindexAssetContent(c *gin.Context) {
  86. ret := gulu.Ret.NewResult()
  87. defer c.JSON(http.StatusOK, ret)
  88. model.ReindexAssetContent()
  89. }
  90. func getImageOCRText(c *gin.Context) {
  91. ret := gulu.Ret.NewResult()
  92. defer c.JSON(http.StatusOK, ret)
  93. arg, ok := util.JsonArg(c, ret)
  94. if !ok {
  95. return
  96. }
  97. path := arg["path"].(string)
  98. ret.Data = map[string]interface{}{
  99. "text": util.GetAssetText(path),
  100. }
  101. }
  102. func setImageOCRText(c *gin.Context) {
  103. ret := gulu.Ret.NewResult()
  104. defer c.JSON(http.StatusOK, ret)
  105. arg, ok := util.JsonArg(c, ret)
  106. if !ok {
  107. return
  108. }
  109. path := arg["path"].(string)
  110. text := arg["text"].(string)
  111. util.SetAssetText(path, text)
  112. // 刷新 OCR 结果到数据库
  113. util.NodeOCRQueueLock.Lock()
  114. defer util.NodeOCRQueueLock.Unlock()
  115. for _, id := range util.NodeOCRQueue {
  116. sql.IndexNodeQueue(id)
  117. }
  118. util.NodeOCRQueue = nil
  119. }
  120. func ocr(c *gin.Context) {
  121. ret := gulu.Ret.NewResult()
  122. defer c.JSON(http.StatusOK, ret)
  123. arg, ok := util.JsonArg(c, ret)
  124. if !ok {
  125. return
  126. }
  127. path := arg["path"].(string)
  128. ocrJSON := util.OcrAsset(path)
  129. ret.Data = map[string]interface{}{
  130. "text": util.GetOcrJsonText(ocrJSON),
  131. "ocrJSON": ocrJSON,
  132. }
  133. }
  134. func renameAsset(c *gin.Context) {
  135. ret := gulu.Ret.NewResult()
  136. defer c.JSON(http.StatusOK, ret)
  137. arg, ok := util.JsonArg(c, ret)
  138. if !ok {
  139. return
  140. }
  141. oldPath := arg["oldPath"].(string)
  142. newName := arg["newName"].(string)
  143. newPath, err := model.RenameAsset(oldPath, newName)
  144. if err != nil {
  145. ret.Code = -1
  146. ret.Msg = err.Error()
  147. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  148. return
  149. }
  150. ret.Data = map[string]interface{}{
  151. "newPath": newPath,
  152. }
  153. }
  154. func getDocImageAssets(c *gin.Context) {
  155. ret := gulu.Ret.NewResult()
  156. defer c.JSON(http.StatusOK, ret)
  157. arg, ok := util.JsonArg(c, ret)
  158. if !ok {
  159. return
  160. }
  161. id := arg["id"].(string)
  162. assets, err := model.DocImageAssets(id)
  163. if err != nil {
  164. ret.Code = -1
  165. ret.Msg = err.Error()
  166. return
  167. }
  168. ret.Data = assets
  169. }
  170. func setFileAnnotation(c *gin.Context) {
  171. ret := gulu.Ret.NewResult()
  172. defer c.JSON(http.StatusOK, ret)
  173. arg, ok := util.JsonArg(c, ret)
  174. if !ok {
  175. return
  176. }
  177. p := arg["path"].(string)
  178. p = strings.ReplaceAll(p, "%23", "#")
  179. data := arg["data"].(string)
  180. writePath, err := resolveFileAnnotationAbsPath(p)
  181. if err != nil {
  182. ret.Code = -1
  183. ret.Msg = err.Error()
  184. return
  185. }
  186. if err := filelock.WriteFile(writePath, []byte(data)); err != nil {
  187. ret.Code = -1
  188. ret.Msg = err.Error()
  189. return
  190. }
  191. model.IncSync()
  192. }
  193. func getFileAnnotation(c *gin.Context) {
  194. ret := gulu.Ret.NewResult()
  195. defer c.JSON(http.StatusOK, ret)
  196. arg, ok := util.JsonArg(c, ret)
  197. if !ok {
  198. return
  199. }
  200. p := arg["path"].(string)
  201. p = strings.ReplaceAll(p, "%23", "#")
  202. readPath, err := resolveFileAnnotationAbsPath(p)
  203. if err != nil {
  204. ret.Code = -1
  205. ret.Msg = err.Error()
  206. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  207. return
  208. }
  209. if !filelock.IsExist(readPath) {
  210. ret.Code = 1
  211. return
  212. }
  213. data, err := filelock.ReadFile(readPath)
  214. if err != nil {
  215. ret.Code = -1
  216. ret.Msg = err.Error()
  217. return
  218. }
  219. ret.Data = map[string]interface{}{
  220. "data": string(data),
  221. }
  222. }
  223. func resolveFileAnnotationAbsPath(assetRelPath string) (ret string, err error) {
  224. filePath := strings.TrimSuffix(assetRelPath, ".sya")
  225. absPath, err := model.GetAssetAbsPath(filePath)
  226. if err != nil {
  227. return
  228. }
  229. dir := filepath.Dir(absPath)
  230. base := filepath.Base(assetRelPath)
  231. ret = filepath.Join(dir, base)
  232. return
  233. }
  234. func removeUnusedAsset(c *gin.Context) {
  235. ret := gulu.Ret.NewResult()
  236. defer c.JSON(http.StatusOK, ret)
  237. arg, ok := util.JsonArg(c, ret)
  238. if !ok {
  239. return
  240. }
  241. p := arg["path"].(string)
  242. asset := model.RemoveUnusedAsset(p)
  243. ret.Data = map[string]interface{}{
  244. "path": asset,
  245. }
  246. }
  247. func removeUnusedAssets(c *gin.Context) {
  248. ret := gulu.Ret.NewResult()
  249. defer c.JSON(http.StatusOK, ret)
  250. paths := model.RemoveUnusedAssets()
  251. ret.Data = map[string]interface{}{
  252. "paths": paths,
  253. }
  254. }
  255. func getUnusedAssets(c *gin.Context) {
  256. ret := gulu.Ret.NewResult()
  257. defer c.JSON(http.StatusOK, ret)
  258. unusedAssets := model.UnusedAssets()
  259. total := len(unusedAssets)
  260. // List only 512 unreferenced assets https://github.com/siyuan-note/siyuan/issues/13075
  261. const maxUnusedAssets = 512
  262. if total > maxUnusedAssets {
  263. unusedAssets = unusedAssets[:maxUnusedAssets]
  264. util.PushMsg(fmt.Sprintf(model.Conf.Language(251), total, maxUnusedAssets), 5000)
  265. }
  266. ret.Data = map[string]interface{}{
  267. "unusedAssets": unusedAssets,
  268. }
  269. }
  270. func getMissingAssets(c *gin.Context) {
  271. ret := gulu.Ret.NewResult()
  272. defer c.JSON(http.StatusOK, ret)
  273. missingAssets := model.MissingAssets()
  274. ret.Data = map[string]interface{}{
  275. "missingAssets": missingAssets,
  276. }
  277. }
  278. func resolveAssetPath(c *gin.Context) {
  279. ret := gulu.Ret.NewResult()
  280. defer c.JSON(http.StatusOK, ret)
  281. arg, ok := util.JsonArg(c, ret)
  282. if !ok {
  283. return
  284. }
  285. path := arg["path"].(string)
  286. p, err := model.GetAssetAbsPath(path)
  287. if err != nil {
  288. ret.Code = -1
  289. ret.Msg = err.Error()
  290. ret.Data = map[string]interface{}{"closeTimeout": 3000}
  291. return
  292. }
  293. ret.Data = p
  294. return
  295. }
  296. func uploadCloud(c *gin.Context) {
  297. ret := gulu.Ret.NewResult()
  298. defer c.JSON(http.StatusOK, ret)
  299. arg, ok := util.JsonArg(c, ret)
  300. if !ok {
  301. return
  302. }
  303. rootID := arg["id"].(string)
  304. count, err := model.UploadAssets2Cloud(rootID)
  305. if err != nil {
  306. ret.Code = -1
  307. ret.Msg = err.Error()
  308. ret.Data = map[string]interface{}{"closeTimeout": 3000}
  309. return
  310. }
  311. util.PushMsg(fmt.Sprintf(model.Conf.Language(41), count), 3000)
  312. }
  313. func insertLocalAssets(c *gin.Context) {
  314. ret := gulu.Ret.NewResult()
  315. defer c.JSON(http.StatusOK, ret)
  316. arg, ok := util.JsonArg(c, ret)
  317. if !ok {
  318. return
  319. }
  320. assetPathsArg := arg["assetPaths"].([]interface{})
  321. var assetPaths []string
  322. for _, pathArg := range assetPathsArg {
  323. assetPaths = append(assetPaths, pathArg.(string))
  324. }
  325. isUpload := true
  326. isUploadArg := arg["isUpload"]
  327. if nil != isUploadArg {
  328. isUpload = isUploadArg.(bool)
  329. }
  330. id := arg["id"].(string)
  331. succMap, err := model.InsertLocalAssets(id, assetPaths, isUpload)
  332. if err != nil {
  333. ret.Code = -1
  334. ret.Msg = err.Error()
  335. return
  336. }
  337. ret.Data = map[string]interface{}{
  338. "succMap": succMap,
  339. }
  340. }