file_system.go 137 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440
  1. package main
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "hash/crc32"
  9. "io"
  10. "io/fs"
  11. "math"
  12. "mime"
  13. "net/http"
  14. "net/url"
  15. "os"
  16. "sync"
  17. "sync/atomic"
  18. "path/filepath"
  19. "runtime"
  20. "sort"
  21. "strconv"
  22. "strings"
  23. "time"
  24. "github.com/gorilla/websocket"
  25. uuid "github.com/satori/go.uuid"
  26. "imuslab.com/arozos/mod/compatibility"
  27. "imuslab.com/arozos/mod/filesystem"
  28. "imuslab.com/arozos/mod/filesystem/arozfs"
  29. fsp "imuslab.com/arozos/mod/filesystem/fspermission"
  30. "imuslab.com/arozos/mod/filesystem/fssort"
  31. "imuslab.com/arozos/mod/filesystem/fuzzy"
  32. hidden "imuslab.com/arozos/mod/filesystem/hidden"
  33. "imuslab.com/arozos/mod/filesystem/localversion"
  34. metadata "imuslab.com/arozos/mod/filesystem/metadata"
  35. "imuslab.com/arozos/mod/filesystem/shortcut"
  36. module "imuslab.com/arozos/mod/modules"
  37. prout "imuslab.com/arozos/mod/prouter"
  38. "imuslab.com/arozos/mod/share"
  39. "imuslab.com/arozos/mod/share/shareEntry"
  40. storage "imuslab.com/arozos/mod/storage"
  41. user "imuslab.com/arozos/mod/user"
  42. "imuslab.com/arozos/mod/utils"
  43. )
  44. var (
  45. thumbRenderHandler *metadata.RenderHandler
  46. shareEntryTable *shareEntry.ShareEntryTable
  47. shareManager *share.Manager
  48. wsConnectionStore sync.Map //Storage of all the file operation tasks, keyed by operation id
  49. fileOprTaskLock sync.RWMutex //Lock guarding the mutable fields of the task records above
  50. fileOprJanitorStarted atomic.Bool //Whether the finished task record janitor is already running
  51. )
  52. // Status of a file operation task or of one of the files inside it
  53. const (
  54. FsTask_Pending = "pending"
  55. FsTask_Ongoing = "ongoing"
  56. FsTask_Completed = "completed"
  57. FsTask_Error = "error"
  58. FsTask_Cancelled = "cancelled"
  59. //How long an operation that finished without error is kept before the
  60. //janitor drops it, in seconds. Long enough for a connected dialog to render
  61. //the result and fire its completion callback, short enough that the listing
  62. //does not fill up with finished transfers.
  63. fileOprFinishedRecordTTL = 3
  64. //How many failed operations are kept per user. Failures are never dropped on
  65. //a timer so they can still be reviewed after reopening the desktop from
  66. //another browser or machine, but the backlog is bounded.
  67. fileOprErrorRecordLimit = 32
  68. //How often the janitor sweeps the finished task records. Kept well under
  69. //fileOprFinishedRecordTTL so a record is actually dropped near its deadline
  70. //rather than up to a whole sweep later.
  71. fileOprJanitorInterval = 2 * time.Second
  72. )
  73. type trashedFile struct {
  74. Filename string
  75. Filepath string
  76. FileExt string
  77. IsDir bool
  78. Filesize int64
  79. RemoveTimestamp int64
  80. RemoveDate string
  81. OriginalPath string
  82. OriginalFilename string
  83. }
  84. // A single source file inside a file operation task
  85. type fileOperationSubtask struct {
  86. Filename string //Base name of this source file
  87. Src string //Virtual path of this source file
  88. IsDir bool //Whether this source is a folder
  89. Size int64 //Total size of this source file in bytes
  90. Done int64 //Bytes of this source file that are already processed
  91. Progress float64 //Progress of this source file, in percentage
  92. Status string //Status of this source file, see the FsTask_* constants
  93. Error string //Error message of this source file, if any
  94. }
  95. type fileOperationTask struct {
  96. ID string //Unique id for the task operation
  97. Owner string //Owner of the file opr
  98. Operation string //Type of the file opr: move / copy / zip / unzip
  99. Src string //Source folder for opr
  100. Dest string //Destination folder for opr
  101. Progress float64 //Progress for the operation
  102. LatestFile string //Latest file that is current transfering
  103. FileOperationSignal int //Current control signal of the file opr
  104. Files []*fileOperationSubtask //Per source file progress of this operation
  105. TotalSize int64 //Total size of all the source files in bytes
  106. DoneSize int64 //Total bytes processed so far
  107. StartTime int64 //Unix timestamp of when this operation started
  108. EndTime int64 //Unix timestamp of when this operation ended, 0 if still running
  109. Status string //Status of this operation, see the FsTask_* constants
  110. Error string //Error message of this operation, if any
  111. }
  112. func FileSystemInit() {
  113. router := prout.NewModuleRouter(prout.RouterOption{
  114. ModuleName: "File Manager",
  115. AdminOnly: false,
  116. UserHandler: userHandler,
  117. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  118. utils.SendErrorResponse(w, "Permission Denied")
  119. },
  120. })
  121. //Upload related functions
  122. router.HandleFunc("/system/file_system/upload", system_fs_handleUpload)
  123. router.HandleFunc("/system/file_system/lowmemUpload", system_fs_handleLowMemoryUpload)
  124. //Other file operations
  125. router.HandleFunc("/system/file_system/validateFileOpr", system_fs_validateFileOpr)
  126. router.HandleFunc("/system/file_system/fileOpr", system_fs_handleOpr)
  127. router.HandleFunc("/system/file_system/ws/fileOpr", system_fs_handleWebSocketOpr)
  128. router.HandleFunc("/system/file_system/fileOprAsync", system_fs_handleAsyncOpr)
  129. router.HandleFunc("/system/file_system/ws/fileOprStatus", system_fs_handleFileOprStatusWebSocket)
  130. router.HandleFunc("/system/file_system/listDir", system_fs_handleList)
  131. router.HandleFunc("/system/file_system/listDirHash", system_fs_handleDirHash)
  132. router.HandleFunc("/system/file_system/listRoots", system_fs_listRoot)
  133. router.HandleFunc("/system/file_system/listDrives", system_fs_listDrives)
  134. router.HandleFunc("/system/file_system/newItem", system_fs_handleNewObjects)
  135. router.HandleFunc("/system/file_system/preference", system_fs_handleUserPreference)
  136. router.HandleFunc("/system/file_system/listTrash", system_fs_scanTrashBin)
  137. router.HandleFunc("/system/file_system/ws/listTrash", system_fs_WebSocketScanTrashBin)
  138. router.HandleFunc("/system/file_system/clearTrash", system_fs_clearTrashBin)
  139. router.HandleFunc("/system/file_system/trashSettings", system_fs_handleTrashSettings)
  140. router.HandleFunc("/system/file_system/restoreTrash", system_fs_restoreFile)
  141. router.HandleFunc("/system/file_system/zipHandler", system_fs_zipHandler)
  142. router.HandleFunc("/system/file_system/getProperties", system_fs_getFileProperties)
  143. router.HandleFunc("/system/file_system/versionHistory", system_fs_FileVersionHistory)
  144. router.HandleFunc("/system/file_system/handleFilePermission", system_fs_handleFilePermission)
  145. router.HandleFunc("/system/file_system/search", system_fs_handleFileSearch)
  146. //Thumbnail caching functions
  147. router.HandleFunc("/system/file_system/handleFolderCache", system_fs_handleFolderCache)
  148. router.HandleFunc("/system/file_system/handleCacheRender", system_fs_handleCacheRender)
  149. router.HandleFunc("/system/file_system/loadThumbnail", system_fs_handleThumbnailLoad)
  150. //Directory specific config
  151. router.HandleFunc("/system/file_system/sortMode", system_fs_handleFolderSortModePreference)
  152. //Register the module
  153. moduleHandler.RegisterModule(module.ModuleInfo{
  154. Name: "File Manager",
  155. Group: "System Tools",
  156. IconPath: "SystemAO/file_system/img/small_icon.png",
  157. Version: "1.0",
  158. StartDir: "SystemAO/file_system/file_explorer.html",
  159. SupportFW: true,
  160. InitFWSize: []int{1075, 610},
  161. LaunchFWDir: "SystemAO/file_system/file_explorer.html",
  162. SupportEmb: false,
  163. })
  164. //Register the Trashbin module
  165. moduleHandler.RegisterModule(module.ModuleInfo{
  166. Name: "Trash Bin",
  167. Group: "System Tools",
  168. IconPath: "SystemAO/file_system/trashbin_img/small_icon.png",
  169. Version: "1.0",
  170. StartDir: "SystemAO/file_system/trashbin.html",
  171. SupportFW: true,
  172. InitFWSize: []int{400, 200},
  173. LaunchFWDir: "SystemAO/file_system/trashbin.html",
  174. SupportEmb: false,
  175. SupportedExt: []string{"*"},
  176. })
  177. //Register the Zip Extractor module
  178. moduleHandler.RegisterModule(module.ModuleInfo{
  179. Name: "Zip Extractor",
  180. Group: "System Tools",
  181. IconPath: "SystemAO/file_system/img/zip_extractor.png",
  182. Version: "1.0",
  183. SupportFW: false,
  184. LaunchEmb: "SystemAO/file_system/zip_extractor.html",
  185. SupportEmb: true,
  186. InitEmbSize: []int{260, 120},
  187. SupportedExt: []string{".zip"},
  188. })
  189. //Create user root if not exists
  190. err := os.MkdirAll(filepath.Join(*root_directory, "users/"), 0755)
  191. if err != nil {
  192. systemWideLogger.PrintAndLog("File System", "Failed to create system storage root", err)
  193. panic(err)
  194. }
  195. //Create database table if not exists
  196. err = sysdb.NewTable("fs")
  197. if err != nil {
  198. systemWideLogger.PrintAndLog("File System", "Failed to create table for file system", err)
  199. panic(err)
  200. }
  201. //Create new table for sort preference
  202. err = sysdb.NewTable("fs-sortpref")
  203. if err != nil {
  204. systemWideLogger.PrintAndLog("File System", "Failed to create table for file system", err)
  205. panic(err)
  206. }
  207. //Create a RenderHandler for caching thumbnails
  208. thumbRenderHandler = metadata.NewRenderHandler()
  209. /*
  210. Share Related Registering
  211. This section of functions create and register the file share service
  212. for the arozos
  213. */
  214. //Create a share manager to handle user file sharae
  215. shareEntryTable = shareEntry.NewShareEntryTable(sysdb)
  216. shareManager = share.NewShareManager(share.Options{
  217. AuthAgent: authAgent,
  218. ShareEntryTable: shareEntryTable,
  219. UserHandler: userHandler,
  220. HostName: *host_name,
  221. TmpFolder: *tmp_directory,
  222. })
  223. //Share related functions
  224. router.HandleFunc("/system/file_system/share/new", shareManager.HandleCreateNewShare)
  225. router.HandleFunc("/system/file_system/share/delete", shareManager.HandleDeleteShare)
  226. router.HandleFunc("/system/file_system/share/edit", shareManager.HandleEditShare)
  227. router.HandleFunc("/system/file_system/share/checkShared", shareManager.HandleShareCheck)
  228. router.HandleFunc("/system/file_system/share/list", shareManager.HandleListAllShares)
  229. //Handle the main share function
  230. //Share function is now routed by the main router
  231. //http.HandleFunc("/share", shareManager.HandleShareAccess)
  232. /*
  233. File Operation Resume Functions
  234. */
  235. //Create a sync map for file operation opened connections
  236. wsConnectionStore = sync.Map{}
  237. router.HandleFunc("/system/file_system/ongoing", system_fs_HandleOnGoingTasks)
  238. /*
  239. Nighly Tasks
  240. These functions allow file system to clear and maintain
  241. the arozos file system when no one is using the system
  242. */
  243. //Trash bin retention and size limit, per user
  244. registerSetting(settingModule{
  245. Name: "File Manager",
  246. Desc: "Trash Bin Retention and Size",
  247. IconPath: "SystemAO/file_system/trashbin_img/small_icon.png",
  248. Group: "Disk",
  249. StartDir: "SystemAO/disk/filemanager/trashsettings.html",
  250. })
  251. //Clear tmp folder if files is placed here too long
  252. nightlyManager.RegisterNightlyTask(system_fs_clearOldTmpFiles)
  253. //Clear shares that its parent file no longer exists in the system
  254. shareManager.ValidateAndClearShares()
  255. nightlyManager.RegisterNightlyTask(shareManager.ValidateAndClearShares)
  256. //Clear file version history that is more than 30 days
  257. go func() {
  258. //Start version history cleaning in background
  259. system_fs_clearVersionHistories()
  260. systemWideLogger.PrintAndLog("File System", "Startup File Version History Cleaning Completed", nil)
  261. }()
  262. systemWideLogger.PrintAndLog("File System", "Started File Version History Cleaning in background", nil)
  263. nightlyManager.RegisterNightlyTask(system_fs_clearVersionHistories)
  264. //Purge trashed files older than each user's retention setting
  265. nightlyManager.RegisterNightlyTask(system_fs_clearExpiredTrash)
  266. }
  267. /*
  268. File Search
  269. Handle file search in wildcard and recursive search
  270. */
  271. func system_fs_handleFileSearch(w http.ResponseWriter, r *http.Request) {
  272. //Get the user information
  273. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  274. if err != nil {
  275. utils.SendErrorResponse(w, "User not logged in")
  276. return
  277. }
  278. //Get the search target root path
  279. vpath, err := utils.PostPara(r, "path")
  280. if err != nil {
  281. utils.SendErrorResponse(w, "Invalid vpath given")
  282. return
  283. }
  284. keyword, err := utils.PostPara(r, "keyword")
  285. if err != nil {
  286. utils.SendErrorResponse(w, "Invalid keyword given")
  287. return
  288. }
  289. //Check if case sensitive is enabled
  290. casesensitve, _ := utils.PostPara(r, "casesensitive")
  291. vrootID, _, err := filesystem.GetIDFromVirtualPath(vpath)
  292. var targetFSH *filesystem.FileSystemHandler = nil
  293. if err != nil {
  294. utils.SendErrorResponse(w, "Invalid path given")
  295. return
  296. }
  297. targetFSH, err = GetFsHandlerByUUID(vrootID)
  298. if err != nil {
  299. utils.SendErrorResponse(w, err.Error())
  300. return
  301. }
  302. //Translate the vpath to realpath if this is an actual path on disk
  303. resolvedPath, err := targetFSH.FileSystemAbstraction.VirtualPathToRealPath(vpath, userinfo.Username)
  304. if err != nil {
  305. utils.SendErrorResponse(w, "Invalid path given")
  306. return
  307. }
  308. rpath := resolvedPath
  309. //Check if the search mode is recursive keyword or wildcard
  310. if len(keyword) > 1 && keyword[:1] == "/" {
  311. //Wildcard
  312. //Updates 31-12-2021: Do not allow wildcard search on virtual type's FSH
  313. if targetFSH == nil {
  314. utils.SendErrorResponse(w, "Invalid path given")
  315. return
  316. }
  317. targetFshAbs := targetFSH.FileSystemAbstraction
  318. wildcard := keyword[1:]
  319. matchingFiles, err := targetFshAbs.Glob(filepath.Join(rpath, wildcard))
  320. if err != nil {
  321. utils.SendErrorResponse(w, err.Error())
  322. return
  323. }
  324. //Prepare result struct
  325. results := []filesystem.FileData{}
  326. escaped := false
  327. for _, matchedFile := range matchingFiles {
  328. thisVpath, _ := targetFSH.FileSystemAbstraction.RealPathToVirtualPath(matchedFile, userinfo.Username)
  329. isHidden, _ := hidden.IsHidden(thisVpath, true)
  330. if !isHidden {
  331. results = append(results, filesystem.GetFileDataFromPath(targetFSH, thisVpath, matchedFile, 2))
  332. }
  333. }
  334. if escaped {
  335. utils.SendErrorResponse(w, "Search keywords contain escape character!")
  336. return
  337. }
  338. //OK. Tidy up the results
  339. js, _ := json.Marshal(results)
  340. utils.SendJSONResponse(w, string(js))
  341. } else {
  342. //Updates 2022-02-16: Build the fuzzy matcher if it is not a wildcard search
  343. matcher := fuzzy.NewFuzzyMatcher(keyword, casesensitve == "true")
  344. //Recursive keyword
  345. results := []filesystem.FileData{}
  346. var err error = nil
  347. fshAbs := targetFSH.FileSystemAbstraction
  348. err = fshAbs.Walk(rpath, func(path string, info os.FileInfo, err error) error {
  349. thisFilename := filepath.Base(path)
  350. if casesensitve != "true" {
  351. thisFilename = strings.ToLower(thisFilename)
  352. }
  353. if !filesystem.IsInsideHiddenFolder(path) {
  354. if matcher.Match(thisFilename) {
  355. //This is a matching file
  356. thisVpath, _ := fshAbs.RealPathToVirtualPath(path, userinfo.Username)
  357. results = append(results, filesystem.GetFileDataFromPath(targetFSH, thisVpath, path, 2))
  358. }
  359. }
  360. return nil
  361. })
  362. if err != nil {
  363. utils.SendErrorResponse(w, err.Error())
  364. return
  365. }
  366. //OK. Tidy up the results
  367. js, _ := json.Marshal(results)
  368. utils.SendJSONResponse(w, string(js))
  369. }
  370. }
  371. /*
  372. Handle low-memory upload operations
  373. This function is specailly designed to work with low memory devices
  374. (e.g. ZeroPi / Orange Pi Zero with 512MB RAM)
  375. Two cases
  376. 1. Not Buffer FS + Huge File
  377. => Write chunks to fsa + merge to fsa
  378. 2. Else
  379. => write chunks to tmp (via os package) + merge to fsa
  380. */
  381. /*
  382. Low memory (websocket) upload tunables
  383. uploadIdleTimeout is how long the server waits for the next chunk before it
  384. assumes the client is gone. A paused upload is deliberately exempt from it -
  385. the client is still there, it is just not sending - so uploadPauseTimeout
  386. takes over instead, to cover an upload someone paused and forgot about.
  387. uploadPauseCloseCode is a private-use websocket close code, so the client can
  388. tell "your pause expired" apart from an ordinary disconnect and offer retry.
  389. */
  390. const (
  391. uploadIdleTimeout = 300 * time.Second
  392. uploadPauseTimeout = 2 * time.Hour
  393. uploadPauseCloseCode = 4001
  394. )
  395. func system_fs_handleLowMemoryUpload(w http.ResponseWriter, r *http.Request) {
  396. //Get user info
  397. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  398. if err != nil {
  399. w.WriteHeader(http.StatusUnauthorized)
  400. w.Write([]byte("401 - Unauthorized"))
  401. return
  402. }
  403. //Get filename and upload path
  404. filename, err := utils.GetPara(r, "filename")
  405. if filename == "" || err != nil {
  406. w.WriteHeader(http.StatusInternalServerError)
  407. w.Write([]byte("500 - Invalid filename given"))
  408. return
  409. }
  410. //Get upload target directory
  411. uploadTarget, err := utils.GetPara(r, "path")
  412. if uploadTarget == "" || err != nil {
  413. w.WriteHeader(http.StatusInternalServerError)
  414. w.Write([]byte("500 - Invalid path given"))
  415. return
  416. }
  417. //Unescape the upload target path
  418. unescapedPath, err := url.PathUnescape(uploadTarget)
  419. if err != nil {
  420. unescapedPath = uploadTarget
  421. }
  422. //Check if the user can write to this folder
  423. if !userinfo.CanWrite(unescapedPath) {
  424. //No permission
  425. w.WriteHeader(http.StatusForbidden)
  426. w.Write([]byte("403 - Access Denied"))
  427. return
  428. }
  429. fsh, subpath, err := GetFSHandlerSubpathFromVpath(unescapedPath)
  430. if err != nil {
  431. w.WriteHeader(http.StatusInternalServerError)
  432. w.Write([]byte("500 - Path translation failed"))
  433. return
  434. }
  435. fshAbs := fsh.FileSystemAbstraction
  436. //Translate the upload target directory
  437. realUploadPath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  438. if err != nil {
  439. w.WriteHeader(http.StatusInternalServerError)
  440. w.Write([]byte("500 - Path translation failed"))
  441. return
  442. }
  443. //Check if it is huge file upload mode
  444. isHugeFile := false
  445. hugefile, _ := utils.GetPara(r, "hugefile")
  446. if hugefile == "true" && !fsh.RequireBuffer {
  447. //Huge file mode is only compatible with local file systems
  448. //For remote file system, use buffer to tmp then upload method
  449. isHugeFile = true
  450. }
  451. //Create destination folder if not exists
  452. targetUploadLocation := arozfs.ToSlash(filepath.Join(realUploadPath, filename))
  453. if !fshAbs.FileExists(realUploadPath) {
  454. fshAbs.MkdirAll(realUploadPath, 0755)
  455. }
  456. //Generate an UUID for this upload
  457. uploadUUID := uuid.NewV4().String()
  458. uploadFolder := filepath.Join(*tmp_directory, "uploads", uploadUUID)
  459. if isHugeFile {
  460. //Change to upload directly to target disk
  461. uploadFolder = filepath.Join(realUploadPath, ".metadata/.upload", uploadUUID)
  462. fshAbs.MkdirAll(uploadFolder, 0700)
  463. } else {
  464. os.MkdirAll(uploadFolder, 0700)
  465. }
  466. //Start websocket connection
  467. var upgrader = websocket.Upgrader{}
  468. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  469. c, err := upgrader.Upgrade(w, r, nil)
  470. if err != nil {
  471. systemWideLogger.PrintAndLog("File System", "Failed to upgrade websocket connection: ", err)
  472. w.WriteHeader(http.StatusInternalServerError)
  473. w.Write([]byte("500 WebSocket upgrade failed"))
  474. return
  475. }
  476. defer c.Close()
  477. //Handle WebSocket upload
  478. blockCounter := 0
  479. chunkName := []string{}
  480. /*
  481. Shared with the watchdog goroutine below, so these are read and written
  482. atomically. pausedSince is 0 while the upload is running, and holds the
  483. unix time the client paused at otherwise.
  484. */
  485. var lastChunkArrivalTime int64 = time.Now().Unix()
  486. var pausedSince int64 = 0
  487. //Setup a timeout listener, check if connection still active every 1 minute
  488. ticker := time.NewTicker(60 * time.Second)
  489. done := make(chan bool)
  490. go func() {
  491. for {
  492. select {
  493. case <-done:
  494. return
  495. case <-ticker.C:
  496. if pausedAt := atomic.LoadInt64(&pausedSince); pausedAt > 0 {
  497. //Paused: silence is expected here, so only the pause clock
  498. //applies. WriteControl is the one write that is safe to make
  499. //from this goroutine while the reader holds the connection.
  500. if time.Since(time.Unix(pausedAt, 0)) > uploadPauseTimeout {
  501. systemWideLogger.PrintAndLog("File System", "Upload paused for too long. Disconnecting.", errors.New("upload pause timeout"))
  502. c.WriteControl(websocket.CloseMessage,
  503. websocket.FormatCloseMessage(uploadPauseCloseCode, "upload pause timeout"),
  504. time.Now().Add(time.Second))
  505. time.Sleep(1 * time.Second)
  506. c.Close()
  507. return
  508. }
  509. continue
  510. }
  511. if time.Now().Unix()-atomic.LoadInt64(&lastChunkArrivalTime) > int64(uploadIdleTimeout.Seconds()) {
  512. //Already 5 minutes without new data arraival. Stop connection
  513. systemWideLogger.PrintAndLog("File System", "Upload WebSocket connection timeout. Disconnecting.", errors.New("websocket connection timeout"))
  514. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  515. time.Sleep(1 * time.Second)
  516. c.Close()
  517. return
  518. }
  519. }
  520. }
  521. }()
  522. totalFileSize := int64(0)
  523. // Full-file CRC32 hasher (IEEE polynomial, matches JS crc32Table implementation)
  524. fileCRC32Hasher := crc32.NewIEEE()
  525. // Per-chunk state machine:
  526. // The client sends a text metadata frame {"index":N,"checksum":"hex"} before each binary frame.
  527. var pendingChunkIndex int
  528. var pendingChunkChecksum string
  529. expectingBinary := false
  530. for {
  531. mt, message, err := c.ReadMessage()
  532. if err != nil {
  533. //Connection closed by client. Clear the tmp folder and exit
  534. systemWideLogger.PrintAndLog("File System", "Upload terminated by client. Cleaning tmp folder", err)
  535. //Clear the tmp folder
  536. time.Sleep(1 * time.Second)
  537. if isHugeFile {
  538. fshAbs.RemoveAll(uploadFolder)
  539. } else {
  540. os.RemoveAll(uploadFolder)
  541. }
  542. return
  543. }
  544. if mt == 1 {
  545. // Text frame – either a control message, chunk metadata or done signal
  546. textMsg := strings.TrimSpace(string(message))
  547. /*
  548. Control messages are checked before anything else, and regardless
  549. of expectingBinary: a pause can land after the metadata frame but
  550. before its binary payload, and the flow control below would
  551. otherwise mistake it for a malformed chunk header.
  552. A chunk header or done signal unmarshals into this struct with
  553. every field false, so this is safe to try first.
  554. */
  555. var ctrl struct {
  556. Pause bool `json:"pause"`
  557. Resume bool `json:"resume"`
  558. Ping bool `json:"ping"`
  559. }
  560. if jsonErr := json.Unmarshal([]byte(textMsg), &ctrl); jsonErr == nil &&
  561. (ctrl.Pause || ctrl.Resume || ctrl.Ping) {
  562. if ctrl.Pause {
  563. atomic.StoreInt64(&pausedSince, time.Now().Unix())
  564. } else if ctrl.Resume {
  565. atomic.StoreInt64(&pausedSince, 0)
  566. //Do not count the paused stretch as idle time
  567. atomic.StoreInt64(&lastChunkArrivalTime, time.Now().Unix())
  568. }
  569. /*
  570. Any control message counts as the client still being there,
  571. so it also holds off the idle timeout. That matters when a
  572. pause is raised before this socket finished connecting: the
  573. pause frame is lost, but the heartbeat still arrives, and
  574. without this the upload would be reaped as idle anyway.
  575. Answering every control message is what puts traffic on the
  576. wire in both directions - a reverse proxy in front of us will
  577. drop a connection it sees nothing on.
  578. */
  579. atomic.StoreInt64(&lastChunkArrivalTime, time.Now().Unix())
  580. c.WriteMessage(1, []byte(`{"pong":true}`))
  581. continue
  582. }
  583. if !expectingBinary {
  584. // Check if this is the done signal
  585. var doneSignal struct {
  586. Done bool `json:"done"`
  587. TotalChunks int `json:"totalChunks"`
  588. FileChecksum string `json:"fileChecksum"`
  589. }
  590. if jsonErr := json.Unmarshal([]byte(textMsg), &doneSignal); jsonErr == nil && doneSignal.Done {
  591. // Verify the full-file CRC32 before merging
  592. if doneSignal.FileChecksum != "" {
  593. computedSum := fileCRC32Hasher.Sum32()
  594. computedSumBytes := []byte{byte(computedSum >> 24), byte(computedSum >> 16), byte(computedSum >> 8), byte(computedSum)}
  595. computedHex := hex.EncodeToString(computedSumBytes)
  596. if doneSignal.FileChecksum != computedHex {
  597. systemWideLogger.PrintAndLog("File System", "Upload file checksum mismatch: client="+doneSignal.FileChecksum+" server="+computedHex, nil)
  598. c.WriteMessage(1, []byte(`{"error":"File integrity check failed: full-file checksum mismatch"}`))
  599. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  600. time.Sleep(1 * time.Second)
  601. c.Close()
  602. if isHugeFile {
  603. fshAbs.RemoveAll(uploadFolder)
  604. } else {
  605. os.RemoveAll(uploadFolder)
  606. }
  607. return
  608. }
  609. }
  610. // Checksum verified – proceed to merge
  611. break
  612. }
  613. // Parse as chunk metadata
  614. var meta struct {
  615. Index int `json:"index"`
  616. Checksum string `json:"checksum"`
  617. }
  618. if jsonErr := json.Unmarshal([]byte(textMsg), &meta); jsonErr != nil {
  619. systemWideLogger.PrintAndLog("File System", "Invalid chunk metadata received: "+textMsg, jsonErr)
  620. continue
  621. }
  622. pendingChunkIndex = meta.Index
  623. pendingChunkChecksum = meta.Checksum
  624. expectingBinary = true
  625. }
  626. } else if mt == 2 {
  627. // Binary frame – the chunk data that follows a metadata frame
  628. if !expectingBinary {
  629. systemWideLogger.PrintAndLog("File System", "Received binary chunk without preceding metadata, ignoring", nil)
  630. continue
  631. }
  632. expectingBinary = false
  633. // Verify chunk CRC32
  634. chunkSum := crc32.ChecksumIEEE(message)
  635. chunkSumBytes := []byte{byte(chunkSum >> 24), byte(chunkSum >> 16), byte(chunkSum >> 8), byte(chunkSum)}
  636. chunkHex := hex.EncodeToString(chunkSumBytes)
  637. if pendingChunkChecksum != "" && pendingChunkChecksum != chunkHex {
  638. // CRC32 mismatch – ask the client to re-send this chunk
  639. systemWideLogger.PrintAndLog("File System", "Chunk "+strconv.Itoa(pendingChunkIndex)+" CRC32 mismatch: expected "+pendingChunkChecksum+" got "+chunkHex, nil)
  640. retryMsg, _ := json.Marshal(map[string]int{"retryChunk": pendingChunkIndex})
  641. c.WriteMessage(1, retryMsg)
  642. // Reset state; client will re-send the metadata+binary for this chunk
  643. continue
  644. }
  645. // Chunk verified – write to tmp folder.
  646. // Use pendingChunkIndex as the canonical filename so that a retry overwrites
  647. // the previous (corrupted) attempt rather than creating a duplicate entry.
  648. chunkFilepath := filepath.Join(uploadFolder, "upld_"+strconv.Itoa(pendingChunkIndex))
  649. if pendingChunkIndex == blockCounter {
  650. // First time this chunk index is successfully received
  651. chunkName = append(chunkName, chunkFilepath)
  652. blockCounter++
  653. }
  654. var writeErr error
  655. if isHugeFile {
  656. writeErr = fshAbs.WriteFile(chunkFilepath, message, 0700)
  657. } else {
  658. writeErr = os.WriteFile(chunkFilepath, message, 0700)
  659. }
  660. if writeErr != nil {
  661. systemWideLogger.PrintAndLog("File System", "Upload chunk write failed: "+writeErr.Error(), writeErr)
  662. c.WriteMessage(1, []byte(`{"error":"Write file chunk to disk failed"}`))
  663. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  664. time.Sleep(1 * time.Second)
  665. c.Close()
  666. if isHugeFile {
  667. fshAbs.RemoveAll(uploadFolder)
  668. } else {
  669. os.RemoveAll(uploadFolder)
  670. }
  671. return
  672. }
  673. // Update running full-file CRC32 with the verified chunk data
  674. fileCRC32Hasher.Write(message)
  675. // Update timing and quota tracking
  676. atomic.StoreInt64(&lastChunkArrivalTime, time.Now().Unix())
  677. totalFileSize += int64(len(message))
  678. if totalFileSize > max_upload_size {
  679. c.WriteMessage(1, []byte(`{"error":"File size too large"}`))
  680. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  681. time.Sleep(1 * time.Second)
  682. c.Close()
  683. if isHugeFile {
  684. fshAbs.RemoveAll(uploadFolder)
  685. } else {
  686. os.RemoveAll(uploadFolder)
  687. }
  688. return
  689. } else if !userinfo.StorageQuota.HaveSpace(totalFileSize) {
  690. c.WriteMessage(1, []byte(`{"error":"User Storage Quota Exceeded"}`))
  691. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  692. time.Sleep(1 * time.Second)
  693. c.Close()
  694. if isHugeFile {
  695. fshAbs.RemoveAll(uploadFolder)
  696. } else {
  697. os.RemoveAll(uploadFolder)
  698. }
  699. return
  700. }
  701. // Acknowledge the chunk; client will send the next metadata+binary pair
  702. c.WriteMessage(1, []byte("next"))
  703. }
  704. }
  705. //Try to decode the location if possible
  706. decodedUploadLocation, err := url.PathUnescape(targetUploadLocation)
  707. if err != nil {
  708. decodedUploadLocation = targetUploadLocation
  709. }
  710. //Do not allow % sign in filename. Replace all with underscore
  711. decodedUploadLocation = strings.ReplaceAll(decodedUploadLocation, "%", "_")
  712. //Merge the file. Merge file location must be on local machine
  713. mergeFileLocation := decodedUploadLocation
  714. var out arozfs.File
  715. if fsh.RequireBuffer {
  716. //The merge file location must be local buffer
  717. mergeFileLocation = getFsBufferFilepath(decodedUploadLocation, false)
  718. out, err = os.OpenFile(mergeFileLocation, os.O_CREATE|os.O_WRONLY, 0755)
  719. } else {
  720. //The merge file location can be local or remote that support OpenFile.
  721. out, err = fshAbs.OpenFile(mergeFileLocation, os.O_CREATE|os.O_WRONLY, 0755)
  722. }
  723. defer out.Close()
  724. if err != nil {
  725. systemWideLogger.PrintAndLog("File System", "Failed to open file:"+err.Error(), err)
  726. c.WriteMessage(1, []byte(`{"error":"Failed to open destination file"}`))
  727. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  728. time.Sleep(1 * time.Second)
  729. c.Close()
  730. return
  731. }
  732. for counter, filesrc := range chunkName {
  733. var srcChunkReader arozfs.File
  734. if isHugeFile {
  735. srcChunkReader, err = fshAbs.Open(filesrc)
  736. } else {
  737. srcChunkReader, err = os.Open(filesrc)
  738. }
  739. if err != nil {
  740. systemWideLogger.PrintAndLog("File System", "Failed to open Source Chunk"+filesrc+" with error "+err.Error(), err)
  741. c.WriteMessage(1, []byte(`{"error":"Failed to open Source Chunk"}`))
  742. return
  743. }
  744. io.Copy(out, srcChunkReader)
  745. srcChunkReader.Close()
  746. //Delete file immediately to save space
  747. if isHugeFile {
  748. fshAbs.Remove(filesrc)
  749. } else {
  750. os.Remove(filesrc)
  751. }
  752. //Write to websocket for the percentage of upload is written fro tmp to dest
  753. moveProg := strconv.Itoa(int(math.Round(float64(counter)/float64(len(chunkName))*100))) + "%"
  754. c.WriteMessage(1, []byte(`{"move":"`+moveProg+`"}`))
  755. }
  756. out.Close()
  757. //Check if the size fit in user quota
  758. var fi fs.FileInfo
  759. if fsh.RequireBuffer {
  760. fi, err = os.Stat(mergeFileLocation)
  761. } else {
  762. fi, err = fshAbs.Stat(mergeFileLocation)
  763. }
  764. if err != nil {
  765. // Could not obtain stat, handle error
  766. systemWideLogger.PrintAndLog("File System", "Failed to validate uploaded file: "+mergeFileLocation+". Error Message: "+err.Error(), err)
  767. c.WriteMessage(1, []byte(`{"error":"Failed to validate uploaded file"}`))
  768. return
  769. }
  770. if !userinfo.StorageQuota.HaveSpace(fi.Size()) {
  771. c.WriteMessage(1, []byte(`{"error":"User Storage Quota Exceeded"}`))
  772. if fsh.RequireBuffer {
  773. os.RemoveAll(mergeFileLocation)
  774. } else {
  775. fshAbs.RemoveAll(mergeFileLocation)
  776. }
  777. return
  778. }
  779. //Upload it to remote side if it fits the user quota && is buffer file
  780. if fsh.RequireBuffer {
  781. //This is local buffer file. Upload to dest fsh
  782. f, err := os.Open(mergeFileLocation)
  783. if err != nil {
  784. systemWideLogger.PrintAndLog("File System", "Failed to open buffered file at "+mergeFileLocation+" with error "+err.Error(), err)
  785. c.WriteMessage(1, []byte(`{"error":"Failed to open buffered object"}`))
  786. f.Close()
  787. return
  788. }
  789. err = fsh.FileSystemAbstraction.WriteStream(decodedUploadLocation, f, 0775)
  790. if err != nil {
  791. systemWideLogger.PrintAndLog("File System", "Failed to write to file system: "+fsh.UUID+" with error "+err.Error(), err)
  792. c.WriteMessage(1, []byte(`{"error":"Failed to upload to remote file system"}`))
  793. f.Close()
  794. return
  795. }
  796. //Remove the buffered file
  797. f.Close()
  798. os.Remove(mergeFileLocation)
  799. }
  800. //Log the upload filename
  801. systemWideLogger.PrintAndLog("File System", userinfo.Username+" uploaded a file: "+filepath.Base(decodedUploadLocation), nil)
  802. //Set owner of the new uploaded file
  803. userinfo.SetOwnerOfFile(fsh, unescapedPath)
  804. //Return complete signal
  805. c.WriteMessage(1, []byte("OK"))
  806. //Stop the timeout listner
  807. done <- true
  808. //Clear the tmp folder
  809. time.Sleep(300 * time.Millisecond)
  810. if isHugeFile {
  811. fshAbs.RemoveAll(uploadFolder)
  812. } else {
  813. os.RemoveAll(uploadFolder)
  814. }
  815. //Close WebSocket connection after finished
  816. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  817. time.Sleep(300 * time.Second)
  818. c.Close()
  819. }
  820. /*
  821. Handle FORM POST based upload
  822. This function is design for general SBCs or computers with more than 2GB of RAM
  823. (e.g. Raspberry Pi 4 / Linux Server)
  824. */
  825. func system_fs_handleUpload(w http.ResponseWriter, r *http.Request) {
  826. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  827. if err != nil {
  828. utils.SendErrorResponse(w, "User not logged in")
  829. return
  830. }
  831. //Limit the max upload size to the user defined size
  832. if max_upload_size != 0 {
  833. r.Body = http.MaxBytesReader(w, r.Body, max_upload_size)
  834. }
  835. err = r.ParseMultipartForm(int64(*upload_buf) << 20)
  836. if err != nil {
  837. //Filesize too big
  838. systemWideLogger.PrintAndLog("File System", "Upload file size too big", err)
  839. utils.SendErrorResponse(w, "File too large")
  840. return
  841. }
  842. file, handler, err := r.FormFile("file")
  843. if err != nil {
  844. systemWideLogger.PrintAndLog("File System", "Error Retrieving File from upload by user: "+userinfo.Username, err)
  845. utils.SendErrorResponse(w, "Unable to parse file from upload")
  846. return
  847. }
  848. //Get upload target directory
  849. uploadTarget, _ := utils.PostPara(r, "path")
  850. if uploadTarget == "" {
  851. utils.SendErrorResponse(w, "Upload target cannot be empty.")
  852. return
  853. }
  854. fsh, subpath, err := GetFSHandlerSubpathFromVpath(uploadTarget)
  855. if err != nil {
  856. utils.SendErrorResponse(w, "Invalid upload target")
  857. return
  858. }
  859. targetFs := fsh.FileSystemAbstraction
  860. //Translate the upload target directory
  861. realUploadPath, err := targetFs.VirtualPathToRealPath(subpath, userinfo.Username)
  862. if err != nil {
  863. utils.SendErrorResponse(w, "Upload target is invalid or permission denied.")
  864. return
  865. }
  866. storeFilename := handler.Filename //Filename of the uploaded file
  867. //Get request time
  868. uploadStartTime := time.Now().UnixNano() / int64(time.Millisecond)
  869. //Update for Firefox 94.0.2 (x64) -> Now firefox put its relative path inside Content-Disposition -> filename
  870. //Skip this handler logic if Firefox version is in between 84.0.2 to 94.0.2
  871. bypassMetaCheck := compatibility.FirefoxBrowserVersionForBypassUploadMetaHeaderCheck(r.UserAgent())
  872. if !bypassMetaCheck && strings.Contains(handler.Header["Content-Disposition"][0], "filename=") && strings.Contains(handler.Header["Content-Disposition"][0], "/") {
  873. //This is a firefox MIME Header for file inside folder. Look for the actual filename
  874. headerFields := strings.Split(handler.Header["Content-Disposition"][0], "; ")
  875. possibleRelativePathname := ""
  876. for _, hf := range headerFields {
  877. if strings.Contains(hf, "filename=") && len(hf) > 11 {
  878. //Found. Overwrite original filename with the latest one
  879. possibleRelativePathname = hf[10 : len(hf)-1]
  880. storeFilename = possibleRelativePathname
  881. break
  882. }
  883. }
  884. }
  885. destFilepath := arozfs.ToSlash(filepath.Join(realUploadPath, storeFilename))
  886. //fmt.Println(destFilepath, realUploadPath, storeFilename)
  887. if !targetFs.FileExists(filepath.Dir(destFilepath)) {
  888. targetFs.MkdirAll(filepath.Dir(destFilepath), 0775)
  889. }
  890. //Check if the upload target is read only.
  891. accmode := userinfo.GetPathAccessPermission(uploadTarget)
  892. if accmode == arozfs.FsReadOnly {
  893. utils.SendErrorResponse(w, "The upload target is Read Only.")
  894. return
  895. } else if accmode == arozfs.FsDenied {
  896. utils.SendErrorResponse(w, "Access Denied")
  897. return
  898. }
  899. //Check for storage quota
  900. uploadFileSize := handler.Size
  901. if !userinfo.StorageQuota.HaveSpace(uploadFileSize) {
  902. utils.SendErrorResponse(w, "User Storage Quota Exceeded")
  903. return
  904. }
  905. //Do not allow % sign in filename. Replace all with underscore
  906. destFilepath = strings.ReplaceAll(destFilepath, "%", "_")
  907. //Move the file to destination file location
  908. if *enable_asyncFileUpload {
  909. //Use Async upload method
  910. systemWideLogger.PrintAndLog("File System", "AsyncFileUpload flag has been deprecated. Falling back to blocking upload.", errors.New("call to deprecated flag: asyncFileUpload"))
  911. }
  912. err = targetFs.WriteStream(destFilepath, file, 0775)
  913. if err != nil {
  914. systemWideLogger.PrintAndLog("File System", "Write stream to destination file system abstraction from upload failed", err)
  915. utils.SendErrorResponse(w, "Write upload to destination disk failed")
  916. return
  917. }
  918. file.Close()
  919. //Clear up buffered files
  920. r.MultipartForm.RemoveAll()
  921. //Set the ownership of file
  922. userinfo.SetOwnerOfFile(fsh, uploadTarget)
  923. //Finish up the upload
  924. /*
  925. fmt.Printf("Uploaded File: %+v\n", handler.Filename)
  926. fmt.Printf("File Size: %+v\n", handler.Size)
  927. fmt.Printf("MIME Header: %+v\n", handler.Header)
  928. fmt.Println("Upload target: " + realUploadPath)
  929. */
  930. //Fnish upload. Fix the tmp filename
  931. systemWideLogger.PrintAndLog("File System", userinfo.Username+" uploaded a file: "+handler.Filename, nil)
  932. //Do upload finishing stuff
  933. //Add a delay to the complete message to make sure browser catch the return value
  934. currentTimeMilli := time.Now().UnixNano() / int64(time.Millisecond)
  935. if currentTimeMilli-uploadStartTime < 100 {
  936. //Sleep until at least 300 ms
  937. time.Sleep(time.Duration(100 - (currentTimeMilli - uploadStartTime)))
  938. }
  939. //Completed
  940. utils.SendOK(w)
  941. }
  942. // Validate if the copy and target process will involve file overwriting problem.
  943. func system_fs_validateFileOpr(w http.ResponseWriter, r *http.Request) {
  944. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  945. if err != nil {
  946. utils.SendErrorResponse(w, err.Error())
  947. return
  948. }
  949. vsrcFiles, _ := utils.PostPara(r, "src")
  950. vdestFile, _ := utils.PostPara(r, "dest")
  951. var duplicateFiles []string = []string{}
  952. //Loop through all files are see if there are duplication during copy and paste
  953. sourceFiles := []string{}
  954. decodedSourceFiles, _ := url.QueryUnescape(vsrcFiles)
  955. err = json.Unmarshal([]byte(decodedSourceFiles), &sourceFiles)
  956. if err != nil {
  957. utils.SendErrorResponse(w, "Source file JSON parse error.")
  958. return
  959. }
  960. destFsh, destSubpath, err := GetFSHandlerSubpathFromVpath(vdestFile)
  961. if err != nil {
  962. utils.SendErrorResponse(w, "Operation Valid Failed: "+err.Error())
  963. return
  964. }
  965. rdestFile, _ := destFsh.FileSystemAbstraction.VirtualPathToRealPath(destSubpath, userinfo.Username)
  966. for _, file := range sourceFiles {
  967. srcFsh, srcSubpath, _ := GetFSHandlerSubpathFromVpath(string(file))
  968. rsrcFile, _ := srcFsh.FileSystemAbstraction.VirtualPathToRealPath(srcSubpath, userinfo.Username)
  969. if destFsh.FileSystemAbstraction.FileExists(filepath.Join(rdestFile, filepath.Base(rsrcFile))) {
  970. //File exists already.
  971. vpath, _ := srcFsh.FileSystemAbstraction.RealPathToVirtualPath(rsrcFile, userinfo.Username)
  972. duplicateFiles = append(duplicateFiles, vpath)
  973. }
  974. }
  975. jsonString, _ := json.Marshal(duplicateFiles)
  976. utils.SendJSONResponse(w, string(jsonString))
  977. }
  978. /*
  979. Trash bin settings
  980. Stored per user in the same preference table the File Manager already uses,
  981. so they follow the account rather than the browser.
  982. Both settings use zero as "no limit", which is also what an account that has
  983. never opened the settings page reads back:
  984. trash/retentionDays 0 = never auto remove, otherwise 1..365
  985. trash/quotaBytes 0 = unlimited
  986. */
  987. const (
  988. trashRetentionPrefKey = "trash/retentionDays"
  989. trashQuotaPrefKey = "trash/quotaBytes"
  990. trashMaxRetentionDays = 365
  991. //What an account that has never opened the settings page holds trash for.
  992. //An explicitly stored zero still means "never remove" - only the absence
  993. //of a stored value falls back to this.
  994. trashDefaultRetentionDays = 30
  995. )
  996. func system_fs_getTrashRetentionDays(username string) int {
  997. result := ""
  998. err := sysdb.Read("fs", "pref/"+trashRetentionPrefKey+"/"+username, &result)
  999. if err != nil {
  1000. //Never set for this account
  1001. return trashDefaultRetentionDays
  1002. }
  1003. days, err := strconv.Atoi(result)
  1004. if err != nil || days < 0 {
  1005. //Stored but unreadable - the default is a safer answer than "never"
  1006. return trashDefaultRetentionDays
  1007. }
  1008. if days > trashMaxRetentionDays {
  1009. days = trashMaxRetentionDays
  1010. }
  1011. return days
  1012. }
  1013. func system_fs_getTrashQuotaBytes(username string) int64 {
  1014. result := ""
  1015. err := sysdb.Read("fs", "pref/"+trashQuotaPrefKey+"/"+username, &result)
  1016. if err != nil {
  1017. return 0
  1018. }
  1019. quota, err := utils.StringToInt64(result)
  1020. if err != nil || quota < 0 {
  1021. return 0
  1022. }
  1023. return quota
  1024. }
  1025. /*
  1026. Space a single trashed entry takes up
  1027. A folder counts for everything inside it, not the zero bytes its own entry
  1028. reports - otherwise a user could fill the bin with folders and never reach
  1029. the quota. Hidden files are included because they were moved along with the
  1030. rest, and the trash itself lives inside a hidden folder.
  1031. */
  1032. func system_fs_getTrashEntrySize(fsh *filesystem.FileSystemHandler, rpath string) int64 {
  1033. if fsh.FileSystemAbstraction.IsDir(rpath) {
  1034. size, _ := fsh.GetDirctorySizeFromRealPath(rpath, true)
  1035. return size
  1036. }
  1037. return int64(fsh.FileSystemAbstraction.GetFileSize(rpath))
  1038. }
  1039. // Total bytes currently sitting in a user's trash across every file system
  1040. func system_fs_getTrashUsage(username string) int64 {
  1041. files, fshs, err := system_fs_listTrash(username)
  1042. if err != nil {
  1043. return 0
  1044. }
  1045. total := int64(0)
  1046. for c, file := range files {
  1047. total += system_fs_getTrashEntrySize(fshs[c], file)
  1048. }
  1049. return total
  1050. }
  1051. /*
  1052. Read or write the trash settings for the logged in user.
  1053. GET with no value -> {"RetentionDays":n,"QuotaBytes":n,"MaxRetentionDays":365,"UsedBytes":n}
  1054. POST retentionDays / quotaBytes -> stores them
  1055. */
  1056. func system_fs_handleTrashSettings(w http.ResponseWriter, r *http.Request) {
  1057. username, err := authAgent.GetUserName(w, r)
  1058. if err != nil {
  1059. utils.SendErrorResponse(w, "User not logged in")
  1060. return
  1061. }
  1062. retention, retentionSet := utils.PostPara(r, "retentionDays")
  1063. quota, quotaSet := utils.PostPara(r, "quotaBytes")
  1064. if retentionSet == nil && retention != "" {
  1065. days, err := strconv.Atoi(retention)
  1066. if err != nil || days < 0 || days > trashMaxRetentionDays {
  1067. utils.SendErrorResponse(w, "Invalid retention day given")
  1068. return
  1069. }
  1070. sysdb.Write("fs", "pref/"+trashRetentionPrefKey+"/"+username, strconv.Itoa(days))
  1071. }
  1072. if quotaSet == nil && quota != "" {
  1073. bytes, err := utils.StringToInt64(quota)
  1074. if err != nil || bytes < 0 {
  1075. utils.SendErrorResponse(w, "Invalid quota given")
  1076. return
  1077. }
  1078. sysdb.Write("fs", "pref/"+trashQuotaPrefKey+"/"+username, utils.Int64ToString(bytes))
  1079. }
  1080. type trashSettings struct {
  1081. RetentionDays int
  1082. QuotaBytes int64
  1083. MaxRetentionDays int
  1084. UsedBytes int64
  1085. }
  1086. js, _ := json.Marshal(trashSettings{
  1087. RetentionDays: system_fs_getTrashRetentionDays(username),
  1088. QuotaBytes: system_fs_getTrashQuotaBytes(username),
  1089. MaxRetentionDays: trashMaxRetentionDays,
  1090. UsedBytes: system_fs_getTrashUsage(username),
  1091. })
  1092. utils.SendJSONResponse(w, string(js))
  1093. }
  1094. /*
  1095. Nightly cleanup
  1096. Removes trashed files past their owner's retention window. Users who have
  1097. not set a retention (or set it to zero) keep their trash indefinitely, which
  1098. is the behaviour every account had before this setting existed.
  1099. */
  1100. func system_fs_clearExpiredTrash() {
  1101. for _, username := range authAgent.ListUsers() {
  1102. retentionDays := system_fs_getTrashRetentionDays(username)
  1103. if retentionDays <= 0 {
  1104. //Auto removal disabled for this user
  1105. continue
  1106. }
  1107. userinfo, err := userHandler.GetUserInfoFromUsername(username)
  1108. if err != nil {
  1109. continue
  1110. }
  1111. cutoff := time.Now().Unix() - int64(retentionDays)*86400
  1112. files, fshs, err := system_fs_listTrash(username)
  1113. if err != nil {
  1114. continue
  1115. }
  1116. removed := 0
  1117. for c, file := range files {
  1118. /*
  1119. The removal time is stored as the file extension when the file
  1120. was recycled. Anything without a parsable timestamp is left
  1121. alone rather than guessed at.
  1122. */
  1123. ext := filepath.Ext(file)
  1124. if len(ext) < 2 {
  1125. continue
  1126. }
  1127. timestamp, err := utils.StringToInt64(ext[1:])
  1128. if err != nil || timestamp > cutoff {
  1129. continue
  1130. }
  1131. fshAbs := fshs[c].FileSystemAbstraction
  1132. fileVpath, err := fshAbs.RealPathToVirtualPath(file, username)
  1133. if err == nil && userinfo.IsOwnerOfFile(fshs[c], fileVpath) {
  1134. userinfo.RemoveOwnershipFromFile(fshs[c], fileVpath)
  1135. }
  1136. fshAbs.RemoveAll(file)
  1137. //Drop the .trash folder too once nothing is left in it
  1138. remaining, _ := fshAbs.Glob(filepath.Dir(file) + "/*")
  1139. if len(remaining) == 0 {
  1140. fshAbs.Remove(filepath.Dir(file))
  1141. }
  1142. removed++
  1143. }
  1144. if removed > 0 {
  1145. systemWideLogger.PrintAndLog("File System", "Removed "+strconv.Itoa(removed)+
  1146. " expired trash item(s) for user "+username, nil)
  1147. }
  1148. }
  1149. }
  1150. // Scan all directory and get trash file and send back results with WebSocket
  1151. func system_fs_WebSocketScanTrashBin(w http.ResponseWriter, r *http.Request) {
  1152. //Get and check user permission
  1153. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1154. if err != nil {
  1155. utils.SendErrorResponse(w, "User not logged in")
  1156. return
  1157. }
  1158. //Upgrade to websocket
  1159. var upgrader = websocket.Upgrader{}
  1160. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  1161. c, err := upgrader.Upgrade(w, r, nil)
  1162. if err != nil {
  1163. w.WriteHeader(http.StatusInternalServerError)
  1164. w.Write([]byte("500 - " + err.Error()))
  1165. systemWideLogger.PrintAndLog("System", fmt.Sprint("Websocket Upgrade Error:", err.Error()), nil)
  1166. return
  1167. }
  1168. //Start Scanning
  1169. scanningRoots := []*filesystem.FileSystemHandler{}
  1170. //Get all roots to scan
  1171. for _, storage := range userinfo.GetAllFileSystemHandler() {
  1172. if storage.Hierarchy == "backup" {
  1173. //Skip this fsh
  1174. continue
  1175. }
  1176. scanningRoots = append(scanningRoots, storage)
  1177. }
  1178. for _, fsh := range scanningRoots {
  1179. thisFshAbs := fsh.FileSystemAbstraction
  1180. rootPath, err := thisFshAbs.VirtualPathToRealPath("", userinfo.Username)
  1181. if err != nil {
  1182. continue
  1183. }
  1184. err = thisFshAbs.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
  1185. oneLevelUpper := filepath.Base(filepath.Dir(path))
  1186. if oneLevelUpper == ".trash" {
  1187. //This is a trashbin dir.
  1188. file := path
  1189. //Parse the trashFile struct
  1190. timestamp := filepath.Ext(file)[1:]
  1191. originalName := strings.TrimSuffix(filepath.Base(file), filepath.Ext(filepath.Base(file)))
  1192. originalExt := filepath.Ext(filepath.Base(originalName))
  1193. virtualFilepath, _ := thisFshAbs.RealPathToVirtualPath(file, userinfo.Username)
  1194. virtualOrgPath, _ := thisFshAbs.RealPathToVirtualPath(filepath.Dir(filepath.Dir(filepath.Dir(file))), userinfo.Username)
  1195. rawsize := thisFshAbs.GetFileSize(file)
  1196. timestampInt64, _ := utils.StringToInt64(timestamp)
  1197. removeTimeDate := time.Unix(timestampInt64, 0)
  1198. if thisFshAbs.IsDir(file) {
  1199. originalExt = ""
  1200. }
  1201. thisTrashFileObject := trashedFile{
  1202. Filename: filepath.Base(file),
  1203. Filepath: virtualFilepath,
  1204. FileExt: originalExt,
  1205. IsDir: thisFshAbs.IsDir(file),
  1206. Filesize: int64(rawsize),
  1207. RemoveTimestamp: timestampInt64,
  1208. RemoveDate: removeTimeDate.Format("2006-01-02 15:04:05"),
  1209. OriginalPath: virtualOrgPath,
  1210. OriginalFilename: originalName,
  1211. }
  1212. //Send out the result as JSON string
  1213. js, _ := json.Marshal(thisTrashFileObject)
  1214. err := c.WriteMessage(1, js)
  1215. if err != nil {
  1216. //Connection already closed
  1217. return err
  1218. }
  1219. }
  1220. return nil
  1221. })
  1222. if err != nil {
  1223. //Scan or client connection error (Connection closed?)
  1224. return
  1225. }
  1226. }
  1227. //Close connection after finished
  1228. c.Close()
  1229. }
  1230. // Scan all the directory and get trash files within the system
  1231. func system_fs_scanTrashBin(w http.ResponseWriter, r *http.Request) {
  1232. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1233. if err != nil {
  1234. utils.SendErrorResponse(w, err.Error())
  1235. return
  1236. }
  1237. username := userinfo.Username
  1238. results := []trashedFile{}
  1239. files, fshs, err := system_fs_listTrash(username)
  1240. if err != nil {
  1241. utils.SendErrorResponse(w, err.Error())
  1242. return
  1243. }
  1244. //Get information of each files and process it into results
  1245. for c, file := range files {
  1246. fsAbs := fshs[c].FileSystemAbstraction
  1247. timestamp := filepath.Ext(file)[1:]
  1248. originalName := strings.TrimSuffix(filepath.Base(file), filepath.Ext(filepath.Base(file)))
  1249. originalExt := filepath.Ext(filepath.Base(originalName))
  1250. virtualFilepath, _ := fsAbs.RealPathToVirtualPath(file, userinfo.Username)
  1251. virtualOrgPath, _ := fsAbs.RealPathToVirtualPath(filepath.Dir(filepath.Dir(filepath.Dir(file))), userinfo.Username)
  1252. rawsize := fsAbs.GetFileSize(file)
  1253. timestampInt64, _ := utils.StringToInt64(timestamp)
  1254. removeTimeDate := time.Unix(timestampInt64, 0)
  1255. if fsAbs.IsDir(file) {
  1256. originalExt = ""
  1257. }
  1258. results = append(results, trashedFile{
  1259. Filename: filepath.Base(file),
  1260. Filepath: virtualFilepath,
  1261. FileExt: originalExt,
  1262. IsDir: fsAbs.IsDir(file),
  1263. Filesize: int64(rawsize),
  1264. RemoveTimestamp: timestampInt64,
  1265. RemoveDate: removeTimeDate.Format("2006-01-02 15:04:05"),
  1266. OriginalPath: virtualOrgPath,
  1267. OriginalFilename: originalName,
  1268. })
  1269. }
  1270. //Sort the results by date, latest on top
  1271. sort.Slice(results[:], func(i, j int) bool {
  1272. return results[i].RemoveTimestamp > results[j].RemoveTimestamp
  1273. })
  1274. //Format and return the json results
  1275. jsonString, _ := json.Marshal(results)
  1276. utils.SendJSONResponse(w, string(jsonString))
  1277. }
  1278. // Restore a trashed file to its parent dir
  1279. func system_fs_restoreFile(w http.ResponseWriter, r *http.Request) {
  1280. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1281. if err != nil {
  1282. utils.SendErrorResponse(w, err.Error())
  1283. return
  1284. }
  1285. targetTrashedFile, err := utils.PostPara(r, "src")
  1286. if err != nil {
  1287. utils.SendErrorResponse(w, "Invalid src given")
  1288. return
  1289. }
  1290. fsh, subpath, err := GetFSHandlerSubpathFromVpath(targetTrashedFile)
  1291. if err != nil {
  1292. utils.SendErrorResponse(w, err.Error())
  1293. return
  1294. }
  1295. fshAbs := fsh.FileSystemAbstraction
  1296. //Translate it to realpath
  1297. realpath, _ := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1298. if !fshAbs.FileExists(realpath) {
  1299. utils.SendErrorResponse(w, "File not exists")
  1300. return
  1301. }
  1302. //Check if this is really a trashed file
  1303. if filepath.Base(filepath.Dir(realpath)) != ".trash" {
  1304. utils.SendErrorResponse(w, "File not in trashbin")
  1305. return
  1306. }
  1307. //OK to proceed.
  1308. originalFilename := strings.TrimSuffix(filepath.Base(realpath), filepath.Ext(filepath.Base(realpath)))
  1309. restoreFolderRoot := filepath.Dir(filepath.Dir(filepath.Dir(realpath)))
  1310. targetPath := filepath.ToSlash(filepath.Join(restoreFolderRoot, originalFilename))
  1311. //systemWideLogger.PrintAndLog("File System", (targetPath)
  1312. fshAbs.Rename(realpath, targetPath)
  1313. //Check if the parent dir has no more fileds. If yes, remove it
  1314. filescounter, _ := fshAbs.Glob(filepath.Dir(realpath) + "/*")
  1315. if len(filescounter) == 0 {
  1316. fshAbs.Remove(filepath.Dir(realpath))
  1317. }
  1318. utils.SendOK(w)
  1319. }
  1320. // Clear all trashed file in the system
  1321. func system_fs_clearTrashBin(w http.ResponseWriter, r *http.Request) {
  1322. u, err := userHandler.GetUserInfoFromRequest(w, r)
  1323. if err != nil {
  1324. utils.SendErrorResponse(w, "User not logged in")
  1325. return
  1326. }
  1327. fileList, fshs, err := system_fs_listTrash(u.Username)
  1328. if err != nil {
  1329. utils.SendErrorResponse(w, "Unable to clear trash: "+err.Error())
  1330. return
  1331. }
  1332. //Get list success. Remove each of them.
  1333. for c, file := range fileList {
  1334. fileVpath, _ := fshs[c].FileSystemAbstraction.RealPathToVirtualPath(file, u.Username)
  1335. isOwner := u.IsOwnerOfFile(fshs[c], fileVpath)
  1336. if isOwner {
  1337. //This user own this system. Remove this file from his quota
  1338. u.RemoveOwnershipFromFile(fshs[c], fileVpath)
  1339. }
  1340. fshAbs := fshs[c].FileSystemAbstraction
  1341. fshAbs.RemoveAll(file)
  1342. //Check if its parent directory have no files. If yes, remove the dir itself as well.
  1343. filesInThisTrashBin, _ := fshAbs.Glob(filepath.Dir(file) + "/*")
  1344. if len(filesInThisTrashBin) == 0 {
  1345. fshAbs.Remove(filepath.Dir(file))
  1346. }
  1347. }
  1348. utils.SendOK(w)
  1349. }
  1350. // Get all trash in a string list
  1351. func system_fs_listTrash(username string) ([]string, []*filesystem.FileSystemHandler, error) {
  1352. userinfo, _ := userHandler.GetUserInfoFromUsername(username)
  1353. scanningRoots := []*filesystem.FileSystemHandler{}
  1354. //Get all roots to scan
  1355. for _, storage := range userinfo.GetAllFileSystemHandler() {
  1356. if storage.Hierarchy == "backup" {
  1357. //Skip this fsh
  1358. continue
  1359. }
  1360. scanningRoots = append(scanningRoots, storage)
  1361. }
  1362. files := []string{}
  1363. fshs := []*filesystem.FileSystemHandler{}
  1364. for _, thisFsh := range scanningRoots {
  1365. thisFshAbs := thisFsh.FileSystemAbstraction
  1366. rootPath, _ := thisFshAbs.VirtualPathToRealPath("", userinfo.Username)
  1367. err := thisFshAbs.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
  1368. oneLevelUpper := filepath.Base(filepath.Dir(path))
  1369. if oneLevelUpper == ".trash" {
  1370. //This is a trashbin dir.
  1371. files = append(files, path)
  1372. fshs = append(fshs, thisFsh)
  1373. }
  1374. return nil
  1375. })
  1376. if err != nil {
  1377. continue
  1378. }
  1379. }
  1380. return files, fshs, nil
  1381. }
  1382. /*
  1383. Handle new file or folder functions
  1384. Required information
  1385. @type {folder / file}
  1386. @ext {any that is listed in the template folder}
  1387. if no paramter is passed in, default listing all the supported template file
  1388. */
  1389. func system_fs_handleNewObjects(w http.ResponseWriter, r *http.Request) {
  1390. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1391. if err != nil {
  1392. utils.SendErrorResponse(w, "User not logged in")
  1393. return
  1394. }
  1395. //Validate the token
  1396. tokenValid := CSRFTokenManager.HandleTokenValidation(w, r)
  1397. if !tokenValid {
  1398. http.Error(w, "Invalid CSRF token", http.StatusUnauthorized)
  1399. return
  1400. }
  1401. fileType, _ := utils.PostPara(r, "type") //File creation type, {file, folder}
  1402. vsrc, _ := utils.PostPara(r, "src") //Virtual file source folder, do not include filename
  1403. filename, _ := utils.PostPara(r, "filename") //Filename for the new file
  1404. if fileType == "" && filename == "" {
  1405. //List all the supported new filetype
  1406. if !filesystem.FileExists("system/newitem/") {
  1407. os.MkdirAll("system/newitem/", 0755)
  1408. }
  1409. type newItemObject struct {
  1410. Desc string
  1411. Ext string
  1412. }
  1413. var newItemList []newItemObject
  1414. newItemTemplate, _ := filepath.Glob("system/newitem/*")
  1415. for _, file := range newItemTemplate {
  1416. thisItem := new(newItemObject)
  1417. thisItem.Desc = strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))
  1418. thisItem.Ext = filepath.Ext(file)[1:]
  1419. newItemList = append(newItemList, *thisItem)
  1420. }
  1421. jsonString, err := json.Marshal(newItemList)
  1422. if err != nil {
  1423. systemWideLogger.PrintAndLog("File System", "Unable to parse JSON string for new item list", err)
  1424. utils.SendErrorResponse(w, "Unable to parse new item list. See server log for more information.")
  1425. return
  1426. }
  1427. utils.SendJSONResponse(w, string(jsonString))
  1428. return
  1429. } else if fileType != "" && filename != "" {
  1430. if vsrc == "" {
  1431. utils.SendErrorResponse(w, "Missing paramter: 'src'")
  1432. return
  1433. }
  1434. fsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrc)
  1435. if err != nil {
  1436. utils.SendErrorResponse(w, err.Error())
  1437. return
  1438. }
  1439. fshAbs := fsh.FileSystemAbstraction
  1440. //Translate the path to realpath
  1441. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1442. if err != nil {
  1443. utils.SendErrorResponse(w, "Invalid path given")
  1444. return
  1445. }
  1446. //Check if directory is readonly
  1447. accmode := userinfo.GetPathAccessPermission(vsrc)
  1448. if accmode == arozfs.FsReadOnly {
  1449. utils.SendErrorResponse(w, "This directory is Read Only")
  1450. return
  1451. } else if accmode == arozfs.FsDenied {
  1452. utils.SendErrorResponse(w, "Access Denied")
  1453. return
  1454. }
  1455. //Check if the filename contains web-unsafe characters
  1456. if !utils.FilenameIsWebSafe(filename) {
  1457. utils.SendErrorResponse(w, "Filename contains illegal characters")
  1458. return
  1459. }
  1460. //Check if the file already exists. If yes, fix its filename.
  1461. newfilePath := filepath.ToSlash(filepath.Join(rpath, filename))
  1462. switch fileType {
  1463. case "file":
  1464. for fshAbs.FileExists(newfilePath) {
  1465. utils.SendErrorResponse(w, "Given filename already exists")
  1466. return
  1467. }
  1468. ext := filepath.Ext(filename)
  1469. defaultFileCotent := []byte("")
  1470. if ext != "" {
  1471. templateFile, _ := fshAbs.Glob("system/newitem/*" + ext)
  1472. if len(templateFile) > 0 {
  1473. //Copy file from templateFile[0] to current dir with the given name
  1474. input, _ := os.ReadFile(templateFile[0])
  1475. defaultFileCotent = input
  1476. }
  1477. }
  1478. err = fshAbs.WriteFile(newfilePath, defaultFileCotent, 0775)
  1479. if err != nil {
  1480. systemWideLogger.PrintAndLog("File System", "Unable to create new file: "+err.Error(), err)
  1481. utils.SendErrorResponse(w, err.Error())
  1482. return
  1483. }
  1484. case "folder":
  1485. if fshAbs.FileExists(newfilePath) {
  1486. utils.SendErrorResponse(w, "Given folder already exists")
  1487. return
  1488. }
  1489. //Create the folder at target location
  1490. err := fshAbs.Mkdir(newfilePath, 0755)
  1491. if err != nil {
  1492. utils.SendErrorResponse(w, err.Error())
  1493. return
  1494. }
  1495. }
  1496. utils.SendOK(w)
  1497. } else {
  1498. utils.SendErrorResponse(w, "Missing paramter(s).")
  1499. return
  1500. }
  1501. }
  1502. /*
  1503. Handle file operations via WebSocket
  1504. This handler only handle zip, unzip, copy and move. Not other operations.
  1505. For other operations, please use the legacy handleOpr endpoint
  1506. The actual operation logic is shared with the asynchronous (background)
  1507. file operation endpoint, see runFileOperationTask below.
  1508. */
  1509. func system_fs_handleWebSocketOpr(w http.ResponseWriter, r *http.Request) {
  1510. //Get and check user permission
  1511. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1512. if err != nil {
  1513. utils.SendErrorResponse(w, "User not logged in")
  1514. return
  1515. }
  1516. operation, _ := utils.GetPara(r, "opr") //Accept copy and move
  1517. vsrcFiles, _ := utils.GetPara(r, "src")
  1518. vdestFile, _ := utils.GetPara(r, "dest")
  1519. existsOpr, _ := utils.GetPara(r, "existsresp")
  1520. sourceFiles, vdestFile, err := parseFileOperationRequest(operation, vsrcFiles, vdestFile)
  1521. if err != nil {
  1522. utils.SendErrorResponse(w, err.Error())
  1523. return
  1524. }
  1525. //Permission checking
  1526. if !userinfo.CanWrite(vdestFile) {
  1527. systemWideLogger.PrintAndLog("File System", "Access denied for "+userinfo.Username+" try to access "+vdestFile, nil)
  1528. w.WriteHeader(http.StatusForbidden)
  1529. w.Write([]byte("403 - Access Denied"))
  1530. return
  1531. }
  1532. //Upgrade to websocket
  1533. var upgrader = websocket.Upgrader{}
  1534. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  1535. c, err := upgrader.Upgrade(w, r, nil)
  1536. if err != nil {
  1537. w.WriteHeader(http.StatusInternalServerError)
  1538. w.Write([]byte("500 - " + err.Error()))
  1539. systemWideLogger.PrintAndLog("System", fmt.Sprint("Websocket Upgrade Error:", err.Error()), nil)
  1540. return
  1541. }
  1542. //Create the file operation task and remember it
  1543. task := NewOngoingFileOperation(userinfo, operation, sourceFiles, vdestFile)
  1544. //Send over the oprId for this file operation for tracking
  1545. time.Sleep(300 * time.Millisecond)
  1546. c.WriteMessage(1, []byte("{\"oprid\":\""+task.ID+"\"}"))
  1547. //Run the operation on this request goroutine and stream the progress back
  1548. runFileOperationTask(task, userinfo, sourceFiles, vdestFile, existsOpr, func(update fileOprProgressUpdate) {
  1549. js, _ := json.Marshal(update)
  1550. c.WriteMessage(1, js)
  1551. })
  1552. //This endpoint do not keep the finished record. Remove it right away.
  1553. wsConnectionStore.Delete(task.ID)
  1554. //Close WebSocket connection after finished
  1555. time.Sleep(1 * time.Second)
  1556. c.WriteControl(8, []byte{}, time.Now().Add(time.Second))
  1557. c.Close()
  1558. }
  1559. /*
  1560. Handle file operations as a background task
  1561. Unlike the WebSocket endpoint above, this endpoint returns the operation id
  1562. right away and let the operation run in the background. The caller can then
  1563. watch the progress of all of its operations through a single status
  1564. WebSocket, see system_fs_handleFileOprStatusWebSocket below.
  1565. */
  1566. func system_fs_handleAsyncOpr(w http.ResponseWriter, r *http.Request) {
  1567. //Get and check user permission
  1568. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1569. if err != nil {
  1570. utils.SendErrorResponse(w, "User not logged in")
  1571. return
  1572. }
  1573. //Validate the token
  1574. tokenValid := CSRFTokenManager.HandleTokenValidation(w, r)
  1575. if !tokenValid {
  1576. http.Error(w, "Invalid CSRF token", http.StatusUnauthorized)
  1577. return
  1578. }
  1579. operation, _ := utils.PostPara(r, "opr")
  1580. vsrcFiles, _ := utils.PostPara(r, "src")
  1581. vdestFile, _ := utils.PostPara(r, "dest")
  1582. existsOpr, _ := utils.PostPara(r, "existsresp")
  1583. sourceFiles, vdestFile, err := parseFileOperationRequest(operation, vsrcFiles, vdestFile)
  1584. if err != nil {
  1585. utils.SendErrorResponse(w, err.Error())
  1586. return
  1587. }
  1588. //Permission checking
  1589. if !userinfo.CanWrite(vdestFile) {
  1590. systemWideLogger.PrintAndLog("File System", "Access denied for "+userinfo.Username+" try to access "+vdestFile, nil)
  1591. utils.SendErrorResponse(w, "Access Denied")
  1592. return
  1593. }
  1594. //Create the task record and start it in the background
  1595. task := NewOngoingFileOperation(userinfo, operation, sourceFiles, vdestFile)
  1596. go runFileOperationTask(task, userinfo, sourceFiles, vdestFile, existsOpr, nil)
  1597. js, _ := json.Marshal(map[string]string{"oprid": task.ID})
  1598. utils.SendJSONResponse(w, string(js))
  1599. }
  1600. /*
  1601. Stream the status of all the file operations of the requesting user
  1602. One connection is enough for all the file operations of a user. The client
  1603. side file operation dialog opens this once and renders every task it reports.
  1604. Control commands can also be sent over this socket in the following format
  1605. {"cmd":"pause | continue | cancel | remove", "oprid":"<operation_id>"} or
  1606. {"cmd":"clear"} for removing all the finished records of this user.
  1607. */
  1608. func system_fs_handleFileOprStatusWebSocket(w http.ResponseWriter, r *http.Request) {
  1609. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1610. if err != nil {
  1611. utils.SendErrorResponse(w, "User not logged in")
  1612. return
  1613. }
  1614. var upgrader = websocket.Upgrader{}
  1615. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  1616. c, err := upgrader.Upgrade(w, r, nil)
  1617. if err != nil {
  1618. w.WriteHeader(http.StatusInternalServerError)
  1619. w.Write([]byte("500 - " + err.Error()))
  1620. systemWideLogger.PrintAndLog("System", fmt.Sprint("Websocket Upgrade Error:", err.Error()), nil)
  1621. return
  1622. }
  1623. defer c.Close()
  1624. //Listen for control commands on this socket until it is closed by the client
  1625. clientGone := make(chan bool, 1)
  1626. go func() {
  1627. type controlCommand struct {
  1628. Cmd string
  1629. Oprid string
  1630. }
  1631. for {
  1632. _, message, err := c.ReadMessage()
  1633. if err != nil {
  1634. clientGone <- true
  1635. return
  1636. }
  1637. cmd := controlCommand{}
  1638. if json.Unmarshal(message, &cmd) != nil {
  1639. continue
  1640. }
  1641. ApplyFileOperationControl(userinfo.Username, cmd.Cmd, cmd.Oprid)
  1642. }
  1643. }()
  1644. //Push the full task list of this user on every tick
  1645. ticker := time.NewTicker(500 * time.Millisecond)
  1646. defer ticker.Stop()
  1647. for {
  1648. js, err := MarshalFileOperationForUser(userinfo.Username, true)
  1649. if err == nil {
  1650. if c.WriteMessage(websocket.TextMessage, js) != nil {
  1651. return
  1652. }
  1653. }
  1654. select {
  1655. case <-clientGone:
  1656. return
  1657. case <-ticker.C:
  1658. }
  1659. }
  1660. }
  1661. /*
  1662. File operation task runner
  1663. These are the shared internals used by both the legacy WebSocket endpoint
  1664. and the background (async) file operation endpoint above.
  1665. */
  1666. // fileOprProgressUpdate is the progress payload of the legacy WebSocket file operation endpoint
  1667. type fileOprProgressUpdate struct {
  1668. LatestFile string
  1669. Progress int
  1670. StatusFlag int
  1671. Error string
  1672. }
  1673. // parseFileOperationRequest validates a file operation request and returns the
  1674. // decoded source file list and the decoded destination virtual path
  1675. func parseFileOperationRequest(operation string, vsrcFiles string, vdestFile string) ([]string, string, error) {
  1676. //Check if opr is supported
  1677. if operation != "move" && operation != "copy" && operation != "zip" && operation != "unzip" {
  1678. systemWideLogger.PrintAndLog("File System", "This file operation is not supported on the file operation endpoint. Received: "+operation, errors.New("operation not supported"))
  1679. return nil, "", errors.New("operation not supported on this endpoint")
  1680. }
  1681. //Decode the source file list
  1682. var sourceFiles []string
  1683. decodedSourceFiles, _ := url.QueryUnescape(vsrcFiles)
  1684. err := json.Unmarshal([]byte(decodedSourceFiles), &sourceFiles)
  1685. if err != nil {
  1686. systemWideLogger.PrintAndLog("File System", "File operation source file JSON parse error", err)
  1687. return nil, "", errors.New("Source file JSON parse error.")
  1688. }
  1689. if len(sourceFiles) == 0 {
  1690. return nil, "", errors.New("No source file given")
  1691. }
  1692. //Bugged char filtering
  1693. tmp := []string{}
  1694. for _, src := range sourceFiles {
  1695. tmp = append(tmp, strings.ReplaceAll(src, "{{plug_sign}}", "+"))
  1696. }
  1697. sourceFiles = tmp
  1698. vdestFile = strings.ReplaceAll(vdestFile, "{{plug_sign}}", "+")
  1699. //Decode the target position
  1700. escapedVdest, _ := url.QueryUnescape(vdestFile)
  1701. vdestFile = escapedVdest
  1702. if vdestFile == "" {
  1703. return nil, "", errors.New("Undefined dest location")
  1704. }
  1705. //Make sure the destination is resolvable before doing anything else
  1706. _, _, err = GetFSHandlerSubpathFromVpath(vdestFile)
  1707. if err != nil {
  1708. return nil, "", err
  1709. }
  1710. return sourceFiles, vdestFile, nil
  1711. }
  1712. // runFileOperationTask executes the file operation described by the given task record.
  1713. // onUpdate is optional and, when given, is called on every progress update.
  1714. func runFileOperationTask(task *fileOperationTask, userinfo *user.User, sourceFiles []string, vdestFile string, existsOpr string, onUpdate func(fileOprProgressUpdate)) {
  1715. if existsOpr == "" {
  1716. existsOpr = "keep"
  1717. }
  1718. oprId := task.ID
  1719. operation := task.Operation
  1720. pushUpdate := func(update fileOprProgressUpdate) {
  1721. if onUpdate != nil {
  1722. onUpdate(update)
  1723. }
  1724. }
  1725. failTask := func(filename string, errmsg string) {
  1726. SetFileOperationTaskEnded(oprId, filesystem.FsOpr_Error, errmsg)
  1727. pushUpdate(fileOprProgressUpdate{
  1728. LatestFile: filename,
  1729. Progress: -1,
  1730. Error: errmsg,
  1731. StatusFlag: filesystem.FsOpr_Error,
  1732. })
  1733. }
  1734. //Resolve the destination file system handler
  1735. destFsh, subpath, err := GetFSHandlerSubpathFromVpath(vdestFile)
  1736. if err != nil {
  1737. failTask(filepath.Base(vdestFile), err.Error())
  1738. return
  1739. }
  1740. destFshAbs := destFsh.FileSystemAbstraction
  1741. rdestFile, _ := destFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1742. if !userinfo.CanWrite(vdestFile) {
  1743. failTask(filepath.Base(vdestFile), "Access Denied: No Write Permission")
  1744. return
  1745. }
  1746. if operation == "zip" {
  1747. //Zip files
  1748. outputFilename := filepath.Join(rdestFile, strings.ReplaceAll(filepath.Base(filepath.Dir(sourceFiles[0])+".zip"), ":", ""))
  1749. if len(sourceFiles) == 1 {
  1750. //Use the basename of the source file as zip file name
  1751. outputFilename = filepath.Join(rdestFile, filepath.Base(sourceFiles[0])) + ".zip"
  1752. }
  1753. //Translate source Files into real paths
  1754. realSourceFiles := []string{}
  1755. sourceFileFsh := []*filesystem.FileSystemHandler{}
  1756. for _, vsrcs := range sourceFiles {
  1757. thisSrcFsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrcs)
  1758. if err != nil {
  1759. failTask(filepath.Base(vsrcs), "Source file not exists")
  1760. return
  1761. }
  1762. rsrc, err := thisSrcFsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
  1763. if err != nil {
  1764. failTask(filepath.Base(rsrc), "Source file not exists")
  1765. return
  1766. }
  1767. realSourceFiles = append(realSourceFiles, rsrc)
  1768. sourceFileFsh = append(sourceFileFsh, thisSrcFsh)
  1769. }
  1770. zipDestPath := outputFilename
  1771. zipDestFsh := destFsh
  1772. if destFsh.RequireBuffer {
  1773. zipDestPath = getFsBufferFilepath(outputFilename, false)
  1774. zipDestFsh = nil
  1775. }
  1776. //Create the zip file
  1777. err = filesystem.ArozZipFileWithProgress(sourceFileFsh, realSourceFiles, zipDestFsh, zipDestPath, false, func(currentFilename string, _ int, _ int, progress float64) int {
  1778. sig, _ := UpdateOngoingFileOperation(oprId, currentFilename, math.Ceil(progress))
  1779. pushUpdate(fileOprProgressUpdate{
  1780. LatestFile: currentFilename,
  1781. Progress: int(math.Ceil(progress)),
  1782. Error: "",
  1783. StatusFlag: sig,
  1784. })
  1785. return sig
  1786. })
  1787. if err != nil {
  1788. systemWideLogger.PrintAndLog("File System", "Zipping request failed: "+err.Error(), err)
  1789. }
  1790. if destFsh.RequireBuffer {
  1791. //Move the buffer result to remote
  1792. f, _ := os.Open(zipDestPath)
  1793. err = destFshAbs.WriteStream(outputFilename, f, 0775)
  1794. if err != nil {
  1795. systemWideLogger.PrintAndLog("File System", "Zip write to remote file system with driver"+destFsh.Filesystem+" failed", err)
  1796. }
  1797. f.Close()
  1798. //Clear local buffers
  1799. os.Remove(zipDestPath)
  1800. cleanFsBufferFileFromList(realSourceFiles)
  1801. }
  1802. } else if operation == "unzip" {
  1803. //Create the destination folder
  1804. destFshAbs.MkdirAll(rdestFile, 0755)
  1805. //Convert the src files into realpaths
  1806. realSourceFiles := []string{}
  1807. for _, vsrcs := range sourceFiles {
  1808. thisSrcFsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrcs)
  1809. if err != nil {
  1810. failTask(filepath.Base(vsrcs), "Source file not exists")
  1811. return
  1812. }
  1813. thisSrcFshAbs := thisSrcFsh.FileSystemAbstraction
  1814. rsrc, err := thisSrcFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1815. if err != nil {
  1816. failTask(filepath.Base(rsrc), "Source file not exists")
  1817. return
  1818. }
  1819. if thisSrcFsh.RequireBuffer {
  1820. localBufferFilepath, err := bufferRemoteFileToLocal(thisSrcFsh, rsrc, false)
  1821. if err != nil {
  1822. failTask(filepath.Base(rsrc), "Failed to buffer file to local disk")
  1823. return
  1824. }
  1825. realSourceFiles = append(realSourceFiles, localBufferFilepath)
  1826. } else {
  1827. realSourceFiles = append(realSourceFiles, rsrc)
  1828. }
  1829. }
  1830. unzipDest := rdestFile
  1831. if destFsh.RequireBuffer {
  1832. unzipDest = getFsBufferFilepath(rdestFile, true)
  1833. }
  1834. //Unzip the files
  1835. filesystem.ArozUnzipFileWithProgress(realSourceFiles, unzipDest, func(currentFile string, filecount int, totalfile int, progress float64) int {
  1836. //Generate the status update struct
  1837. sig, _ := UpdateOngoingFileOperation(oprId, filepath.Base(currentFile), math.Ceil(progress))
  1838. pushUpdate(fileOprProgressUpdate{
  1839. LatestFile: filepath.Base(currentFile),
  1840. Progress: int(math.Ceil(progress)),
  1841. Error: "",
  1842. StatusFlag: sig,
  1843. })
  1844. return sig
  1845. })
  1846. if destFsh.RequireBuffer {
  1847. //Push the unzip results back to remote fs
  1848. filepath.Walk(unzipDest, func(path string, info os.FileInfo, err error) error {
  1849. path = filepath.ToSlash(path)
  1850. relpath := strings.TrimPrefix(path, filepath.ToSlash(unzipDest))
  1851. if info.IsDir() {
  1852. destFshAbs.MkdirAll(filepath.Join(rdestFile, relpath), 0775)
  1853. } else {
  1854. f, _ := os.Open(path)
  1855. destFshAbs.WriteStream(filepath.Join(rdestFile, relpath), f, 0775)
  1856. f.Close()
  1857. }
  1858. return nil
  1859. })
  1860. cleanFsBufferFileFromList([]string{unzipDest})
  1861. }
  1862. cleanFsBufferFileFromList(realSourceFiles)
  1863. } else {
  1864. //Other operations that allow multiple source files to handle one by one
  1865. for i := 0; i < len(sourceFiles); i++ {
  1866. vsrcFile := sourceFiles[i]
  1867. thisSrcFsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrcFile)
  1868. if err != nil {
  1869. MarkFileOperationSubtaskEnded(oprId, i, "Source file not exists")
  1870. failTask(filepath.Base(vsrcFile), "Source file not exists")
  1871. return
  1872. }
  1873. thisSrcFshAbs := thisSrcFsh.FileSystemAbstraction
  1874. rsrcFile, _ := thisSrcFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1875. if !thisSrcFshAbs.FileExists(rsrcFile) {
  1876. //This source file not exists. Report Error and Stop
  1877. MarkFileOperationSubtaskEnded(oprId, i, "Source file not exists")
  1878. failTask(filepath.Base(rsrcFile), "Source file not exists")
  1879. return
  1880. }
  1881. //Progress handler shared by the move and copy operations. The overall
  1882. //progress is worked out from the bytes of every source file, so a
  1883. //small file no longer counts for as much of the bar as a large one.
  1884. subtaskProgressHandler := func(currentFile string, bytesDone int64, bytesTotal int64) int {
  1885. sig, overallProgress, _ := UpdateOngoingFileOperationSubtask(oprId, i, filepath.Base(currentFile), bytesDone, bytesTotal)
  1886. pushUpdate(fileOprProgressUpdate{
  1887. LatestFile: filepath.Base(currentFile),
  1888. Progress: int(math.Ceil(overallProgress)),
  1889. Error: "",
  1890. StatusFlag: sig,
  1891. })
  1892. return sig
  1893. }
  1894. if operation == "move" {
  1895. err := filesystem.FileMoveWithProgress(thisSrcFsh, rsrcFile, destFsh, rdestFile, existsOpr, true, subtaskProgressHandler)
  1896. //Handle move starting error
  1897. if err != nil {
  1898. MarkFileOperationSubtaskEnded(oprId, i, err.Error())
  1899. failTask(filepath.Base(rsrcFile), err.Error())
  1900. return
  1901. }
  1902. //Remove the cache for the original file
  1903. metadata.RemoveCache(thisSrcFsh, rsrcFile)
  1904. } else if operation == "copy" {
  1905. err := filesystem.FileCopyWithProgress(thisSrcFsh, rsrcFile, destFsh, rdestFile, existsOpr, subtaskProgressHandler)
  1906. //Handle Copy starting error
  1907. if err != nil {
  1908. MarkFileOperationSubtaskEnded(oprId, i, err.Error())
  1909. failTask(filepath.Base(rsrcFile), err.Error())
  1910. return
  1911. }
  1912. }
  1913. //This source file is done. Mark it as completed
  1914. MarkFileOperationSubtaskEnded(oprId, i, "")
  1915. }
  1916. }
  1917. //Check if the operation was cancelled by the user half way through
  1918. endingSignal := filesystem.FsOpr_Continue
  1919. if t, err := GetOngoingFileOperationByOprID(oprId); err == nil {
  1920. fileOprTaskLock.RLock()
  1921. if t.FileOperationSignal == filesystem.FsOpr_Cancel {
  1922. endingSignal = filesystem.FsOpr_Cancel
  1923. }
  1924. fileOprTaskLock.RUnlock()
  1925. }
  1926. SetFileOperationTaskEnded(oprId, endingSignal, "")
  1927. }
  1928. /*
  1929. Handle file operations
  1930. Support {move, copy, delete, recycle, rename}
  1931. */
  1932. //Handle file operations.
  1933. func system_fs_handleOpr(w http.ResponseWriter, r *http.Request) {
  1934. //Check if user logged in
  1935. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  1936. if err != nil {
  1937. utils.SendErrorResponse(w, "User not logged in")
  1938. return
  1939. }
  1940. //Validate the token
  1941. tokenValid := CSRFTokenManager.HandleTokenValidation(w, r)
  1942. if !tokenValid {
  1943. http.Error(w, "Invalid CSRF token", http.StatusUnauthorized)
  1944. return
  1945. }
  1946. operation, _ := utils.PostPara(r, "opr")
  1947. vsrcFiles, _ := utils.PostPara(r, "src")
  1948. vdestFile, _ := utils.PostPara(r, "dest")
  1949. vnfilenames, _ := utils.PostPara(r, "new") //Only use when rename or create new file / folder
  1950. //Check if operation valid.
  1951. if operation == "" {
  1952. //Undefined operations.
  1953. utils.SendErrorResponse(w, "Undefined operations paramter: Missing 'opr' in request header.")
  1954. return
  1955. }
  1956. //As the user can pass in multiple source files at the same time, parse sourceFiles from json string
  1957. var sourceFiles []string
  1958. //This line is required in order to allow passing of special charaters
  1959. decodedSourceFiles := system_fs_specialURIDecode(vsrcFiles)
  1960. err = json.Unmarshal([]byte(decodedSourceFiles), &sourceFiles)
  1961. if err != nil {
  1962. utils.SendErrorResponse(w, "Source file JSON parse error.")
  1963. return
  1964. }
  1965. //Check if new filenames are also valid. If yes, translate it into string array
  1966. var newFilenames []string
  1967. if vnfilenames != "" {
  1968. vnfilenames, _ := url.QueryUnescape(vnfilenames)
  1969. err = json.Unmarshal([]byte(vnfilenames), &newFilenames)
  1970. if err != nil {
  1971. utils.SendErrorResponse(w, "Unable to parse JSON for new filenames")
  1972. return
  1973. }
  1974. }
  1975. if operation == "zip" {
  1976. //Zip operation. Parse the real filepath list
  1977. rsrcFiles := []string{}
  1978. srcFshs := []*filesystem.FileSystemHandler{}
  1979. destFsh, subpath, err := GetFSHandlerSubpathFromVpath(vdestFile)
  1980. if err != nil {
  1981. utils.SendErrorResponse(w, "Unable to resolve zip destination path")
  1982. return
  1983. }
  1984. destFshAbs := destFsh.FileSystemAbstraction
  1985. rdestFile, _ := destFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  1986. for _, vsrcFile := range sourceFiles {
  1987. vsrcFsh, vsrcSubpath, err := GetFSHandlerSubpathFromVpath(vsrcFile)
  1988. if err != nil {
  1989. continue
  1990. }
  1991. rsrcFile, _ := vsrcFsh.FileSystemAbstraction.VirtualPathToRealPath(vsrcSubpath, userinfo.Username)
  1992. if vsrcFsh.FileSystemAbstraction.FileExists(rsrcFile) {
  1993. //Push directly its local path to list
  1994. rsrcFiles = append(rsrcFiles, rsrcFile)
  1995. srcFshs = append(srcFshs, vsrcFsh)
  1996. }
  1997. }
  1998. zipFilename := rdestFile
  1999. if destFshAbs.IsDir(rdestFile) {
  2000. //Append the filename to it
  2001. if len(rsrcFiles) == 1 {
  2002. zipFilename = filepath.Join(rdestFile, strings.TrimSuffix(filepath.Base(rsrcFiles[0]), filepath.Ext(filepath.Base(rsrcFiles[0])))+".zip")
  2003. } else if len(rsrcFiles) > 1 {
  2004. zipFilename = filepath.Join(rdestFile, filepath.Base(filepath.Dir(rsrcFiles[0]))+".zip")
  2005. }
  2006. }
  2007. //Create a buffer if destination fsh request buffer
  2008. zipFileTargetLocation := zipFilename
  2009. zipDestFsh := destFsh
  2010. if destFsh.RequireBuffer {
  2011. zipFileTargetLocation = getFsBufferFilepath(zipFilename, false)
  2012. zipDestFsh = nil
  2013. }
  2014. //Create a zip file at target location
  2015. err = filesystem.ArozZipFile(srcFshs, rsrcFiles, zipDestFsh, zipFileTargetLocation, false)
  2016. if err != nil {
  2017. os.Remove(zipFileTargetLocation)
  2018. utils.SendErrorResponse(w, err.Error())
  2019. return
  2020. }
  2021. //Write it to final destination from buffer
  2022. if destFsh.RequireBuffer {
  2023. //Upload the finalized zip file
  2024. f, _ := destFshAbs.Open(zipFileTargetLocation)
  2025. destFshAbs.WriteStream(zipFilename, f, 0775)
  2026. f.Close()
  2027. //Remove all buff files
  2028. os.Remove(zipFileTargetLocation)
  2029. cleanFsBufferFileFromList(rsrcFiles)
  2030. }
  2031. } else {
  2032. //For operations that is handled file by file
  2033. for i, vsrcFile := range sourceFiles {
  2034. //Convert the virtual path to realpath on disk
  2035. srcFsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrcFile)
  2036. if err != nil {
  2037. continue
  2038. }
  2039. srcFshAbs := srcFsh.FileSystemAbstraction
  2040. rsrcFile, _ := srcFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  2041. destFsh, destSubpath, err := GetFSHandlerSubpathFromVpath(vdestFile)
  2042. var destFshAbs filesystem.FileSystemAbstraction = nil
  2043. var rdestFile string = ""
  2044. if err == nil {
  2045. destFshAbs = destFsh.FileSystemAbstraction
  2046. rdestFile, _ = destFshAbs.VirtualPathToRealPath(destSubpath, userinfo.Username)
  2047. }
  2048. //Check if the source file exists
  2049. if operation == "rename" {
  2050. //Check if the usage is correct.
  2051. if vdestFile != "" {
  2052. utils.SendErrorResponse(w, "Rename only accept 'src' and 'new'. Please use move if you want to move a file.")
  2053. return
  2054. }
  2055. //Check if new name paramter is passed in.
  2056. if len(newFilenames) == 0 {
  2057. utils.SendErrorResponse(w, "Missing paramter (JSON string): 'new'")
  2058. return
  2059. }
  2060. //Check if the source filenames and new filenanmes match
  2061. if len(newFilenames) != len(sourceFiles) {
  2062. utils.SendErrorResponse(w, "New filenames do not match with source filename's length.")
  2063. return
  2064. }
  2065. //Check if the target dir is not readonly
  2066. accmode := userinfo.GetPathAccessPermission(string(vsrcFile))
  2067. switch accmode {
  2068. case arozfs.FsReadOnly:
  2069. utils.SendErrorResponse(w, "This directory is Read Only")
  2070. return
  2071. case arozfs.FsDenied:
  2072. utils.SendErrorResponse(w, "Access Denied")
  2073. return
  2074. }
  2075. thisFilename := filepath.Base(newFilenames[i])
  2076. //Check if the new filename contains web-unsafe characters
  2077. if !utils.FilenameIsWebSafe(thisFilename) {
  2078. utils.SendErrorResponse(w, "Filename contains illegal characters")
  2079. return
  2080. }
  2081. //Check if the name already exists. If yes, return false
  2082. if srcFshAbs.FileExists(filepath.Join(filepath.Dir(rsrcFile), thisFilename)) {
  2083. utils.SendErrorResponse(w, "File already exists")
  2084. return
  2085. }
  2086. //Everything is ok. Rename the file.
  2087. targetNewName := filepath.Join(filepath.Dir(rsrcFile), thisFilename)
  2088. err = srcFshAbs.Rename(rsrcFile, targetNewName)
  2089. if err != nil {
  2090. systemWideLogger.PrintAndLog("File System", "File rename failed", err)
  2091. utils.SendErrorResponse(w, err.Error())
  2092. return
  2093. }
  2094. //Remove the cache for the original file
  2095. metadata.RemoveCache(srcFsh, rsrcFile)
  2096. } else if operation == "move" {
  2097. //File move operation. Check if the source file / dir and target directory exists
  2098. /*
  2099. Example usage from file explorer
  2100. $.ajax({
  2101. type: 'POST',
  2102. url: `/system/file_system/fileOpr`,
  2103. data: {opr: "move" ,src: JSON.stringify(fileList), dest: targetDir},
  2104. success: function(data){
  2105. if (data.error !== undefined){
  2106. msgbox("remove",data.error);
  2107. }else{
  2108. //OK, do something
  2109. }
  2110. }
  2111. });
  2112. */
  2113. if !srcFshAbs.FileExists(rsrcFile) {
  2114. utils.SendErrorResponse(w, "Source file not exists")
  2115. return
  2116. }
  2117. //Check if the source file is read only.
  2118. accmode := userinfo.GetPathAccessPermission(string(vsrcFile))
  2119. if accmode == arozfs.FsReadOnly {
  2120. utils.SendErrorResponse(w, "This source file is Read Only")
  2121. return
  2122. } else if accmode == arozfs.FsDenied {
  2123. utils.SendErrorResponse(w, "Access Denied")
  2124. return
  2125. }
  2126. if rdestFile == "" {
  2127. utils.SendErrorResponse(w, "Undefined dest location")
  2128. return
  2129. }
  2130. //Get exists overwrite mode
  2131. existsOpr, _ := utils.PostPara(r, "existsresp")
  2132. //Check if use fast move instead
  2133. //Check if the source and destination folder are under the same root. If yes, use os.Rename for faster move operations
  2134. //Check if the two files are under the same user root path
  2135. //srcAbs, _ := filepath.Abs(rsrcFile)
  2136. //destAbs, _ := filepath.Abs(rdestFile)
  2137. //underSameRoot, _ := filesystem.UnderTheSameRoot(srcAbs, destAbs)
  2138. //Updates 19-10-2020: Added ownership management to file move and copy
  2139. userinfo.RemoveOwnershipFromFile(srcFsh, vsrcFile)
  2140. err = filesystem.FileMove(srcFsh, rsrcFile, destFsh, rdestFile, existsOpr, true, nil)
  2141. if err != nil {
  2142. utils.SendErrorResponse(w, err.Error())
  2143. //Restore the ownership if remove failed
  2144. userinfo.SetOwnerOfFile(srcFsh, vsrcFile)
  2145. return
  2146. }
  2147. //Set user to own the new file
  2148. newfileRpath := filepath.ToSlash(filepath.Clean(rdestFile)) + "/" + filepath.Base(rsrcFile)
  2149. newfileVpath, _ := destFsh.FileSystemAbstraction.RealPathToVirtualPath(newfileRpath, userinfo.Username)
  2150. userinfo.SetOwnerOfFile(destFsh, newfileVpath)
  2151. //Remove cache for the original file
  2152. metadata.RemoveCache(srcFsh, rsrcFile)
  2153. } else if operation == "copy" {
  2154. //Copy file. See move example and change 'opr' to 'copy'
  2155. if !srcFshAbs.FileExists(rsrcFile) {
  2156. utils.SendErrorResponse(w, "Source file not exists")
  2157. return
  2158. }
  2159. //Check if the desintation is read only.
  2160. if !userinfo.CanWrite(vdestFile) {
  2161. utils.SendErrorResponse(w, "Access Denied")
  2162. return
  2163. }
  2164. if !destFshAbs.FileExists(rdestFile) {
  2165. if destFshAbs.FileExists(filepath.Dir(rdestFile)) {
  2166. //User pass in the whole path for the folder. Report error usecase.
  2167. utils.SendErrorResponse(w, "Dest location should be an existing folder instead of the full path of the copied file")
  2168. return
  2169. }
  2170. utils.SendErrorResponse(w, "Dest folder not found")
  2171. return
  2172. }
  2173. existsOpr, _ := utils.PostPara(r, "existsresp")
  2174. //Check if the user have space for the extra file
  2175. if !userinfo.StorageQuota.HaveSpace(filesystem.GetFileSize(rdestFile)) {
  2176. utils.SendErrorResponse(w, "Storage Quota Full")
  2177. return
  2178. }
  2179. err = filesystem.FileCopy(srcFsh, rsrcFile, destFsh, rdestFile, existsOpr, nil)
  2180. if err != nil {
  2181. utils.SendErrorResponse(w, err.Error())
  2182. return
  2183. }
  2184. //Set user to own this file
  2185. newfileRpath := filepath.ToSlash(filepath.Clean(rdestFile)) + "/" + filepath.Base(rsrcFile)
  2186. newfileVpath, _ := destFsh.FileSystemAbstraction.RealPathToVirtualPath(newfileRpath, userinfo.Username)
  2187. userinfo.SetOwnerOfFile(destFsh, newfileVpath)
  2188. } else if operation == "delete" {
  2189. //Delete the file permanently
  2190. if !srcFshAbs.FileExists(rsrcFile) {
  2191. //Check if it is a non escapted file instead
  2192. utils.SendErrorResponse(w, "Source file not exists")
  2193. return
  2194. }
  2195. if !userinfo.CanWrite(vsrcFile) {
  2196. utils.SendErrorResponse(w, "Access Denied")
  2197. return
  2198. }
  2199. //Check if the user own this file
  2200. isOwner := userinfo.IsOwnerOfFile(srcFsh, vsrcFile)
  2201. if isOwner {
  2202. //This user own this system. Remove this file from his quota
  2203. userinfo.RemoveOwnershipFromFile(srcFsh, vsrcFile)
  2204. }
  2205. //Check if this file has any cached files. If yes, remove it
  2206. metadata.RemoveCache(srcFsh, rsrcFile)
  2207. //Clear the cache folder if there is no files inside
  2208. fc, err := srcFshAbs.Glob(filepath.ToSlash(filepath.Dir(rsrcFile)) + "/.metadata/.cache/*")
  2209. if len(fc) == 0 && err == nil {
  2210. srcFshAbs.Remove(filepath.ToSlash(filepath.Dir(rsrcFile)) + "/.metadata/.cache/")
  2211. }
  2212. err = srcFshAbs.RemoveAll(rsrcFile)
  2213. if err != nil {
  2214. systemWideLogger.PrintAndLog("File System", "Unable to remove file from "+srcFsh.UUID, err)
  2215. utils.SendErrorResponse(w, err.Error())
  2216. return
  2217. }
  2218. } else if operation == "recycle" {
  2219. //Put it into a subfolder named trash and allow it to to be removed later
  2220. if !srcFshAbs.FileExists(rsrcFile) {
  2221. //Check if it is a non escapted file instead
  2222. utils.SendErrorResponse(w, "Source file not exists")
  2223. return
  2224. }
  2225. //Check if the upload target is read only.
  2226. if !userinfo.CanWrite(vsrcFile) {
  2227. utils.SendErrorResponse(w, "Access Denied")
  2228. return
  2229. }
  2230. //Check if this file has any cached files. If yes, remove it
  2231. metadata.RemoveCache(srcFsh, rsrcFile)
  2232. //Clear the cache folder if there is no files inside
  2233. fc, err := srcFshAbs.Glob(filepath.ToSlash(filepath.Dir(rsrcFile)) + "/.metadata/.cache/*")
  2234. if len(fc) == 0 && err == nil {
  2235. srcFshAbs.Remove(filepath.ToSlash(filepath.Dir(rsrcFile)) + "/.metadata/.cache/")
  2236. }
  2237. /*
  2238. Trash quota
  2239. Checked before the move, not after: once the file is inside
  2240. .trash it has already left the tree the user was looking at,
  2241. and undoing that cleanly is harder than refusing up front.
  2242. The client recognises this error code and offers to empty
  2243. the bin, delete outright, or do nothing.
  2244. */
  2245. userTrashQuota := system_fs_getTrashQuotaBytes(userinfo.Username)
  2246. if userTrashQuota > 0 {
  2247. incomingSize := system_fs_getTrashEntrySize(srcFsh, rsrcFile)
  2248. if system_fs_getTrashUsage(userinfo.Username)+incomingSize > userTrashQuota {
  2249. utils.SendErrorResponse(w, "TRASH_QUOTA_EXCEEDED")
  2250. return
  2251. }
  2252. }
  2253. //Create a trash directory for this folder
  2254. trashDir := filepath.ToSlash(filepath.Dir(rsrcFile)) + "/.metadata/.trash/"
  2255. srcFshAbs.MkdirAll(trashDir, 0755)
  2256. hidden.HideFile(filepath.Dir(trashDir))
  2257. hidden.HideFile(trashDir)
  2258. err = srcFshAbs.Rename(rsrcFile, trashDir+filepath.Base(rsrcFile)+"."+utils.Int64ToString(time.Now().Unix()))
  2259. if err != nil {
  2260. if srcFsh.RequireBuffer {
  2261. utils.SendErrorResponse(w, "Incompatible File System Type: Try SHIFT + DELETE to delete file permanently")
  2262. } else {
  2263. systemWideLogger.PrintAndLog("File System", "Failed to move file to trash. See log for more info.", err)
  2264. utils.SendErrorResponse(w, "Failed to move file to trash")
  2265. }
  2266. return
  2267. }
  2268. } else if operation == "unzip" {
  2269. //Unzip the file to destination
  2270. //Check if the user can write to the target dest file
  2271. if !userinfo.CanWrite(string(vdestFile)) {
  2272. utils.SendErrorResponse(w, "Access Denied")
  2273. return
  2274. }
  2275. //Make the rdest directory if not exists
  2276. if !destFshAbs.FileExists(rdestFile) {
  2277. err = destFshAbs.MkdirAll(rdestFile, 0755)
  2278. if err != nil {
  2279. utils.SendErrorResponse(w, err.Error())
  2280. return
  2281. }
  2282. }
  2283. unzipSource := rsrcFile
  2284. unzipDest := rdestFile
  2285. if srcFsh.RequireBuffer {
  2286. localBufferedFile, _ := bufferRemoteFileToLocal(srcFsh, rsrcFile, false)
  2287. unzipSource = localBufferedFile
  2288. }
  2289. if destFsh.RequireBuffer {
  2290. localUnzipBuffer, _ := bufferRemoteFileToLocal(destFsh, rdestFile, true)
  2291. unzipDest = localUnzipBuffer
  2292. }
  2293. //OK! Unzip to destination
  2294. err := filesystem.Unzip(unzipSource, unzipDest)
  2295. if err != nil {
  2296. utils.SendErrorResponse(w, err.Error())
  2297. return
  2298. }
  2299. if srcFsh.RequireBuffer {
  2300. //Remove the local buffered file
  2301. os.Remove(unzipSource)
  2302. }
  2303. if destFsh.RequireBuffer {
  2304. //Push the buffer to target fs
  2305. filepath.Walk(unzipDest, func(path string, info os.FileInfo, err error) error {
  2306. path = filepath.ToSlash(path)
  2307. relpath := strings.TrimPrefix(path, filepath.ToSlash(unzipDest))
  2308. if info.IsDir() {
  2309. destFshAbs.MkdirAll(filepath.Join(rdestFile, relpath), 0775)
  2310. } else {
  2311. f, _ := os.Open(path)
  2312. destFshAbs.WriteStream(filepath.Join(rdestFile, relpath), f, 0775)
  2313. f.Close()
  2314. }
  2315. return nil
  2316. })
  2317. cleanFsBufferFileFromList([]string{unzipDest})
  2318. }
  2319. } else {
  2320. utils.SendErrorResponse(w, "Unknown file opeartion given")
  2321. return
  2322. }
  2323. }
  2324. }
  2325. utils.SendOK(w)
  2326. }
  2327. // Allow systems to store key value pairs in the database as preferences.
  2328. func system_fs_handleUserPreference(w http.ResponseWriter, r *http.Request) {
  2329. username, err := authAgent.GetUserName(w, r)
  2330. if err != nil {
  2331. utils.SendErrorResponse(w, "User not logged in")
  2332. return
  2333. }
  2334. key, _ := utils.GetPara(r, "key")
  2335. value, _ := utils.GetPara(r, "value")
  2336. remove, _ := utils.GetPara(r, "remove")
  2337. if key != "" && value == "" && remove == "" {
  2338. //Get mode. Read the prefernece with given key
  2339. result := ""
  2340. err := sysdb.Read("fs", "pref/"+key+"/"+username, &result)
  2341. if err != nil {
  2342. utils.SendJSONResponse(w, `{"error":"Key not found."}`)
  2343. return
  2344. }
  2345. utils.SendTextResponse(w, result)
  2346. } else if key != "" && value == "" && remove == "true" {
  2347. //Remove mode. Delete this key from sysdb
  2348. err := sysdb.Delete("fs", "pref/"+key+"/"+username)
  2349. if err != nil {
  2350. utils.SendErrorResponse(w, err.Error())
  2351. }
  2352. utils.SendOK(w)
  2353. } else if key != "" && value != "" {
  2354. //Set mode. Set the preference with given key
  2355. if len(value) > 1024*1024 { //1KB
  2356. //Size too big. Reject storage
  2357. utils.SendErrorResponse(w, "Preference value too long. Preference value can only store maximum 1KB.")
  2358. return
  2359. }
  2360. sysdb.Write("fs", "pref/"+key+"/"+username, value)
  2361. utils.SendOK(w)
  2362. }
  2363. }
  2364. func system_fs_removeUserPreferences(username string) {
  2365. entries, err := sysdb.ListTable("fs")
  2366. if err != nil {
  2367. return
  2368. }
  2369. for _, keypairs := range entries {
  2370. if strings.Contains(string(keypairs[0]), "pref/") && strings.Contains(string(keypairs[0]), "/"+username) {
  2371. //Remove this preference
  2372. sysdb.Delete("fs", string(keypairs[0]))
  2373. }
  2374. }
  2375. }
  2376. func system_fs_listDrives(w http.ResponseWriter, r *http.Request) {
  2377. if !authAgent.CheckAuth(r) {
  2378. utils.SendErrorResponse(w, "User not logged in")
  2379. return
  2380. }
  2381. userinfo, _ := userHandler.GetUserInfoFromRequest(w, r)
  2382. type driveInfo struct {
  2383. Drivepath string
  2384. DriveFreeSpace uint64
  2385. DriveTotalSpace uint64
  2386. DriveAvailSpace uint64
  2387. }
  2388. var drives []driveInfo
  2389. if runtime.GOOS == "windows" {
  2390. //Under windows
  2391. for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
  2392. f, err := os.Open(string(drive) + ":\\")
  2393. if err == nil {
  2394. thisdrive := new(driveInfo)
  2395. thisdrive.Drivepath = string(drive) + ":\\"
  2396. free, total, avail := storage.GetDriveCapacity(string(drive) + ":\\")
  2397. thisdrive.DriveFreeSpace = free
  2398. thisdrive.DriveTotalSpace = total
  2399. thisdrive.DriveAvailSpace = avail
  2400. drives = append(drives, *thisdrive)
  2401. f.Close()
  2402. }
  2403. }
  2404. } else {
  2405. //Under linux environment
  2406. //Append all the virtual directories root as root instead
  2407. storageDevices := []string{}
  2408. for _, fshandler := range userinfo.GetAllFileSystemHandler() {
  2409. storageDevices = append(storageDevices, fshandler.Path)
  2410. }
  2411. //List all storage information of each devices
  2412. for _, dev := range storageDevices {
  2413. thisdrive := new(driveInfo)
  2414. thisdrive.Drivepath = filepath.Base(dev)
  2415. free, total, avail := storage.GetDriveCapacity(string(dev))
  2416. thisdrive.DriveFreeSpace = free
  2417. thisdrive.DriveTotalSpace = total
  2418. thisdrive.DriveAvailSpace = avail
  2419. drives = append(drives, *thisdrive)
  2420. }
  2421. }
  2422. jsonString, _ := json.Marshal(drives)
  2423. utils.SendJSONResponse(w, string(jsonString))
  2424. }
  2425. func system_fs_listRoot(w http.ResponseWriter, r *http.Request) {
  2426. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2427. if err != nil {
  2428. utils.SendErrorResponse(w, err.Error())
  2429. return
  2430. }
  2431. username := userinfo.Username
  2432. userRoot, _ := utils.GetPara(r, "user")
  2433. if userRoot == "true" {
  2434. type fileObject struct {
  2435. Filename string
  2436. Filepath string
  2437. IsDir bool
  2438. }
  2439. //List the root media folders under user:/
  2440. fsh, _ := userinfo.GetFileSystemHandlerFromVirtualPath("user:/")
  2441. fshAbs := fsh.FileSystemAbstraction
  2442. filesInUserRoot := []fileObject{}
  2443. filesInRoot, _ := fshAbs.Glob(filepath.ToSlash(filepath.Clean(*root_directory)) + "/users/" + username + "/*")
  2444. for _, file := range filesInRoot {
  2445. //Check if this is a hidden file
  2446. if len(filepath.Base(file)) > 0 && filepath.Base(file)[:1] == "." {
  2447. continue
  2448. }
  2449. thisFile := new(fileObject)
  2450. thisFile.Filename = filepath.Base(file)
  2451. thisFile.Filepath, _ = fshAbs.RealPathToVirtualPath(file, userinfo.Username)
  2452. thisFile.IsDir = fshAbs.IsDir(file)
  2453. filesInUserRoot = append(filesInUserRoot, *thisFile)
  2454. }
  2455. jsonString, _ := json.Marshal(filesInUserRoot)
  2456. utils.SendJSONResponse(w, string(jsonString))
  2457. } else {
  2458. type rootObject struct {
  2459. rootID string //The vroot id
  2460. RootName string //The name of this vroot
  2461. RootPath string //The path of this vroot
  2462. BufferedFs bool //If buffer typed FS
  2463. }
  2464. roots := []*rootObject{}
  2465. for _, store := range userinfo.GetAllFileSystemHandler() {
  2466. var thisDevice = new(rootObject)
  2467. thisDevice.RootName = store.Name
  2468. thisDevice.RootPath = store.UUID + ":/"
  2469. thisDevice.rootID = store.UUID
  2470. thisDevice.BufferedFs = store.RequireBuffer
  2471. roots = append(roots, thisDevice)
  2472. }
  2473. jsonString, _ := json.Marshal(roots)
  2474. utils.SendJSONResponse(w, string(jsonString))
  2475. }
  2476. }
  2477. /*
  2478. Special Glob for handling path with [ or ] inside.
  2479. You can also pass in normal path for globing if you are not sure.
  2480. */
  2481. func system_fs_specialURIDecode(inputPath string) string {
  2482. inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
  2483. inputPath, _ = url.QueryUnescape(inputPath)
  2484. inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
  2485. return inputPath
  2486. }
  2487. /*
  2488. func system_fs_specialURIEncode(inputPath string) string {
  2489. inputPath = strings.ReplaceAll(inputPath, " ", "{{space_sign}}")
  2490. inputPath, _ = url.QueryUnescape(inputPath)
  2491. inputPath = strings.ReplaceAll(inputPath, "{{space_sign}}", "%20")
  2492. return inputPath
  2493. }
  2494. */
  2495. // Handle file properties request
  2496. func system_fs_getFileProperties(w http.ResponseWriter, r *http.Request) {
  2497. type fileProperties struct {
  2498. VirtualPath string
  2499. StoragePath string
  2500. Basename string
  2501. VirtualDirname string
  2502. StorageDirname string
  2503. Ext string
  2504. MimeType string
  2505. Filesize int64
  2506. Permission string
  2507. LastModTime string
  2508. LastModUnix int64
  2509. CreationTime string
  2510. CreationUnix int64
  2511. IsDirectory bool
  2512. Owner string
  2513. }
  2514. result := fileProperties{}
  2515. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2516. if err != nil {
  2517. utils.SendErrorResponse(w, err.Error())
  2518. return
  2519. }
  2520. vpath, err := utils.PostPara(r, "path")
  2521. if err != nil {
  2522. utils.SendErrorResponse(w, "path not defined")
  2523. return
  2524. }
  2525. vrootID, subpath, _ := filesystem.GetIDFromVirtualPath(vpath)
  2526. fsh, err := GetFsHandlerByUUID(vrootID)
  2527. if err != nil {
  2528. utils.SendErrorResponse(w, err.Error())
  2529. return
  2530. }
  2531. fshAbs := fsh.FileSystemAbstraction
  2532. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  2533. if err != nil {
  2534. utils.SendErrorResponse(w, err.Error())
  2535. return
  2536. }
  2537. fileStat, err := fshAbs.Stat(rpath)
  2538. if err != nil {
  2539. utils.SendErrorResponse(w, err.Error())
  2540. return
  2541. }
  2542. fileMime := "text/directory"
  2543. if !fileStat.IsDir() {
  2544. m, _, err := filesystem.GetMime(rpath)
  2545. if err != nil {
  2546. fileMime = mime.TypeByExtension(filepath.Ext(rpath))
  2547. } else {
  2548. fileMime = m
  2549. }
  2550. }
  2551. filesize := fileStat.Size()
  2552. //Get file overall size if this is folder
  2553. if fileStat.IsDir() {
  2554. if fsh.IsNetworkDrive() {
  2555. filesize = -1
  2556. } else {
  2557. //Check if du exists
  2558. usefallback := true //Use fallback
  2559. if fsh.IsLocalDrive() {
  2560. //Try using native syscall to grab directory size
  2561. nativeSize, err := filesystem.GetDirectorySizeNative(rpath)
  2562. if err == nil {
  2563. usefallback = false
  2564. filesize = nativeSize
  2565. }
  2566. }
  2567. if usefallback {
  2568. // invalid platform. walk the whole file system
  2569. var size int64 = 0
  2570. fshAbs.Walk(rpath, func(_ string, info os.FileInfo, err error) error {
  2571. if err != nil {
  2572. return err
  2573. }
  2574. if !info.IsDir() {
  2575. size += info.Size()
  2576. }
  2577. return err
  2578. })
  2579. filesize = size
  2580. }
  2581. }
  2582. }
  2583. //Get the file creation time. Not every platform / file system records one,
  2584. //in which case it is left empty for the client to grey out
  2585. creationTimeString := ""
  2586. var creationUnix int64 = 0
  2587. if creationTime, ok := filesystem.GetFileCreationTime(fileStat); ok {
  2588. creationTimeString = time.Unix(creationTime, 0).Format("2006-01-02 15:04:05")
  2589. creationUnix = creationTime
  2590. }
  2591. //Get file owner
  2592. owner := userinfo.GetFileOwner(fsh, vpath)
  2593. if owner == "" {
  2594. //Handle special virtual roots
  2595. owner = "Unknown"
  2596. }
  2597. result = fileProperties{
  2598. VirtualPath: vpath,
  2599. StoragePath: filepath.ToSlash(filepath.Clean(rpath)),
  2600. Basename: filepath.Base(rpath),
  2601. VirtualDirname: filepath.ToSlash(filepath.Dir(vpath)),
  2602. StorageDirname: filepath.ToSlash(filepath.Dir(rpath)),
  2603. Ext: filepath.Ext(rpath),
  2604. MimeType: fileMime,
  2605. Filesize: filesize,
  2606. Permission: fileStat.Mode().Perm().String(),
  2607. LastModTime: fileStat.ModTime().Format("2006-01-02 15:04:05"),
  2608. LastModUnix: fileStat.ModTime().Unix(),
  2609. CreationTime: creationTimeString,
  2610. CreationUnix: creationUnix,
  2611. IsDirectory: fileStat.IsDir(),
  2612. Owner: owner,
  2613. }
  2614. jsonString, _ := json.Marshal(result)
  2615. utils.SendJSONResponse(w, string(jsonString))
  2616. }
  2617. /*
  2618. List directory in the given path
  2619. Usage: Pass in dir like the following examples:
  2620. AOR:/Desktop <= Open /user/{username}/Desktop
  2621. S1:/ <= Open {uuid=S1}/
  2622. */
  2623. func system_fs_handleList(w http.ResponseWriter, r *http.Request) {
  2624. currentDir, err := utils.PostPara(r, "dir")
  2625. if err != nil {
  2626. utils.SendErrorResponse(w, err.Error())
  2627. return
  2628. }
  2629. sortMode, _ := utils.PostPara(r, "sort")
  2630. showHidden, _ := utils.PostPara(r, "showHidden")
  2631. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2632. if err != nil {
  2633. //user not logged in. Redirect to login page.
  2634. utils.SendErrorResponse(w, "User not logged in")
  2635. return
  2636. }
  2637. if currentDir == "" {
  2638. utils.SendErrorResponse(w, "Invalid dir given.")
  2639. return
  2640. }
  2641. // Pad a slash at the end of currentDir if not exists
  2642. if !strings.HasSuffix(currentDir, "/") {
  2643. currentDir = currentDir + "/"
  2644. }
  2645. fsh, subpath, err := GetFSHandlerSubpathFromVpath(currentDir)
  2646. if err != nil {
  2647. utils.SendErrorResponse(w, err.Error())
  2648. return
  2649. }
  2650. fshAbs := fsh.FileSystemAbstraction
  2651. realpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  2652. if err != nil {
  2653. utils.SendErrorResponse(w, err.Error())
  2654. return
  2655. }
  2656. if !fshAbs.FileExists(realpath) {
  2657. //Path not exists
  2658. userRoot, _ := fshAbs.VirtualPathToRealPath("/", userinfo.Username)
  2659. if filepath.Clean(realpath) == filepath.Clean(userRoot) || realpath == "" {
  2660. //Initiate user folder (Initiaed in user object)
  2661. err = fshAbs.MkdirAll(userRoot, 0775)
  2662. if err != nil {
  2663. systemWideLogger.PrintAndLog("File System", "Unable to create user root on "+fsh.UUID+": "+err.Error(), nil)
  2664. utils.SendErrorResponse(w, "Unable to create user root folder due to file system error")
  2665. return
  2666. }
  2667. } else {
  2668. //Folder not exists
  2669. systemWideLogger.PrintAndLog("File System", "Requested path: "+realpath+" does not exists", nil)
  2670. utils.SendErrorResponse(w, "Folder not exists")
  2671. return
  2672. }
  2673. }
  2674. if sortMode == "" {
  2675. sortMode = "default"
  2676. }
  2677. files, err := fshAbs.ReadDir(realpath)
  2678. if err != nil {
  2679. utils.SendErrorResponse(w, "Readdir Failed: "+strings.ReplaceAll(err.Error(), "\\", "/"))
  2680. systemWideLogger.PrintAndLog("File System", "Unable to read dir: "+err.Error(), err)
  2681. return
  2682. }
  2683. //Remapping use parsed list
  2684. parsedFilelist := map[string]filesystem.FileData{}
  2685. //Sorting use list
  2686. realpathList := []string{}
  2687. fileInfoList := []fs.FileInfo{}
  2688. for _, f := range files {
  2689. //Check if it is hidden file
  2690. isHidden, _ := hidden.IsHidden(f.Name(), false)
  2691. if showHidden != "true" && isHidden {
  2692. //Skipping hidden files
  2693. continue
  2694. }
  2695. //Check if this file contains invalid characters
  2696. if !utils.FilenameIsWebSafe(f.Name()) {
  2697. continue
  2698. }
  2699. //Check if this is an aodb file
  2700. if f.Name() == "aofs.db" || f.Name() == "aofs.db.lock" {
  2701. //Database file (reserved)
  2702. continue
  2703. }
  2704. //Check if it is shortcut file. If yes, render a shortcut data struct
  2705. var shortCutInfo *arozfs.ShortcutData = nil
  2706. if filepath.Ext(f.Name()) == ".shortcut" {
  2707. //This is a shortcut file
  2708. fcontent, err := fshAbs.ReadFile(arozfs.ToSlash(filepath.Join(realpath, f.Name())))
  2709. if err != nil {
  2710. shortCutInfo = nil
  2711. } else {
  2712. shorcutData, err := shortcut.ReadShortcut(fcontent)
  2713. if err != nil {
  2714. shortCutInfo = nil
  2715. } else {
  2716. shortCutInfo = shorcutData
  2717. }
  2718. }
  2719. }
  2720. statInfo, err := f.Info()
  2721. if err != nil {
  2722. continue
  2723. }
  2724. thisvPath, _ := fshAbs.RealPathToVirtualPath(filepath.Join(realpath, f.Name()), userinfo.Username)
  2725. thisFile := filesystem.FileData{
  2726. Filename: f.Name(),
  2727. Filepath: currentDir + f.Name(),
  2728. Realpath: filepath.ToSlash(filepath.Join(realpath, f.Name())),
  2729. IsDir: f.IsDir(),
  2730. Filesize: statInfo.Size(),
  2731. Displaysize: filesystem.GetFileDisplaySize(statInfo.Size(), 2),
  2732. ModTime: statInfo.ModTime().Unix(),
  2733. IsShared: shareManager.FileIsShared(userinfo, thisvPath),
  2734. Shortcut: shortCutInfo,
  2735. }
  2736. parsedFilelist[currentDir+f.Name()] = thisFile
  2737. realpathList = append(realpathList, currentDir+f.Name())
  2738. fileInfoList = append(fileInfoList, statInfo)
  2739. }
  2740. //Sort the filelist
  2741. sortedRealpathList := fssort.SortFileList(realpathList, fileInfoList, sortMode)
  2742. results := []filesystem.FileData{}
  2743. for _, thisRpath := range sortedRealpathList {
  2744. val, ok := parsedFilelist[thisRpath]
  2745. if ok {
  2746. results = append(results, val)
  2747. }
  2748. }
  2749. jsonString, _ := json.Marshal(results)
  2750. utils.SendJSONResponse(w, string(jsonString))
  2751. }
  2752. // Handle getting a hash from a given contents in the given path
  2753. func system_fs_handleDirHash(w http.ResponseWriter, r *http.Request) {
  2754. currentDir, err := utils.GetPara(r, "dir")
  2755. if err != nil {
  2756. utils.SendErrorResponse(w, "Invalid dir given")
  2757. return
  2758. }
  2759. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2760. if err != nil {
  2761. utils.SendErrorResponse(w, "User not logged in")
  2762. return
  2763. }
  2764. fsh, subpath, err := GetFSHandlerSubpathFromVpath(currentDir)
  2765. if err != nil {
  2766. utils.SendErrorResponse(w, "Unable to resolve target directory")
  2767. return
  2768. }
  2769. fshAbs := fsh.FileSystemAbstraction
  2770. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  2771. if err != nil {
  2772. utils.SendErrorResponse(w, "Invalid dir given")
  2773. return
  2774. }
  2775. //Get a list of files in this directory
  2776. /*
  2777. currentDir = filepath.ToSlash(filepath.Clean(rpath)) + "/"
  2778. filesInDir, err := fshAbs.Glob(currentDir + "*")
  2779. if err != nil {
  2780. utils.SendErrorResponse(w, err.Error())
  2781. return
  2782. }
  2783. filenames := []string{}
  2784. for _, file := range filesInDir {
  2785. if len(filepath.Base(file)) > 0 && string([]rune(filepath.Base(file))[0]) != "." {
  2786. //Ignore hidden files
  2787. filenames = append(filenames, filepath.Base(file))
  2788. }
  2789. }
  2790. */
  2791. finfos, err := fshAbs.ReadDir(rpath)
  2792. if err != nil {
  2793. utils.SendErrorResponse(w, err.Error())
  2794. return
  2795. }
  2796. filenames := []string{}
  2797. for _, fi := range finfos {
  2798. isHiddenFile, _ := hidden.IsHidden(fi.Name(), false)
  2799. if len(fi.Name()) > 0 && !isHiddenFile {
  2800. //Ignore hidden files
  2801. filenames = append(filenames, fi.Name())
  2802. }
  2803. }
  2804. sort.Strings(filenames)
  2805. //Build a hash base on the filelist
  2806. h := sha256.New()
  2807. h.Write([]byte(strings.Join(filenames, ",")))
  2808. utils.SendTextResponse(w, hex.EncodeToString((h.Sum(nil))))
  2809. }
  2810. /*
  2811. File zipping and unzipping functions
  2812. */
  2813. // Handle all zip related API
  2814. func system_fs_zipHandler(w http.ResponseWriter, r *http.Request) {
  2815. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2816. if err != nil {
  2817. utils.SendErrorResponse(w, err.Error())
  2818. return
  2819. }
  2820. opr, err := utils.PostPara(r, "opr")
  2821. if err != nil {
  2822. utils.SendErrorResponse(w, "Invalid opr or opr not defined")
  2823. return
  2824. }
  2825. vsrc, _ := utils.PostPara(r, "src")
  2826. if vsrc == "" {
  2827. utils.SendErrorResponse(w, "Invalid src paramter")
  2828. return
  2829. }
  2830. vdest, _ := utils.PostPara(r, "dest")
  2831. rdest := ""
  2832. //Convert source path from JSON string to object
  2833. virtualSourcePaths := []string{}
  2834. err = json.Unmarshal([]byte(vsrc), &virtualSourcePaths)
  2835. if err != nil {
  2836. utils.SendErrorResponse(w, err.Error())
  2837. return
  2838. }
  2839. //Check each of the path
  2840. realSourcePaths := []string{}
  2841. sourceFshs := []*filesystem.FileSystemHandler{}
  2842. for _, vpath := range virtualSourcePaths {
  2843. thisSrcFsh, subpath, err := GetFSHandlerSubpathFromVpath(vpath)
  2844. if err != nil {
  2845. utils.SendErrorResponse(w, "Unable to resolve file: "+vpath)
  2846. return
  2847. }
  2848. thisSrcFshAbs := thisSrcFsh.FileSystemAbstraction
  2849. thisrpath, err := thisSrcFshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  2850. if err != nil || !thisSrcFshAbs.FileExists(thisrpath) {
  2851. utils.SendErrorResponse(w, "File not exists: "+vpath)
  2852. return
  2853. }
  2854. realSourcePaths = append(realSourcePaths, thisrpath)
  2855. sourceFshs = append(sourceFshs, thisSrcFsh)
  2856. }
  2857. ///Convert dest to real if given
  2858. var destFsh *filesystem.FileSystemHandler = nil
  2859. var subpath string = ""
  2860. var filename string = ""
  2861. if vdest != "" {
  2862. //Given target virtual dest
  2863. destFsh, subpath, err = GetFSHandlerSubpathFromVpath(rdest)
  2864. if err != nil {
  2865. utils.SendErrorResponse(w, err.Error())
  2866. return
  2867. }
  2868. } else {
  2869. //Given no virtual dest. Zip to tmp:/
  2870. filename = utils.Int64ToString(time.Now().Unix()) + ".zip"
  2871. destFsh, subpath, err = GetFSHandlerSubpathFromVpath(filepath.Join("tmp:/", filename))
  2872. if err != nil {
  2873. utils.SendErrorResponse(w, err.Error())
  2874. return
  2875. }
  2876. }
  2877. rdest, _ = destFsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
  2878. destFshAbs := destFsh.FileSystemAbstraction
  2879. zipOutput := rdest
  2880. zipDestFsh := destFsh
  2881. if destFsh.RequireBuffer {
  2882. zipOutput = getFsBufferFilepath(rdest, false)
  2883. zipDestFsh = nil
  2884. }
  2885. if opr == "tmpzipAsync" {
  2886. /*
  2887. Same as tmpzip, but the zipping runs in the background and reports
  2888. its progress into the file operation task list, so the caller can
  2889. follow it on /system/file_system/ongoing instead of staring at a
  2890. request that returns nothing until the archive is finished.
  2891. */
  2892. if filename == "" {
  2893. utils.SendErrorResponse(w, "tmpzipAsync does not support a custom destination")
  2894. return
  2895. }
  2896. vzipPath := "tmp:/" + filename
  2897. task := NewOngoingFileOperation(userinfo, "zip", virtualSourcePaths, vzipPath)
  2898. go runTmpZipTask(task.ID, sourceFshs, realSourcePaths, zipDestFsh, zipOutput, destFsh, rdest)
  2899. js, _ := json.Marshal(map[string]string{
  2900. "oprid": task.ID,
  2901. "dest": vzipPath,
  2902. })
  2903. utils.SendJSONResponse(w, string(js))
  2904. return
  2905. }
  2906. if opr == "zip" {
  2907. //Check if destination location exists
  2908. if rdest == "" || !destFshAbs.FileExists(filepath.Dir(zipOutput)) {
  2909. utils.SendErrorResponse(w, "Invalid dest location")
  2910. return
  2911. }
  2912. //OK. Create the zip at the desired location
  2913. err := filesystem.ArozZipFile(sourceFshs, realSourcePaths, zipDestFsh, zipOutput, false)
  2914. if err != nil {
  2915. utils.SendErrorResponse(w, err.Error())
  2916. return
  2917. }
  2918. utils.SendOK(w)
  2919. } else if opr == "tmpzip" {
  2920. //Zip to tmp folder
  2921. err := filesystem.ArozZipFile(sourceFshs, realSourcePaths, zipDestFsh, zipOutput, false)
  2922. if err != nil {
  2923. utils.SendErrorResponse(w, err.Error())
  2924. return
  2925. }
  2926. //Send the tmp filename to the user
  2927. utils.SendTextResponse(w, "tmp:/"+filename)
  2928. }
  2929. if destFsh.RequireBuffer {
  2930. //Write the buffer zip file to destination
  2931. f, _ := os.Open(zipOutput)
  2932. destFsh.FileSystemAbstraction.WriteStream(rdest, f, 0775)
  2933. f.Close()
  2934. os.Remove(zipOutput)
  2935. }
  2936. cleanFsBufferFileFromList(realSourcePaths)
  2937. }
  2938. /*
  2939. runTmpZipTask builds a temporary zip in the background, keeping the file
  2940. operation record of oprId up to date so the front end can poll its progress.
  2941. The progress handler returns the task's control signal, which is what lets a
  2942. user cancel the archive from the file operation list: ArozZipFileWithProgress
  2943. stops and reports the cancellation as an error, and finishTmpZipTask turns that
  2944. back into a cancelled record.
  2945. */
  2946. func runTmpZipTask(oprId string, sourceFshs []*filesystem.FileSystemHandler, realSourcePaths []string, zipDestFsh *filesystem.FileSystemHandler, zipOutput string, destFsh *filesystem.FileSystemHandler, rdest string) {
  2947. err := filesystem.ArozZipFileWithProgress(sourceFshs, realSourcePaths, zipDestFsh, zipOutput, false, func(currentFilename string, _ int, _ int, progress float64) int {
  2948. sig, _ := UpdateOngoingFileOperation(oprId, currentFilename, math.Ceil(progress))
  2949. return sig
  2950. })
  2951. if err != nil {
  2952. systemWideLogger.PrintAndLog("File System", "Zipping request failed: "+err.Error(), err)
  2953. finishTmpZipTask(oprId, err)
  2954. return
  2955. }
  2956. if destFsh.RequireBuffer {
  2957. //Write the buffer zip file to destination
  2958. f, _ := os.Open(zipOutput)
  2959. err = destFsh.FileSystemAbstraction.WriteStream(rdest, f, 0775)
  2960. f.Close()
  2961. os.Remove(zipOutput)
  2962. if err != nil {
  2963. systemWideLogger.PrintAndLog("File System", "Zip write to remote file system with driver "+destFsh.Filesystem+" failed", err)
  2964. finishTmpZipTask(oprId, err)
  2965. return
  2966. }
  2967. }
  2968. cleanFsBufferFileFromList(realSourcePaths)
  2969. finishTmpZipTask(oprId, nil)
  2970. }
  2971. // finishTmpZipTask closes off the task record of a background tmp zip.
  2972. // Pass in the error the zipping ended with, or nil when it completed.
  2973. func finishTmpZipTask(oprId string, err error) {
  2974. if err != nil {
  2975. SetFileOperationTaskEnded(oprId, filesystem.FsOpr_Error, err.Error())
  2976. return
  2977. }
  2978. SetFileOperationTaskEnded(oprId, filesystem.FsOpr_Continue, "")
  2979. }
  2980. // Manage file version history
  2981. func system_fs_FileVersionHistory(w http.ResponseWriter, r *http.Request) {
  2982. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  2983. if err != nil {
  2984. utils.SendErrorResponse(w, err.Error())
  2985. return
  2986. }
  2987. path, err := utils.PostPara(r, "path")
  2988. if err != nil {
  2989. utils.SendErrorResponse(w, "Invalid path given")
  2990. return
  2991. }
  2992. fsh, subpath, err := GetFSHandlerSubpathFromVpath(path)
  2993. if err != nil {
  2994. if err != nil {
  2995. utils.SendErrorResponse(w, "Invalid path given")
  2996. return
  2997. }
  2998. }
  2999. fshAbs := fsh.FileSystemAbstraction
  3000. opr, _ := utils.PostPara(r, "opr")
  3001. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  3002. if err != nil {
  3003. utils.SendErrorResponse(w, "Unable to translate virtual path")
  3004. return
  3005. }
  3006. if opr == "" {
  3007. //List file history
  3008. fileVersionData, err := localversion.GetFileVersionData(fsh, rpath)
  3009. if err != nil {
  3010. utils.SendErrorResponse(w, "Unable to load version information: "+err.Error())
  3011. return
  3012. }
  3013. js, _ := json.Marshal(fileVersionData)
  3014. utils.SendJSONResponse(w, string(js))
  3015. } else if opr == "delete" {
  3016. //Delete file history of given history ID
  3017. historyID, err := utils.PostPara(r, "histid")
  3018. if err != nil {
  3019. utils.SendErrorResponse(w, "Invalid history id given")
  3020. return
  3021. }
  3022. err = localversion.RemoveFileHistory(fsh, rpath, historyID)
  3023. if err != nil {
  3024. utils.SendErrorResponse(w, err.Error())
  3025. return
  3026. }
  3027. utils.SendOK(w)
  3028. } else if opr == "deleteAll" {
  3029. //Delete all file history of given vpath
  3030. err = localversion.RemoveAllRelatedFileHistory(fsh, rpath)
  3031. if err != nil {
  3032. utils.SendErrorResponse(w, err.Error())
  3033. return
  3034. }
  3035. utils.SendOK(w)
  3036. } else if opr == "restore" {
  3037. //Restore file history of given history ID
  3038. historyID, err := utils.PostPara(r, "histid")
  3039. if err != nil {
  3040. utils.SendErrorResponse(w, "Invalid history id given")
  3041. return
  3042. }
  3043. err = localversion.RestoreFileHistory(fsh, rpath, historyID)
  3044. if err != nil {
  3045. utils.SendErrorResponse(w, err.Error())
  3046. return
  3047. }
  3048. utils.SendOK(w)
  3049. } else if opr == "new" {
  3050. //Create a new snapshot of this file
  3051. err = localversion.CreateFileSnapshot(fsh, rpath)
  3052. if err != nil {
  3053. utils.SendErrorResponse(w, err.Error())
  3054. return
  3055. }
  3056. utils.SendOK(w)
  3057. } else {
  3058. utils.SendErrorResponse(w, "Unknown opr")
  3059. }
  3060. }
  3061. func system_fs_clearVersionHistories() {
  3062. allFsh := GetAllLoadedFsh()
  3063. for _, fsh := range allFsh {
  3064. if !fsh.ReadOnly {
  3065. localversion.CleanExpiredVersionBackups(fsh, fsh.Path, 30*86400)
  3066. }
  3067. }
  3068. }
  3069. // Handle cache rendering with websocket pipeline
  3070. func system_fs_handleCacheRender(w http.ResponseWriter, r *http.Request) {
  3071. userinfo, _ := userHandler.GetUserInfoFromRequest(w, r)
  3072. vpath, err := utils.GetPara(r, "folder")
  3073. if err != nil {
  3074. utils.SendErrorResponse(w, "Invalid folder paramter")
  3075. return
  3076. }
  3077. fsh, subpath, err := GetFSHandlerSubpathFromVpath(vpath)
  3078. if err != nil {
  3079. utils.SendErrorResponse(w, "Unable to resolve target directory")
  3080. return
  3081. }
  3082. rpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
  3083. //Get folder sort mode
  3084. sortMode := "default"
  3085. folder := filepath.ToSlash(filepath.Clean(vpath))
  3086. if sysdb.KeyExists("fs-sortpref", userinfo.Username+"/"+folder) {
  3087. sysdb.Read("fs-sortpref", userinfo.Username+"/"+folder, &sortMode)
  3088. }
  3089. //Perform cache rendering
  3090. thumbRenderHandler.HandleLoadCache(w, r, fsh, rpath, sortMode)
  3091. }
  3092. // Handle loading of one thumbnail
  3093. func system_fs_handleThumbnailLoad(w http.ResponseWriter, r *http.Request) {
  3094. userinfo, _ := userHandler.GetUserInfoFromRequest(w, r)
  3095. vpath, err := utils.GetPara(r, "vpath")
  3096. if err != nil {
  3097. utils.SendErrorResponse(w, "vpath not defined")
  3098. return
  3099. }
  3100. byteMode, _ := utils.GetPara(r, "bytes")
  3101. isByteMode := byteMode == "true"
  3102. fsh, subpath, err := GetFSHandlerSubpathFromVpath(vpath)
  3103. if err != nil {
  3104. if isByteMode {
  3105. http.NotFound(w, r)
  3106. return
  3107. }
  3108. utils.SendErrorResponse(w, "Unable to resolve target directory")
  3109. return
  3110. }
  3111. rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
  3112. if err != nil {
  3113. if isByteMode {
  3114. http.NotFound(w, r)
  3115. return
  3116. }
  3117. utils.SendErrorResponse(w, err.Error())
  3118. return
  3119. }
  3120. if isByteMode {
  3121. thumbnailBytes, err := thumbRenderHandler.LoadCacheAsBytes(fsh, vpath, userinfo.Username, false)
  3122. if err != nil {
  3123. http.NotFound(w, r)
  3124. return
  3125. }
  3126. filetype := http.DetectContentType(thumbnailBytes)
  3127. w.Header().Add("Content-Type", filetype)
  3128. w.Write(thumbnailBytes)
  3129. } else {
  3130. thumbnailPath, err := thumbRenderHandler.LoadCache(fsh, rpath, false)
  3131. if err != nil {
  3132. utils.SendErrorResponse(w, err.Error())
  3133. return
  3134. }
  3135. js, _ := json.Marshal(thumbnailPath)
  3136. utils.SendJSONResponse(w, string(js))
  3137. }
  3138. }
  3139. // Handle file thumbnail caching
  3140. func system_fs_handleFolderCache(w http.ResponseWriter, r *http.Request) {
  3141. userinfo, _ := userHandler.GetUserInfoFromRequest(w, r)
  3142. vfolderpath, err := utils.GetPara(r, "folder")
  3143. if err != nil {
  3144. utils.SendErrorResponse(w, "folder not defined")
  3145. return
  3146. }
  3147. fsh, _, err := GetFSHandlerSubpathFromVpath(vfolderpath)
  3148. if err != nil {
  3149. utils.SendErrorResponse(w, "unable to resolve path")
  3150. return
  3151. }
  3152. thumbRenderHandler.BuildCacheForFolder(fsh, vfolderpath, userinfo.Username)
  3153. utils.SendOK(w)
  3154. }
  3155. // Handle the get and set of sort mode of a particular folder
  3156. func system_fs_handleFolderSortModePreference(w http.ResponseWriter, r *http.Request) {
  3157. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  3158. if err != nil {
  3159. utils.SendErrorResponse(w, "User not logged in")
  3160. return
  3161. }
  3162. folder, err := utils.PostPara(r, "folder")
  3163. if err != nil {
  3164. utils.SendErrorResponse(w, "Invalid folder given")
  3165. return
  3166. }
  3167. opr, _ := utils.PostPara(r, "opr")
  3168. folder = filepath.ToSlash(filepath.Clean(folder))
  3169. if opr == "" || opr == "get" {
  3170. sortMode := "default"
  3171. if sysdb.KeyExists("fs-sortpref", userinfo.Username+"/"+folder) {
  3172. sysdb.Read("fs-sortpref", userinfo.Username+"/"+folder, &sortMode)
  3173. }
  3174. js, err := json.Marshal(sortMode)
  3175. if err != nil {
  3176. utils.SendErrorResponse(w, err.Error())
  3177. return
  3178. }
  3179. utils.SendJSONResponse(w, string(js))
  3180. } else if opr == "set" {
  3181. sortMode, err := utils.PostPara(r, "mode")
  3182. if err != nil {
  3183. utils.SendErrorResponse(w, "Invalid sort mode given")
  3184. return
  3185. }
  3186. if !utils.StringInArray(fssort.ValidSortModes, sortMode) {
  3187. utils.SendErrorResponse(w, "Not supported sort mode: "+sortMode)
  3188. return
  3189. }
  3190. sysdb.Write("fs-sortpref", userinfo.Username+"/"+folder, sortMode)
  3191. utils.SendOK(w)
  3192. } else {
  3193. utils.SendErrorResponse(w, "Invalid opr mode")
  3194. return
  3195. }
  3196. }
  3197. // Handle setting and loading of file permission on Linux
  3198. func system_fs_handleFilePermission(w http.ResponseWriter, r *http.Request) {
  3199. file, err := utils.PostPara(r, "file")
  3200. if err != nil {
  3201. utils.SendErrorResponse(w, "Invalid file")
  3202. return
  3203. }
  3204. //Translate the file to real path
  3205. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  3206. if err != nil {
  3207. utils.SendErrorResponse(w, "User not logged in")
  3208. return
  3209. }
  3210. fsh, subpath, err := GetFSHandlerSubpathFromVpath(file)
  3211. if err != nil {
  3212. utils.SendErrorResponse(w, err.Error())
  3213. return
  3214. }
  3215. fshAbs := fsh.FileSystemAbstraction
  3216. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  3217. if err != nil {
  3218. utils.SendErrorResponse(w, err.Error())
  3219. return
  3220. }
  3221. newMode, _ := utils.PostPara(r, "mode")
  3222. if newMode == "" {
  3223. //Read the file mode
  3224. //Check if the file exists
  3225. if !fshAbs.FileExists(rpath) {
  3226. utils.SendErrorResponse(w, "File not exists!")
  3227. return
  3228. }
  3229. //Read the file permission
  3230. filePermission, err := fsp.GetFilePermissions(fsh, rpath)
  3231. if err != nil {
  3232. utils.SendErrorResponse(w, err.Error())
  3233. return
  3234. }
  3235. //Send the file permission to client
  3236. js, _ := json.Marshal(filePermission)
  3237. utils.SendJSONResponse(w, string(js))
  3238. } else {
  3239. //Set the file mode
  3240. //Check if the file exists
  3241. if !filesystem.FileExists(rpath) {
  3242. utils.SendErrorResponse(w, "File not exists!")
  3243. return
  3244. }
  3245. //Check if windows. If yes, ignore this request
  3246. if runtime.GOOS == "windows" {
  3247. utils.SendErrorResponse(w, "Windows host not supported")
  3248. return
  3249. }
  3250. //Check if this user has permission to change the file permission
  3251. //Aka user must be 1. This is his own folder or 2. Admin
  3252. fsh, _ := userinfo.GetFileSystemHandlerFromVirtualPath(file)
  3253. if fsh.Hierarchy == "user" {
  3254. //Always ok as this is owned by the user
  3255. } else if fsh.Hierarchy == "public" {
  3256. //Require admin
  3257. if !userinfo.IsAdmin() {
  3258. utils.SendErrorResponse(w, "Permission Denied")
  3259. return
  3260. }
  3261. } else {
  3262. //Not implemeneted. Require admin
  3263. if !userinfo.IsAdmin() {
  3264. utils.SendErrorResponse(w, "Permission Denied")
  3265. return
  3266. }
  3267. }
  3268. //Be noted that if the system is not running in sudo mode,
  3269. //File permission change might not works.
  3270. err := fsp.SetFilePermisson(fsh, rpath, newMode)
  3271. if err != nil {
  3272. utils.SendErrorResponse(w, err.Error())
  3273. return
  3274. } else {
  3275. utils.SendOK(w)
  3276. }
  3277. }
  3278. }
  3279. // Clear the old files inside the tmp file
  3280. func system_fs_clearOldTmpFiles() {
  3281. filesToBeDelete := []string{}
  3282. tmpAbs, _ := filepath.Abs(*tmp_directory)
  3283. filepath.Walk(*tmp_directory, func(path string, info os.FileInfo, err error) error {
  3284. if filepath.Base(path) != "aofs.db" && filepath.Base(path) != "aofs.db.lock" {
  3285. //Check if root folders. Do not delete root folders
  3286. parentAbs, _ := filepath.Abs(filepath.Dir(path))
  3287. if tmpAbs == parentAbs {
  3288. //Root folder. Do not remove
  3289. return nil
  3290. }
  3291. //Get its modification time
  3292. modTime, err := filesystem.GetModTime(path)
  3293. if err != nil {
  3294. return nil
  3295. }
  3296. //Check if mod time is more than 24 hours ago
  3297. if time.Now().Unix()-modTime > int64(*maxTempFileKeepTime) {
  3298. //Delete OK
  3299. filesToBeDelete = append(filesToBeDelete, path)
  3300. }
  3301. }
  3302. return nil
  3303. })
  3304. //Remove all files from the delete list
  3305. for _, fileToBeDelete := range filesToBeDelete {
  3306. os.RemoveAll(fileToBeDelete)
  3307. }
  3308. }
  3309. /*
  3310. File System Utilities for Buffered type FS
  3311. These functions help create a local representation of file
  3312. buffer from remote file systems like webdav or SMB
  3313. **REMEMBER TO CLEAR THE BUFFER FILES YOURSELF**
  3314. Example Usage
  3315. //Replace a destination path (for file create) with local buffer filepath
  3316. if destFsh.RequireBuffer {
  3317. dest = getFsBufferFilepath(outputFilename)
  3318. }
  3319. //Buffer a remote file to local first before doing any advance file operations
  3320. if thisSrcFsh.RequireBuffer {
  3321. localBufferFilepath, err := bufferRemoteFileToLocal(fsh, remoteRealSrc)
  3322. if err != nil{
  3323. //Handle Error
  3324. }
  3325. }
  3326. //Clean a list of source files that contains local buffer files
  3327. clearnFsBufferFileFromList(realSourceFiles)
  3328. */
  3329. // Generate a random buffer filepath. Remember to delete file after usage
  3330. func getFsBufferFilepath(originalFilename string, keepOriginalName bool) string {
  3331. thisBuffFilename := uuid.NewV4().String()
  3332. tmpDir := filepath.Join(*tmp_directory, "fsBuff")
  3333. targetFile := filepath.Join(tmpDir, thisBuffFilename+filepath.Ext(originalFilename))
  3334. if keepOriginalName {
  3335. targetFile = filepath.Join(tmpDir, thisBuffFilename, filepath.Base(originalFilename))
  3336. }
  3337. os.MkdirAll(filepath.Dir(targetFile), 0775)
  3338. return filepath.ToSlash(targetFile)
  3339. }
  3340. // Generate a buffer filepath and buffer the remote file to local. Remember to remove file after done.
  3341. func bufferRemoteFileToLocal(targetFsh *filesystem.FileSystemHandler, rpath string, keepOriginalName bool) (string, error) {
  3342. newBufferFilename := getFsBufferFilepath(rpath, keepOriginalName)
  3343. src, err := targetFsh.FileSystemAbstraction.ReadStream(rpath)
  3344. if err != nil {
  3345. systemWideLogger.PrintAndLog("File System", "Buffer from remote to local failed: "+err.Error(), err)
  3346. return "", err
  3347. }
  3348. defer src.Close()
  3349. dest, err := os.OpenFile(newBufferFilename, os.O_CREATE|os.O_WRONLY, 0775)
  3350. if err != nil {
  3351. systemWideLogger.PrintAndLog("File System", "Buffer from remote to local failed: "+err.Error(), err)
  3352. return "", err
  3353. }
  3354. io.Copy(dest, src)
  3355. dest.Close()
  3356. return newBufferFilename, nil
  3357. }
  3358. // Check if a file is buffer filepath
  3359. func isFsBufferFilepath(filename string) bool {
  3360. tmpDir := filepath.Join(*tmp_directory, "fsBuff")
  3361. filenameAbs, _ := filepath.Abs(filename)
  3362. filenameAbs = filepath.ToSlash(filenameAbs)
  3363. tmpDirAbs, _ := filepath.Abs(tmpDir)
  3364. tmpDirAbs = filepath.ToSlash(tmpDirAbs)
  3365. return strings.HasPrefix(filenameAbs, tmpDirAbs)
  3366. }
  3367. func cleanFsBufferFileFromList(filelist []string) {
  3368. for _, thisFilepath := range filelist {
  3369. if isFsBufferFilepath(thisFilepath) {
  3370. os.RemoveAll(thisFilepath)
  3371. folderContent, _ := os.ReadDir(filepath.Dir(thisFilepath))
  3372. if len(folderContent) == 0 {
  3373. //Nothing in this folder. Remove it
  3374. os.Remove(filepath.Dir(thisFilepath))
  3375. }
  3376. }
  3377. }
  3378. }
  3379. /*
  3380. File operation task book keeping
  3381. All file operations of all users are tracked in wsConnectionStore, keyed by
  3382. the operation id. Finished records are kept around for a while so the file
  3383. operation dialog can still show the completed entries to the user before
  3384. they are cleared (either by the user or by the janitor below).
  3385. */
  3386. // Handle all the on going task requests.
  3387. // Accept parameter: flag={continue / pause / cancel / remove / clear}
  3388. // When no flag is given, the ongoing task list of this user is returned.
  3389. // Pass in all=true to include the recently finished tasks in the listing.
  3390. func system_fs_HandleOnGoingTasks(w http.ResponseWriter, r *http.Request) {
  3391. //Get the user information
  3392. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  3393. if err != nil {
  3394. utils.SendErrorResponse(w, "User not logged in")
  3395. return
  3396. }
  3397. statusFlag, _ := utils.PostPara(r, "flag")
  3398. oprid, _ := utils.PostPara(r, "oprid")
  3399. if statusFlag == "" {
  3400. //Also accept the flag as a GET parameter for easier polling
  3401. statusFlag, _ = utils.GetPara(r, "flag")
  3402. oprid, _ = utils.GetPara(r, "oprid")
  3403. }
  3404. if statusFlag == "" {
  3405. //No flag defined. Print all operations of this user
  3406. includeFinished, _ := utils.GetPara(r, "all")
  3407. js, _ := MarshalFileOperationForUser(userinfo.Username, includeFinished == "true")
  3408. utils.SendJSONResponse(w, string(js))
  3409. return
  3410. }
  3411. if statusFlag == "clear" {
  3412. //Clear all the finished records of this user
  3413. ClearFinishedFileOperationForUser(userinfo.Username)
  3414. utils.SendOK(w)
  3415. return
  3416. }
  3417. if oprid == "" {
  3418. utils.SendErrorResponse(w, "oprid is empty or not set")
  3419. return
  3420. }
  3421. err = ApplyFileOperationControl(userinfo.Username, statusFlag, oprid)
  3422. if err != nil {
  3423. utils.SendErrorResponse(w, err.Error())
  3424. return
  3425. }
  3426. utils.SendOK(w)
  3427. }
  3428. // ApplyFileOperationControl applies a control signal to one of the file operations
  3429. // owned by the given user. Supported commands are continue, pause, cancel and remove.
  3430. func ApplyFileOperationControl(username string, command string, oprid string) error {
  3431. if command == "clear" {
  3432. ClearFinishedFileOperationForUser(username)
  3433. return nil
  3434. }
  3435. //Get the operation record
  3436. oprRecord, err := GetOngoingFileOperationByOprID(oprid)
  3437. if err != nil {
  3438. return err
  3439. }
  3440. //Only the owner of this operation can control it
  3441. if oprRecord.Owner != username {
  3442. return errors.New("permission denied")
  3443. }
  3444. fileOprTaskLock.Lock()
  3445. defer fileOprTaskLock.Unlock()
  3446. switch command {
  3447. case "continue":
  3448. //Continue the file operation
  3449. oprRecord.FileOperationSignal = filesystem.FsOpr_Continue
  3450. case "pause":
  3451. //Pause the file operation until the flag is set to other status
  3452. oprRecord.FileOperationSignal = filesystem.FsOpr_Pause
  3453. case "cancel":
  3454. //Cancel and stop the operation
  3455. oprRecord.FileOperationSignal = filesystem.FsOpr_Cancel
  3456. if taskIsFinished(oprRecord) {
  3457. //Already finished. Nothing left to cancel, just drop the record
  3458. wsConnectionStore.Delete(oprid)
  3459. }
  3460. case "remove":
  3461. //Remove a finished record from the listing
  3462. if !taskIsFinished(oprRecord) {
  3463. return errors.New("task is still running")
  3464. }
  3465. wsConnectionStore.Delete(oprid)
  3466. default:
  3467. return errors.New("unsupported operation")
  3468. }
  3469. return nil
  3470. }
  3471. // NewOngoingFileOperation creates and registers a new file operation task record.
  3472. // The size of each source file is resolved here so the front end can show the
  3473. // transferred / total size of every file in the operation.
  3474. func NewOngoingFileOperation(userinfo *user.User, operation string, sourceFiles []string, vdestFile string) *fileOperationTask {
  3475. /*
  3476. Sizing a source folder means walking it. For a move that the storage can
  3477. satisfy with a rename that walk costs far more than the move itself, and
  3478. it would only feed a bar that jumps straight to done, so it is skipped.
  3479. Should the rename fail after all and the files end up being streamed, the
  3480. first progress report fills the real sizes in, see
  3481. UpdateOngoingFileOperationSubtask.
  3482. */
  3483. resolveSizes := !fileOperationCanBeRenamed(operation, sourceFiles, vdestFile)
  3484. subtasks := []*fileOperationSubtask{}
  3485. totalSize := int64(0)
  3486. for _, vsrc := range sourceFiles {
  3487. thisSize, thisIsDir := resolveFileOperationSourceInfo(userinfo, vsrc, resolveSizes)
  3488. totalSize += thisSize
  3489. subtasks = append(subtasks, &fileOperationSubtask{
  3490. Filename: filepath.Base(strings.TrimSuffix(vsrc, "/")),
  3491. Src: arozfs.ToSlash(vsrc),
  3492. IsDir: thisIsDir,
  3493. Size: thisSize,
  3494. Done: 0,
  3495. Progress: 0,
  3496. Status: FsTask_Pending,
  3497. })
  3498. }
  3499. thisTask := fileOperationTask{
  3500. ID: strconv.Itoa(int(time.Now().Unix())) + "_" + uuid.NewV4().String(),
  3501. Owner: userinfo.Username,
  3502. Operation: operation,
  3503. Src: arozfs.ToSlash(filepath.Dir(sourceFiles[0])),
  3504. Dest: arozfs.ToSlash(vdestFile),
  3505. Progress: 0.0,
  3506. LatestFile: arozfs.ToSlash(filepath.Base(sourceFiles[0])),
  3507. FileOperationSignal: filesystem.FsOpr_Continue,
  3508. Files: subtasks,
  3509. TotalSize: totalSize,
  3510. DoneSize: 0,
  3511. StartTime: time.Now().Unix(),
  3512. Status: FsTask_Ongoing,
  3513. }
  3514. wsConnectionStore.Store(thisTask.ID, &thisTask)
  3515. startFinishedFileOperationJanitor()
  3516. return &thisTask
  3517. }
  3518. // resolveFileOperationSourceInfo returns the size of a source file in bytes and
  3519. // whether it is a folder. Set resolveSize to false to skip the folder walk when
  3520. // the size is not worth what it costs to work out.
  3521. // The size is 0 when it cannot be determined.
  3522. func resolveFileOperationSourceInfo(userinfo *user.User, vsrc string, resolveSize bool) (int64, bool) {
  3523. fsh, subpath, err := GetFSHandlerSubpathFromVpath(vsrc)
  3524. if err != nil {
  3525. return 0, false
  3526. }
  3527. rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
  3528. if err != nil {
  3529. return 0, false
  3530. }
  3531. isDir := fsh.FileSystemAbstraction.IsDir(rpath)
  3532. if !resolveSize {
  3533. return 0, isDir
  3534. }
  3535. if isDir {
  3536. size, _ := fsh.GetDirctorySizeFromRealPath(rpath, false)
  3537. return size, true
  3538. }
  3539. return fsh.FileSystemAbstraction.GetFileSize(rpath), false
  3540. }
  3541. // fileOperationCanBeRenamed reports whether an operation is a move that the
  3542. // storage holding both of its ends can carry out on its own, in which case no
  3543. // bytes travel through ArozOS and the operation finishes right away.
  3544. func fileOperationCanBeRenamed(operation string, sourceFiles []string, vdestFile string) bool {
  3545. if operation != "move" {
  3546. return false
  3547. }
  3548. destFsh, _, err := GetFSHandlerSubpathFromVpath(vdestFile)
  3549. if err != nil || destFsh.ReadOnly {
  3550. return false
  3551. }
  3552. for _, vsrc := range sourceFiles {
  3553. srcFsh, _, err := GetFSHandlerSubpathFromVpath(vsrc)
  3554. if err != nil || srcFsh.ReadOnly || !filesystem.SameFileSystem(srcFsh, destFsh) {
  3555. return false
  3556. }
  3557. }
  3558. return true
  3559. }
  3560. // GetAllFileOperationForUser returns all the file operation records of the given user.
  3561. // Set includeFinished to true to also return the recently finished records.
  3562. // The records are live objects that the running operations keep updating, so
  3563. // read them through MarshalFileOperationForUser unless you hold fileOprTaskLock.
  3564. func GetAllFileOperationForUser(username string, includeFinished bool) []*fileOperationTask {
  3565. fileOprTaskLock.RLock()
  3566. defer fileOprTaskLock.RUnlock()
  3567. return collectFileOperationForUser(username, includeFinished)
  3568. }
  3569. // MarshalFileOperationForUser serializes the file operation records of a user
  3570. // while holding the task lock, so the running operations cannot update a record
  3571. // half way through the serialization.
  3572. func MarshalFileOperationForUser(username string, includeFinished bool) ([]byte, error) {
  3573. fileOprTaskLock.RLock()
  3574. defer fileOprTaskLock.RUnlock()
  3575. return json.Marshal(collectFileOperationForUser(username, includeFinished))
  3576. }
  3577. // collectFileOperationForUser gathers the task records of a user in operation id
  3578. // order. The caller is expected to be holding fileOprTaskLock.
  3579. func collectFileOperationForUser(username string, includeFinished bool) []*fileOperationTask {
  3580. results := []*fileOperationTask{}
  3581. wsConnectionStore.Range(func(key, value interface{}) bool {
  3582. taskInfo := value.(*fileOperationTask)
  3583. if taskInfo.Owner == username && (includeFinished || !taskIsFinished(taskInfo)) {
  3584. results = append(results, taskInfo)
  3585. }
  3586. return true
  3587. })
  3588. sort.Slice(results, func(i, j int) bool {
  3589. return results[i].ID < results[j].ID
  3590. })
  3591. return results
  3592. }
  3593. // ClearFinishedFileOperationForUser removes all the finished records of a user
  3594. func ClearFinishedFileOperationForUser(username string) {
  3595. fileOprTaskLock.Lock()
  3596. defer fileOprTaskLock.Unlock()
  3597. wsConnectionStore.Range(func(key, value interface{}) bool {
  3598. taskInfo := value.(*fileOperationTask)
  3599. if taskInfo.Owner == username && taskIsFinished(taskInfo) {
  3600. wsConnectionStore.Delete(key)
  3601. }
  3602. return true
  3603. })
  3604. }
  3605. // Get an ongoing task record
  3606. func GetOngoingFileOperationByOprID(oprid string) (*fileOperationTask, error) {
  3607. object, ok := wsConnectionStore.Load(oprid)
  3608. if !ok {
  3609. return nil, errors.New("task not exists")
  3610. }
  3611. return object.(*fileOperationTask), nil
  3612. }
  3613. // Set or update an ongoing task record
  3614. func SetOngoingFileOperation(opr *fileOperationTask) {
  3615. wsConnectionStore.Store(opr.ID, opr)
  3616. }
  3617. // Update the status of an ongoing task record, return latest status code and error if any
  3618. func UpdateOngoingFileOperation(oprid string, currentFile string, progress float64) (int, error) {
  3619. t, err := GetOngoingFileOperationByOprID(oprid)
  3620. if err != nil {
  3621. return 0, err
  3622. }
  3623. fileOprTaskLock.Lock()
  3624. t.LatestFile = currentFile
  3625. t.Progress = progress
  3626. t.DoneSize = int64(float64(t.TotalSize) * progress / 100)
  3627. //Zip and unzip report one progress value for the whole operation. Spread it
  3628. //over the source files in order so the dialog can still show them one by one.
  3629. remaining := t.DoneSize
  3630. for _, thisSubtask := range t.Files {
  3631. if remaining >= thisSubtask.Size {
  3632. thisSubtask.Done = thisSubtask.Size
  3633. thisSubtask.Progress = 100
  3634. thisSubtask.Status = FsTask_Completed
  3635. remaining -= thisSubtask.Size
  3636. } else {
  3637. thisSubtask.Done = remaining
  3638. if thisSubtask.Size > 0 {
  3639. thisSubtask.Progress = float64(remaining) / float64(thisSubtask.Size) * 100
  3640. } else {
  3641. thisSubtask.Progress = progress
  3642. }
  3643. thisSubtask.Status = FsTask_Ongoing
  3644. remaining = 0
  3645. }
  3646. }
  3647. sig := t.FileOperationSignal
  3648. fileOprTaskLock.Unlock()
  3649. return sig, nil
  3650. }
  3651. /*
  3652. UpdateOngoingFileOperationSubtask records how many bytes of one source file are
  3653. already processed and recalculates the progress of the whole operation from it.
  3654. It returns the current control signal and the overall progress of the operation.
  3655. bytesTotal is the size the file operation itself reports for this source, which
  3656. wins over the size resolved when the task was created: a folder can grow or
  3657. shrink between the two, and the transfer knows better than the earlier walk.
  3658. */
  3659. func UpdateOngoingFileOperationSubtask(oprid string, subtaskIndex int, currentFile string, bytesDone int64, bytesTotal int64) (int, float64, error) {
  3660. t, err := GetOngoingFileOperationByOprID(oprid)
  3661. if err != nil {
  3662. return 0, 0, err
  3663. }
  3664. fileOprTaskLock.Lock()
  3665. if currentFile != "" {
  3666. t.LatestFile = currentFile
  3667. }
  3668. if subtaskIndex >= 0 && subtaskIndex < len(t.Files) {
  3669. thisSubtask := t.Files[subtaskIndex]
  3670. thisSubtask.Status = FsTask_Ongoing
  3671. if bytesTotal > 0 && bytesTotal != thisSubtask.Size {
  3672. //Keep the operation total in step with the corrected file size
  3673. t.TotalSize += bytesTotal - thisSubtask.Size
  3674. thisSubtask.Size = bytesTotal
  3675. }
  3676. if bytesDone > thisSubtask.Size {
  3677. bytesDone = thisSubtask.Size
  3678. }
  3679. thisSubtask.Done = bytesDone
  3680. if thisSubtask.Size > 0 {
  3681. thisSubtask.Progress = float64(bytesDone) / float64(thisSubtask.Size) * 100
  3682. }
  3683. }
  3684. t.DoneSize = sumFileOperationDoneSize(t)
  3685. recalculateFileOperationProgress(t)
  3686. sig := t.FileOperationSignal
  3687. progress := t.Progress
  3688. fileOprTaskLock.Unlock()
  3689. return sig, progress, nil
  3690. }
  3691. /*
  3692. recalculateFileOperationProgress works out the overall progress of a task from
  3693. the bytes of the files inside it, so copying a 1 KB text file next to a 4 GB
  3694. image no longer moves the bar half way when the text file lands.
  3695. Operations whose sizes could not be resolved fall back to weighting every source
  3696. file equally. The caller is expected to be holding fileOprTaskLock.
  3697. */
  3698. func recalculateFileOperationProgress(t *fileOperationTask) {
  3699. if t.TotalSize > 0 {
  3700. progress := float64(t.DoneSize) / float64(t.TotalSize) * 100
  3701. if progress > 100 {
  3702. progress = 100
  3703. }
  3704. t.Progress = progress
  3705. return
  3706. }
  3707. if len(t.Files) == 0 {
  3708. return
  3709. }
  3710. totalProgress := float64(0)
  3711. for _, thisSubtask := range t.Files {
  3712. totalProgress += thisSubtask.Progress
  3713. }
  3714. t.Progress = totalProgress / float64(len(t.Files))
  3715. }
  3716. // MarkFileOperationSubtaskEnded marks one source file inside a task as finished.
  3717. // Pass in an empty errmsg for a successful completion.
  3718. func MarkFileOperationSubtaskEnded(oprid string, subtaskIndex int, errmsg string) {
  3719. t, err := GetOngoingFileOperationByOprID(oprid)
  3720. if err != nil {
  3721. return
  3722. }
  3723. fileOprTaskLock.Lock()
  3724. defer fileOprTaskLock.Unlock()
  3725. if subtaskIndex < 0 || subtaskIndex >= len(t.Files) {
  3726. return
  3727. }
  3728. thisSubtask := t.Files[subtaskIndex]
  3729. if errmsg != "" {
  3730. thisSubtask.Status = FsTask_Error
  3731. thisSubtask.Error = errmsg
  3732. } else {
  3733. thisSubtask.Status = FsTask_Completed
  3734. thisSubtask.Progress = 100
  3735. thisSubtask.Done = thisSubtask.Size
  3736. }
  3737. t.DoneSize = sumFileOperationDoneSize(t)
  3738. recalculateFileOperationProgress(t)
  3739. }
  3740. // SetFileOperationTaskEnded closes off a task record with the given ending signal.
  3741. // The record is kept in the store so the front end can still render the result.
  3742. func SetFileOperationTaskEnded(oprid string, endingSignal int, errmsg string) {
  3743. t, err := GetOngoingFileOperationByOprID(oprid)
  3744. if err != nil {
  3745. return
  3746. }
  3747. fileOprTaskLock.Lock()
  3748. defer fileOprTaskLock.Unlock()
  3749. t.EndTime = time.Now().Unix()
  3750. t.Error = errmsg
  3751. switch {
  3752. case endingSignal == filesystem.FsOpr_Cancel || t.FileOperationSignal == filesystem.FsOpr_Cancel:
  3753. //A file operation stopped by the user reports the abort as an error on
  3754. //its way out. That is not a failure, so record it as a cancellation.
  3755. t.Status = FsTask_Cancelled
  3756. t.Error = ""
  3757. for _, thisSubtask := range t.Files {
  3758. if thisSubtask.Status == FsTask_Error {
  3759. thisSubtask.Status = FsTask_Cancelled
  3760. thisSubtask.Error = ""
  3761. }
  3762. }
  3763. case errmsg != "" || endingSignal == filesystem.FsOpr_Error:
  3764. t.Status = FsTask_Error
  3765. default:
  3766. t.Status = FsTask_Completed
  3767. t.Progress = 100
  3768. t.DoneSize = t.TotalSize
  3769. for _, thisSubtask := range t.Files {
  3770. if thisSubtask.Status == FsTask_Pending || thisSubtask.Status == FsTask_Ongoing {
  3771. thisSubtask.Status = FsTask_Completed
  3772. thisSubtask.Progress = 100
  3773. thisSubtask.Done = thisSubtask.Size
  3774. }
  3775. }
  3776. }
  3777. //Any file still marked as running at this point will never move again
  3778. for _, thisSubtask := range t.Files {
  3779. if thisSubtask.Status == FsTask_Pending || thisSubtask.Status == FsTask_Ongoing {
  3780. thisSubtask.Status = t.Status
  3781. }
  3782. }
  3783. }
  3784. // taskIsFinished checks if a task record has stopped running.
  3785. // The caller is expected to be holding fileOprTaskLock when required.
  3786. func taskIsFinished(t *fileOperationTask) bool {
  3787. return t.Status != FsTask_Ongoing && t.Status != FsTask_Pending
  3788. }
  3789. // sumFileOperationDoneSize sums up the transferred bytes of all the files in a task.
  3790. // The caller is expected to be holding fileOprTaskLock.
  3791. func sumFileOperationDoneSize(t *fileOperationTask) int64 {
  3792. total := int64(0)
  3793. for _, thisSubtask := range t.Files {
  3794. total += thisSubtask.Done
  3795. }
  3796. return total
  3797. }
  3798. // startFinishedFileOperationJanitor starts the background cleaner that tidies up
  3799. // the finished task records, see clearExpiredFileOperationRecords.
  3800. func startFinishedFileOperationJanitor() {
  3801. if !fileOprJanitorStarted.CompareAndSwap(false, true) {
  3802. return
  3803. }
  3804. go func() {
  3805. for {
  3806. time.Sleep(fileOprJanitorInterval)
  3807. clearExpiredFileOperationRecords()
  3808. }
  3809. }()
  3810. }
  3811. /*
  3812. clearExpiredFileOperationRecords drops the finished task records that are no
  3813. longer worth keeping.
  3814. An operation that finished without an error is cleared on its own shortly
  3815. after it ended, whether or not anyone was watching it: closing the browser
  3816. tab half way through a copy must not leave the record behind forever.
  3817. A failed operation is kept instead, so the user can still find out what went
  3818. wrong after reopening the desktop from this or from another machine. Only the
  3819. newest fileOprErrorRecordLimit failures of each user are kept.
  3820. */
  3821. func clearExpiredFileOperationRecords() {
  3822. expireTime := time.Now().Unix() - fileOprFinishedRecordTTL
  3823. errorRecords := map[string][]*fileOperationTask{}
  3824. fileOprTaskLock.Lock()
  3825. defer fileOprTaskLock.Unlock()
  3826. wsConnectionStore.Range(func(key, value interface{}) bool {
  3827. taskInfo := value.(*fileOperationTask)
  3828. if !taskIsFinished(taskInfo) || taskInfo.EndTime == 0 {
  3829. //Still running, leave it alone
  3830. return true
  3831. }
  3832. if taskInfo.Status == FsTask_Error {
  3833. errorRecords[taskInfo.Owner] = append(errorRecords[taskInfo.Owner], taskInfo)
  3834. return true
  3835. }
  3836. if taskInfo.EndTime < expireTime {
  3837. wsConnectionStore.Delete(key)
  3838. }
  3839. return true
  3840. })
  3841. //Trim the failure backlog of each user down to the newest few
  3842. for _, records := range errorRecords {
  3843. if len(records) <= fileOprErrorRecordLimit {
  3844. continue
  3845. }
  3846. sort.Slice(records, func(i, j int) bool {
  3847. return records[i].EndTime > records[j].EndTime
  3848. })
  3849. for _, expiredRecord := range records[fileOprErrorRecordLimit:] {
  3850. wsConnectionStore.Delete(expiredRecord.ID)
  3851. }
  3852. }
  3853. }