file_system.go 140 KB

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