From 7f7665880f13a7c0c539e390568f85e90d4ea9ee Mon Sep 17 00:00:00 2001 From: kodi Date: Sat, 14 Mar 2026 17:20:36 +0100 Subject: [PATCH] feature: duplicate 01 --- .../__pycache__/dependencies.cpython-313.pyc | Bin 6548 -> 6929 bytes .../app/__pycache__/main.cpython-313.pyc | Bin 2922 -> 3042 bytes .../__pycache__/tasks_runner.cpython-313.pyc | Bin 11582 -> 15575 bytes .../routes_duplicate.cpython-313.pyc | Bin 0 -> 1031 bytes .../api/__pycache__/schemas.cpython-313.pyc | Bin 10184 -> 10371 bytes webui/backend/app/api/routes_duplicate.py | 17 ++ webui/backend/app/api/schemas.py | 4 + .../history_repository.cpython-313.pyc | Bin 7824 -> 7835 bytes .../task_repository.cpython-313.pyc | Bin 19687 -> 19698 bytes webui/backend/app/db/history_repository.py | 2 +- webui/backend/app/db/task_repository.py | 2 +- webui/backend/app/dependencies.py | 10 + webui/backend/app/main.py | 2 + .../duplicate_task_service.cpython-313.pyc | Bin 0 -> 9506 bytes .../app/services/duplicate_task_service.py | 188 ++++++++++++++++ webui/backend/app/tasks_runner.py | 101 +++++++++ ... b00fba24-e752-405f-98a7-6f2c21fa3a1c.zip} | Bin 34381 -> 25946 bytes .../f9131f12-dc5f-4ddc-9e9a-1c4b977f4ea8.zip | Bin 62947 -> 0 bytes webui/backend/data/tasks.db | Bin 233472 -> 233472 bytes .../test_api_duplicate_golden.cpython-313.pyc | Bin 0 -> 17836 bytes .../test_api_history_golden.cpython-313.pyc | Bin 24738 -> 26390 bytes .../test_api_tasks_golden.cpython-313.pyc | Bin 13763 -> 14717 bytes .../tests/golden/test_api_duplicate_golden.py | 202 ++++++++++++++++++ .../tests/golden/test_api_history_golden.py | 22 +- .../tests/golden/test_api_tasks_golden.py | 24 +++ .../test_task_repository.cpython-313.pyc | Bin 6156 -> 6756 bytes .../tests/unit/test_task_repository.py | 12 ++ 27 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 webui/backend/app/api/__pycache__/routes_duplicate.cpython-313.pyc create mode 100644 webui/backend/app/api/routes_duplicate.py create mode 100644 webui/backend/app/services/__pycache__/duplicate_task_service.cpython-313.pyc create mode 100644 webui/backend/app/services/duplicate_task_service.py rename webui/backend/data/archive_tmp/{ae8c717c-02a9-4179-b7f1-307675d64ee3.zip => b00fba24-e752-405f-98a7-6f2c21fa3a1c.zip} (61%) delete mode 100644 webui/backend/data/archive_tmp/f9131f12-dc5f-4ddc-9e9a-1c4b977f4ea8.zip create mode 100644 webui/backend/tests/golden/__pycache__/test_api_duplicate_golden.cpython-313.pyc create mode 100644 webui/backend/tests/golden/test_api_duplicate_golden.py diff --git a/webui/backend/app/__pycache__/dependencies.cpython-313.pyc b/webui/backend/app/__pycache__/dependencies.cpython-313.pyc index 55cedd40fad106c6f30f82e2d37feb567667920d..855c7129f383580e0b618f44dd67634f96dd4e25 100644 GIT binary patch delta 2130 zcma)7OKcle6rG7Zc5LHc{29mjII)|g{^b8_5+^Y&gqEf)@G8`ns*Xvfsc90MJ9b1W zP{IZw7KrlL&~A`efLMU42&rNN5<;v%MQqTBB@zM^v0%%BdtaDjjEumN@0@w(-uvGD zy?JN+_LT1ruh%W%@3YrGEq@fc?Tg9R?hPpNE|XaFZ1%|<pr$8@6yGth0d|L2lRFaFlg5kfRpEG1lF{jaj&HmT2I{ zE!+g_Y2YT)su{=92 zP@c`Rv_SK0fn@|*V2dm((BgV7UD&U+1Y~#kv3hl*yjrSh``b2`EO+xg`xzyPneE`; z+Mmy-D961hjOe;nD_$>eX!?#`(>9BxwNkaF0rVqD3c3fyp7fKa^LtPC1A8~5_~*{c z{3|7$Nf0fg=s9o|B3HGNezOQ#wW61671)6+^8GyKNXcY)C3zg*(>8G816oOBNLXF* z`;IPU5LdI@XlmwaZg$Jrsf-{s%I`Xx?MCg`WVgB2F8>%((?k!ENb&X7cd^O{>GUEM zK%Svb&?I_!!WB`-W}eTuG8o*N>+2-NVmLw5D;wLI;i#5C+p0ANcg~$q=22~lzv#}R z*~jk9Ug~?>6QyxWTnP2wYxlkY-GJ+`-X*+}oA6fqMtjHQ+v(Cjz zRWIrqY?oIxSVHQl{gaP$$??a@l>-X+fC&_a1L|8_*a=g7$D11$-bFe@qQn+rTWS{+s`P@o7Y91w#MB5><>iC27qZor7LBQ}rw=)bOuUk$=IF_|cQ# zfBI7jT*L()^uL^gzqgCx!hat0L7>Bb2^C)_~mbf z;>rp#Z}U$=$z6&IlY$+o)#|VXK77aMO85Y;1$!)*?iN#rE({aXEns3AaTCv4aKM7Y zlC*oM)Mg>5k4Hj_^V>&Q-Ndj`EC}cF=A{A0Jj#URC{a zjPI!fay$P}?cNm^-cgywh>C_24J7I$Mqi9MT}I;e02*+h(FGbipfLgJYoNXa>M5Xh tfE5y!8sn?cn%}If)i<;ScuB&7(YHij=BHy5^78w)VluC{ci8Du^%o(5mo5MR delta 2007 zcma)7O>7%Q6!ykm+i@B@abkPDvE9^3FzY&v6DMh%nh>G{Xn;T&RaHr8Y)gDZ_GNoZVv3>`J4IP`*!C2 ztZ&WUE`=ZV_J%z8EL{4swi>+^R-_m2=VWRBqBrYdah51c>^M_Y&o$o+x#0uH9bADW zU0lJzjWgB7jXSs#Y{0REbR&`I=D%eadDFlZi*dsaZ{|shG_>% zY?>VtXqwHitUxm>Y?h4(ILl75QGrgfQ*2D2Q>@IM5~$4PSWcihcA7mc&}mj-nn0D! zy!On454-v#{)P9kl;-z*XXIm;LzVyKyIjdqHiywz@lD-azgpYUjXlHE>+97k)ooJ; z7(Rs zWBhslh(v*JEah;mc_Dy55G13K>6A*4)a7RLdc6uRxdM9Uc`)#wT-GVqeW)D28%W6$ zNEP|FhlWuTK6wgW3#KEJNX-&GPA1Dg4t`Qj5~N#+6agv2sG!LVqp>0fiP&j0Fqx!L z;hWugOu|Rq-(=~|RseLPxwWfX{_QI04bznb{!ZwqTtT;G{#mGkVLyiI^V7J--1XrP zP%T?0=y-Wlqz;;I8~VDT!)|RuhXr(giQnw`wD>HAkq*#MX+9Vrd?40&9e(zRDlenY zs}Hu1q$PEUqWmCaN{B5#w5~YA@AMW*LP#V>q)n)aH&8o)OolGyOc?ivbMgvqp665H z`zaFDxA>_Co|0f)mUqR+7S0i`k+J}G_$Xi@UhJM0_ox>^gI5H%kMaOG=sPC(D zuOZT8i2aX9bg^QT;d*mdZ#BD}R>zR7D$88Ei}mwlSp!zrMf69!b&||2z@nMWx@5uk~1?VtG;NlLq;F z36=Y)PO{-mcma;zm(v=DLF~uN;0!AK0^lu6{8~hO0vy!eU~6&QjUf;qgEyp)TS6&PZa zl^9GxYJmXAV^(5_Q88tN@PoW3^D#Tf{#6-=_qKy#4Y4q?YA28#rXS~3CstQ{0T z*@9V@)d0v!o}9(3%V@ND2J?SLHp%kTq|(gEb6B+*wI`ooUCn4Yxr8m2(RlMEHep6a zg~@&F;vy-f1v#0?i6yDG1mMigciFEna`^)-0tIC8zR9~eRTu4Xi!ypS!H(QxyBHetriZX9PAxqN{>14VH0^2st> Os*_!~9!$32UIhSm=uy1@ diff --git a/webui/backend/app/__pycache__/tasks_runner.cpython-313.pyc b/webui/backend/app/__pycache__/tasks_runner.cpython-313.pyc index 11b107fc0ed12cc1a086d23e983d8b813c5852a8..bfa008a952db5f7c84e60dccc6599b27e2605db9 100644 GIT binary patch delta 4345 zcmai1du&_P89(Rx5x?!kb{sp2W4no4H_wtj(gs@6H%ZfWxp5nCTi11NTDOTEj%`W{ z$dxuRI)<9`r~|f1%O(vKkhX4t!O*nP3A9P@M@+lgTvDb9hQ=REg>+&d4ek4`<0NJ6 zIFWw-?)N_5Ip;ekzki@`$}(p*8wiZoo_IOYxpC4`lN*Jv3q&L$?IXREQ6JJmpTGp= zSH)DyubQdhSJkKK)iSN3qwcHd)iIqyYx?xP24+xbZJ)8%#7sU?Ljpvs@DNeAM!=Ya zF!NC%r2iPn5S5LwWHL2*;mBGW#S{(@MnwW)DTJ&M-J;4vj;oJT2<#EnNHdLCD{2%< zE7tK=>d)0<0FdZFqEjfnSSRWg$^eu>p^TzeG%Az{e3=x=3>IdEvVfjNp{!nV`#-T> zv?>}lu(BysC0JGNR>&$Ks}#yEHi&kGa)>_Bp-@iIFFF;fS`3KQ3gr@AtOjbaOg0S0 z(kBM8$)vgU6Q|T|-T?Knf-rjP)!q3kbKB5EDM~xGLb~k3x$NyvY(p|jHlB99|q-B>m z4D9Ds%#YGt{6ouT^DZPCk*x+!r}bqc>c}eT;W!)QbJjKV{^^kInn3%{+g-U7YL6q_ zk0!d3rwB3*ARI(kaLE?bLk4C>(Gdg-fNY7U#!p3u6HJO{QtT8PMu~(lf^Zb!7(xQz zc@486&39<5^f-UczK-Um-?zV|qDlUO%aeN$g<}Yq7E2>AfRII2jY`SI*s@+qX2&EJ z%ScKZEQ6|XgaL%x#9qwoc~dD1rFyGSU8zJr;ah6fLRzP5HqakWzg%-vZSO;=vIa_O zL-5Hb%}0W4FABbeAOfVd0KPLgm;;$g?c4ZNz{%gPchi6HkLvI3-j5PxVhD$wvc>Ss# zrKU+_EAzmbDw%||w_W-FU2nc8;BNT8Ys<3yyMc9rnohqScty4QVF(ejutzY`;+A<7 zKXKo%?;|{c@XZUx9)mUU@V^FqO%2e@*tgN_I{vKxkY_}_$>{!xk7SEd+=25C>9I1t3%ARO^REj0Di{@WDl}k1n zRVv-Q1zgDuOFG#t4RF5I?SU$$pX0C9jJ0^oE@)uSXeaNU>b_!cD>`Zlj@G=R^`*gA zgVP7z&ddx(uk1NI<7j=y5##;t<8&i`)xEkdL>#qq#0Wl8vDy>oSdgsDhHK~$|**~t#Nvxbl?n2v8tY9kfkOc`Ny zd3Pi$hL-b~$-yr*89c49$VpvTV<-FcX`!8@1v}YUM@Z^fzRK^|DK1#8nD-Nd*!?h@ zuzs)5?Vlm|+5*wgnduGh&_hM?_6IF^LYH zRZNUwV`t92C&yQCNx6YK#y+iNNZVMg@u4p|=y5gCEYj zJ-4KK5*Z=)s2a!!4e53dZVw4^MJ6>qoI075jU%bi(bUP&MDj%Xw9pnky;4kNSzPih zoLt{nHl6V$QyE`8mCVEvNniTZn4+)rXn4U{OFp7yeyCE`jO9*%2U$p^W%bF}=n01B zUp6vnG?q!g#gR$LhI9rFeEMV}b4)fQ(q(hyPL#E=@o_0Ryrg3sLkBu0JtVQTG%Rb= z$FiBksI0?#9Xw@Z{kDhW(s(A3O0x5WrF(Z0 z)48Q_m0xY}QY*hv&QNGvj{}?`MV-B%bLVyLtGe1^*T$K3yJpt*&*(fignE6|Y-{^3 z_rI|J(!SRRXZ9cXOY6bBEi{o+Pw8jtd{d4e^-k!E-cX^XC*RWZdhklieR=PF6UKKt zx~_Ql{aSZX{U_^X>&)K4!oA_2WPYW~d-qM~o~@iPeo)sqv9;)5nfI@n=qi$r^}Kb;@Mq0c+wRX)qW*f1&=ZK)MDw=EsQ*++be6jDU zsjFzJyrBW^m+3}Ww;ycU){uyr9PSA(#YPFwhedO#T=N>!zSkY8fG+NJfpY1+(&)IusO;zXmzt{gv z<5g1wN_3`k#1;WmM>d=NsCW%xsiXhqLxos_4Eb))$-qM!pCSx&0c{Y_9{XI z;Q+#K0Ya)J4`t-0vEp#*N^?l?FIEKUFSsvsA%|UrDIGzfuq~9H{kXC$if|Yq1`wM6 zcjJyWi<icr4!7PcSRN(P6KQs%sLCQ5<7CU^5ctUN?_a(8pU zxdp8z5pbK7{+mkhp5_iMw!QS<_{rvBeyw?AW4or~<`L@CRNdT4wVLXiHbFB)Ki5@g zEDUxyVfY+^|D6oc=twq`Wl}WCa0S`32va=TGD+X$pSIL=zXc4~W+WW~xS^tyUMGPM ii2gldevjC$lbXMiP1ni#>txgC+GAR3`HUbcLH-N-<@c!o delta 1725 zcmah}No<=%6rTAv?^|mV+i4wVDewza5s5mkOPYcdHL-&en3gWIantxWO6nyY+t8d+ zBo15(4Ub5uf(z;;CqREIZU}Mhi3=c0MnVXQMMx+WDpVjbZyaJvB*KT!-@Nzc?el-V z`q_E^eV@-w#Bb~3-KE*X>;CBc1##LSj4+xbS;}b=xsp@3BL3B!n$@@_Wm-uj_ zOu5D$VH$F-vp%LvX0RxX)4|CY`V$(b&^RS?u_$v%=4Nr`mdqnOcqH=*JFjFuVds;~ z51-KROoAi`i!r|x1%z2ZvY;>vN)}@MEF{@Jc7W}ZY(Lx2!>=gj!It~fLjB5%&1%)+ zKY8E@Mz&A>LH^AZn-JbnrRf6w(nCYeI56 zc-MnAE!DcV$mgIKIzngdTcNKMIt^FDeQQ_Hyo_)f?>IZI&Y<8d!a0N;pM1yMbaNl7 z<`Mh?TK>h_%4(sw#I40fjj!@cs98X`jIfAMM6d*G>O6=X9FYJmL0@EyHtpHSJ(Vs) ze=NRMLFEzxrp0RrRRN~IrIxK~H+IWqRht!yFElKf246wb0zw{Pm)PCRHocuJWcx!cNQ7Ok>x5`0p0L z=fkmqOL%RsiSw)Q$MBf)kl3RmpQ`zWur>YsHN4_Okc)H;8EzAQ6X6Yn{~se?6_bj? z+~Y}4go+z$cWRTj>SMY9f2T}iQfTTXX~pEBaab!0FKaybt}j1eo5IyHF@K8%=H%3S5P)W9#|%Q7cXG zowS_KRBFX$**eZY6h|>IQpa}wrcz4VWaxVmY?J5@WTH)uw#mc;V^XL7ErKYW`~w0# BWMu#V diff --git a/webui/backend/app/api/__pycache__/routes_duplicate.cpython-313.pyc b/webui/backend/app/api/__pycache__/routes_duplicate.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0731e7b46e592eefa930abde3e4d4ddae708f721 GIT binary patch literal 1031 zcmZ8f&rcIU6rS1L?zY_)6fhF1SOHC|q`gQC31E!GAFV;tm0Od|(oSKawCkH)Q4$i= zgZu*~o;}IQlNbL2jqH>3&As(8tKSvQB3@ zi7>DlOAj0iD@}dfs@3YQ<=XWcPfLE}_TvYIy5}1upJjGDEVlRpy%I?q1WyJtk-H%r1%ONRLG%8*Tr#nyuJX#xbrsz$*6h z?P#_~ar{+ODft7y+J~5%^)jpaI>(YdZkEt0;a&8Gg6K6Nv^9rdk|gmimT94*OK%r~ zWm4$R>x2wCOQ*<9xkA&@5|$KlEO$*PaIO^e`d@P}h6Q!bWYSM>u@%qG zu2`iFlrn2M4o*7@LScSNmT_MAgT1EB;7}MriWp9Q372ECOPWy<8~(1R4)oNAt&cnV zI}JV6(B~V<{9i8mel>Q1E~QmCix|X%gdwp+aPd`Ot_L;H&`1yZQPZq==wPM^vWROH z3ng`I5JV6XuJfI$GgviFJHtzBY}4XxYC+_W_HQW%&xO@WHscT$;p_e3t}>a^KKtKC zx6Jn2Q=B??0|oX;3#kYU3&QXmUcXmm3m`NRx60RWY08w+Lo#tht{##5hve2FdH5?b z^e%M}ncP*IDv`o(5-$^b#m2R{FO$2823~3_b=18Y;_kj$uhnlTw7p zdcy1jql-dHuo{LH6_r606d^>|O&8q+U1fC9Rqy{*b>7oj2aJtCDiX>9I_Za3Pq`QT*NwWm;OU~8>`Kwa zN8Zk!lIdePWulLBb|avh?&oX}-7;V!t@c$fJP9LkR(|Ik68*{tXW)HWz;Lq*MFa`J^fS-nNy3h{XVa~S+1eOuHWJuQfnbJ6 zQdK~sl|hXXx!d>E#1H>XHYIn|rk+&~PT+b+pi)IcWdfJ~Do*af^7*@cd~}(x<(atoi?Pav{WzAr{OLHjsa-G(1Dx433Q>3Lju!uRS%1C z%#PCXv$faKb~LLkW?H*8#pA8*5u>%zini}D`gwRfGXQbM0Kg?q@Bz2-ea^SArLtp3 z{#~pAILvejd668>Q(ICS?{aU23vZOnAdrjYvX5h#koaeuI$hJ;^6q97gtZ<-@sPR0 zb666M>taURZmPV+hBq=qDJ0;62GNz0Uo0x=(>4ko_xeiPF4&`K1E zh&qWq=tUBNVVIhc5UnaQbRm>Ui&?ZVT16|NMfZP3w8sbEk8{sC_kRC#@44T_L+`5X zKkar)ih4fO&9s$Vwflv6GA}(ft#w~P`PBZ@yEYM@`sR2pv<8(UD}Y8I0yF{5G@0cy z)xfNwPfkPBP$;*OMl;`=qHsp3)m1E_%IT+Pt}CLR%G_;g{KQ>i>Oq3!1-f8%QM#v0 zba8IfbJ=9}gb3!65j}H1OO#=uF&^@0f1`wE9@}-LGGw_}T&H;Tuze1HfMnC+!)YAFd?!bBF zS+N+gh>S&B8X_@y0eLosf(+_+YT^=I*9zAa3?nv7U$s#&%nI^@N9lWZrj^S}eu2z^ zN)MY{Uq7bY0B&*-BQQs3C{QX!c!{-OF^z3?yPqOpR#@2*ix}+r)7I|u;C zFraKGW!au_v2xkw7(5_d99~z`v}0NgtCHKjQL!9FPErIctRZhv#NqNkfC*rpybbVz z57xDni#aOOi!5V^0CRL&*TfiypX!J8*9e3G-ft8jb>I+OqEM_RffGuZTrE>*OG2`o}R7K%N`|Sl&2)AB!9b`K3+x~COCO9^r(Ch0hai8bNshqT)|ajUjxu2e>8>-jiGIB??2hxt(Hm50T#)% ztx-&FD4yQt@qbbNxRc8+SG9M`Q-4$);n;~Xd&00{W1>Hi`VV_u~EcJI(i&?unKNkK76-IUNP_K>~1;P5iOOk)KI9`cqme)7PjvcWX?bR8)%P1G5`Po diff --git a/webui/backend/app/api/routes_duplicate.py b/webui/backend/app/api/routes_duplicate.py new file mode 100644 index 0000000..72e680b --- /dev/null +++ b/webui/backend/app/api/routes_duplicate.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from fastapi import APIRouter, Depends + +from backend.app.api.schemas import DuplicateRequest, TaskCreateResponse +from backend.app.dependencies import get_duplicate_task_service +from backend.app.services.duplicate_task_service import DuplicateTaskService + +router = APIRouter(prefix="/files") + + +@router.post("/duplicate", response_model=TaskCreateResponse, status_code=202) +async def duplicate_paths( + request: DuplicateRequest, + service: DuplicateTaskService = Depends(get_duplicate_task_service), +) -> TaskCreateResponse: + return service.create_duplicate_task(paths=request.paths) diff --git a/webui/backend/app/api/schemas.py b/webui/backend/app/api/schemas.py index 6db03cd..afb46c4 100644 --- a/webui/backend/app/api/schemas.py +++ b/webui/backend/app/api/schemas.py @@ -172,6 +172,10 @@ class CopyRequest(BaseModel): destination_base: str | None = None +class DuplicateRequest(BaseModel): + paths: list[str] + + class TaskCreateResponse(BaseModel): task_id: str status: str diff --git a/webui/backend/app/db/__pycache__/history_repository.cpython-313.pyc b/webui/backend/app/db/__pycache__/history_repository.cpython-313.pyc index c232b7698c9d0205562915052532e2caf1db9c4d..581c374855fd7bc218494a5cccfcc92532187e5b 100644 GIT binary patch delta 1103 zcmZuw&rcIU6lS)izl#`yt+e2hs<17qSr4jL1K@6BS1iF^3=?KkiH-n@CUyX80K^g%kE7{KSltM~5E zyR^wzOwO`vNfmcGZO^rVl8U$59IC=?HC4nHRnhU~+i1anY56g_5;rL& zWdoPx5z(s;7ITt@3w^6@ntcBEeMzD+W{rrU^;}$MpEAhH7rp z_3PZJJg#~j4l79dF$DAI0kd07){TvxrHa+Ku7rTG64l zQekL4@nMx*BRyC2{F=ULoR~#M8Lk7@F@Ta>8=hEzDU7X9zXcWA<^lP*Ju)64socXhAbo4FZE-c4$evUre~?6bGY;?;R`Tdm>aDohx14P z5M1a*dg_|fS4T%yZa}7aeHAHd0n9xIwyLzx4lH`(8c@RhYJ9u`dp} zgkhbykc$(d)#nM%^~ENg(|ugLixif_zV#{y(iq3$Fs#b`Ty|7zlO#Lp`ES;|Jjj)n zC&=dp!7YM21osGN#tfgc5Y0M?lO{FLLrI#1)M_ZLfd&dK6#4;zBT_DKL6ll1c8Dd%ZpRzGgp?a+ zBp>?+kT{_Ti6cLt5=h+kA8;)QBo4sLIyfL}<4Ac(r|=9OnpD2v!L$AsBfeaX3wImEbQvF2#|K>$_psvCT)8*LPqOonan9 zAAK-8yk6`otwd7fI}UGmEzg5B%rjKEgUEVr&*fsGXFV~cDWyaPg2y3jk*b=gqUJa4 zhEiHWk5F72uAmVgZ&Y%-Zd=#s^KulBldDiySioXCZHp39w!BG(Rl-)VEGMhB&ZlI*7>E10P3 zFiYIks8If(ODYo?+ii1$#bqA$(S;2EdevkTeAA`5ih1=B- zDGDJ1HehULU@JPPmMfB5B-*IvKUvGs&+7U>C-OCdn*=R_I|Qhlf(!u-gP5cT=C*qd z?*_IUWBG2xv}wi>)8ZW52Yv2@rU?NN$9GktC4q*&u!$G^BK1{Kvi0G++9GSnL<$?4 zD41rakIi&Yg=P(Q2rfkF`jR2vY?Ibf@ZU{Dexn!Ieb3o~L3Fo%rnK`hRsODSE6iqx JDfuT)^czbg?Cbyl diff --git a/webui/backend/app/db/__pycache__/task_repository.cpython-313.pyc b/webui/backend/app/db/__pycache__/task_repository.cpython-313.pyc index da7a6d7d1a58419124b69b6eb517d694bedfa64f..11d23ee05d2430a37889fad66a775ee4a8d31809 100644 GIT binary patch delta 2631 zcmb_eO>9(E6z+TTIvr+Out3|PKeV(dnb%Ti1k1FY(soLM{IqJNA!5j5m>J5z%)Iu# zH)$YZ!JhSy=U_m_JdDEF`3$k$*ANQSm z&i9>r?zyM@c1!u|mQwR~O^v^Te#_UEQi(5WCKW}Krw=L=h&q_s%AZEs z$3;8Ybgu|we?OoPFa$V2;CQB!wq7Zq?r4y6wU{|dMyryb4L{M7zrS%x9ejZdx>lkP z!(I3gWQm~+;;`1iI%)rJU| zizNDnf~fp{q-EO<>~Y>33aU^>?!o#K>haZ~j*mp*W-e>%zOu|4!O;Y01{5hBZ#*X~ zQ&_$-YGS?{RMpL;d{(HC&lXN)jek{BK&^R=IEt$B$Hri=9b!-Nf2n0m-G2)HRZq;a z7$^*KvZ@I9(0uIgTFR<{Q?wo_}w zgq=DYi`#~68i`cA)GTs#-_B6m3ZvVstM8ijoK3kID=BQL3^e9{*mqs&xmz-N5|ZOX!EU}B z&!jo$1n5~AsF*1(E~swdSx}{Tlw5u=G^O;)Uxyz1LS&-(b6fs?Ak^>)eON0_(86(@ zM)~PbLq0k@qbP^uosq*$Jk%B{78JMS3ON<-aYT+Dtjq5j-Kl83WJTx5bToW~&-@k` z-v+z`Xd@uyr)`PP;Pg3xRVHv%{t-=h!5y!V_lLL2^w_=U*b_FlnIe?LEA#l)6@jYg zs^Gpu$Li{Yz#jy?+8y?=xf7pP82jAWoYy12Ws9~q+9u3w(w)oP^j?v)b0X_#+>M$z zjG(UoO9YMv_j&3tJ{r@)eklO=0vn~oHh(&0*{@^clwLW3pT4myE=X~Y{Hg3pnAirD zI{==Nyy8*GE1s8&*j<_jeodfzKf7J7%ZinWTNrOo)++ig00qV(vS2hT-lTG20fYGdVkH8HPIsN{_n}Y@PWpU(`K3 z21KWPaJ0dD3+4g&_-M~?7)$gL5HiE+k>U6v88eYfCl82w^8B$b MB_cmMHo+t8U$FKY!TlcuDlAxYzW9EVSej8Q^L0YOY^JJd>Iw{OOZ zR7G+rl@QQuct|vYN~}QHft>P_fCY$MSs+$}#0m){kPxsyWP!Nnj(>5&r(PUC-*?YB zcjn!9<=-}xA2yW6-y0j<`{v}PJ{yT6}MmMD{Q*gZ5rhZsU|5O5!06fj0$+MY`p zTAhHUX@`epz;crm)awX3@ezH6Z*M!Th6X60*((xaoC0EkVAIYzB8&jYB0`|s35o(s zcl@G^RLotADI;-iVR?B69Zb_Mva9+o{%g$fBDM=^~RvHkUCpS5@UcXdD2v13Cd*lOvH6dRFKziWG(MR=#ReRd1!9k`(G` z=2B|%z3m>g@d?r>yHaXfTUW`aR{tsglsfb@@|}BDStg2kO46MK#p{%oNc#mh|aan zST>WgW+wNfL!^wH$e8wII$=Vu;AzDx9$CENxmCa}#Krprnk^QKY3?fD|0L$A+_Ty2vSuf0j9y9@ zVq7!ovF!i7kcz!Z3a%=Jz1%tbLlo;pX1-{~!-{QrV)3d21@IHzdMPaaKeWmJmc+U8 z+LIL=r4vQLd=S-Vz%9)W#4XP&?t85A775L>t6?lDJCC_-Z}I-#Gi6=hbT(7vSM>hK zl8MmQMNf$^n_aCkDQ3c33z#nX^+N;sqx3^e0ZsuP0n7oO1jGRez#j diff --git a/webui/backend/app/db/history_repository.py b/webui/backend/app/db/history_repository.py index 209ef8f..1e0acd4 100644 --- a/webui/backend/app/db/history_repository.py +++ b/webui/backend/app/db/history_repository.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from pathlib import Path VALID_HISTORY_STATUSES = {"queued", "completed", "failed", "requested", "ready", "preflight_failed", "cancelled"} -VALID_HISTORY_OPERATIONS = {"mkdir", "rename", "delete", "copy", "move", "upload", "download"} +VALID_HISTORY_OPERATIONS = {"mkdir", "rename", "delete", "copy", "move", "upload", "download", "duplicate"} class HistoryRepository: diff --git a/webui/backend/app/db/task_repository.py b/webui/backend/app/db/task_repository.py index 65c5372..27a8b6e 100644 --- a/webui/backend/app/db/task_repository.py +++ b/webui/backend/app/db/task_repository.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from pathlib import Path VALID_STATUSES = {"queued", "running", "completed", "failed", "requested", "preparing", "ready", "cancelled"} -VALID_OPERATIONS = {"copy", "move", "download"} +VALID_OPERATIONS = {"copy", "move", "download", "duplicate"} TASK_MIGRATION_COLUMNS: dict[str, str] = { "operation": "TEXT NOT NULL DEFAULT 'copy'", "status": "TEXT NOT NULL DEFAULT 'queued'", diff --git a/webui/backend/app/dependencies.py b/webui/backend/app/dependencies.py index c0a8ac6..061b3d6 100644 --- a/webui/backend/app/dependencies.py +++ b/webui/backend/app/dependencies.py @@ -14,6 +14,7 @@ from backend.app.services.bookmark_service import BookmarkService from backend.app.services.browse_service import BrowseService from backend.app.services.copy_task_service import CopyTaskService from backend.app.services.archive_download_task_service import ArchiveDownloadTaskService +from backend.app.services.duplicate_task_service import DuplicateTaskService from backend.app.services.file_ops_service import FileOpsService from backend.app.services.history_service import HistoryService from backend.app.services.move_task_service import MoveTaskService @@ -112,6 +113,15 @@ async def get_copy_task_service() -> CopyTaskService: ) +async def get_duplicate_task_service() -> DuplicateTaskService: + return DuplicateTaskService( + path_guard=get_path_guard(), + repository=get_task_repository(), + runner=get_task_runner(), + history_repository=get_history_repository(), + ) + + async def get_move_task_service() -> MoveTaskService: return MoveTaskService( path_guard=get_path_guard(), diff --git a/webui/backend/app/main.py b/webui/backend/app/main.py index e07a32e..182bb10 100644 --- a/webui/backend/app/main.py +++ b/webui/backend/app/main.py @@ -10,6 +10,7 @@ from backend.app.api.errors import AppError from backend.app.api.routes_bookmarks import router as bookmarks_router from backend.app.api.routes_browse import router as browse_router from backend.app.api.routes_copy import router as copy_router +from backend.app.api.routes_duplicate import router as duplicate_router from backend.app.api.routes_files import router as files_router from backend.app.api.routes_history import router as history_router from backend.app.api.routes_move import router as move_router @@ -30,6 +31,7 @@ app.mount("/ui", StaticFiles(directory=str(UI_DIR), html=True), name="ui") app.include_router(browse_router, prefix="/api") app.include_router(files_router, prefix="/api") app.include_router(copy_router, prefix="/api") +app.include_router(duplicate_router, prefix="/api") app.include_router(move_router, prefix="/api") app.include_router(search_router, prefix="/api") app.include_router(settings_router, prefix="/api") diff --git a/webui/backend/app/services/__pycache__/duplicate_task_service.cpython-313.pyc b/webui/backend/app/services/__pycache__/duplicate_task_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33bfdbad1372ba91055258d0f51fd3b445a64b4b GIT binary patch literal 9506 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 literal 0 HcmV?d00001 diff --git a/webui/backend/app/services/duplicate_task_service.py b/webui/backend/app/services/duplicate_task_service.py new file mode 100644 index 0000000..2442e1e --- /dev/null +++ b/webui/backend/app/services/duplicate_task_service.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +import os +import uuid +from datetime import datetime, timezone +from pathlib import Path + +from backend.app.api.errors import AppError +from backend.app.api.schemas import TaskCreateResponse +from backend.app.db.history_repository import HistoryRepository +from backend.app.db.task_repository import TaskRepository +from backend.app.security.path_guard import PathGuard, ResolvedPath +from backend.app.tasks_runner import TaskRunner + + +class DuplicateTaskService: + def __init__(self, path_guard: PathGuard, repository: TaskRepository, runner: TaskRunner, history_repository: HistoryRepository | None = None): + self._path_guard = path_guard + self._repository = repository + self._runner = runner + self._history_repository = history_repository + + def create_duplicate_task(self, paths: list[str] | None) -> TaskCreateResponse: + if not paths: + raise AppError( + code="invalid_request", + message="At least 1 path is required", + status_code=400, + ) + try: + items: list[dict[str, str]] = [] + reserved_destinations: set[str] = set() + for input_path in paths: + item = self._build_duplicate_item(input_path, reserved_destinations) + if item is None: + continue + reserved_destinations.add(item["destination_absolute"]) + items.append(item) + + source_summary = self._source_summary(paths, items) + destination_summary = self._destination_summary(items) + task_id = str(uuid.uuid4()) + task = self._repository.create_task( + operation="duplicate", + source=source_summary, + destination=destination_summary, + task_id=task_id, + ) + self._record_history( + entry_id=task_id, + operation="duplicate", + status="queued", + source=source_summary, + destination=destination_summary, + ) + self._runner.enqueue_duplicate_batch( + task_id=task["id"], + items=[ + { + "source": item["source_absolute"], + "destination": item["destination_absolute"], + "kind": item["kind"], + } + for item in items + ], + ) + return TaskCreateResponse(task_id=task["id"], status=task["status"]) + except AppError as exc: + self._record_history( + operation="duplicate", + status="failed", + source=paths[0] if len(paths) == 1 else f"{len(paths)} items", + destination="same directory", + error_code=exc.code, + error_message=exc.message, + finished_at=self._now_iso(), + ) + raise + + def _build_duplicate_item(self, source: str, reserved_destinations: set[str]) -> dict[str, str] | None: + resolved_source = self._path_guard.resolve_existing_path(source) + _, _, lexical_source, _ = self._path_guard.resolve_lexical_path(source) + if self._should_skip_name(lexical_source.name): + return None + if lexical_source.is_symlink(): + raise AppError( + code="type_conflict", + message="Source must not be a symlink", + status_code=409, + details={"path": source}, + ) + + source_is_file = resolved_source.absolute.is_file() + source_is_directory = resolved_source.absolute.is_dir() + if not source_is_file and not source_is_directory: + raise AppError( + code="type_conflict", + message="Unsupported source path type", + status_code=409, + details={"path": source}, + ) + + if source_is_directory: + self._validate_directory_tree(resolved_source) + + destination_absolute = self._next_duplicate_destination(resolved_source.absolute, reserved_destinations) + destination_relative = self._path_guard.entry_relative_path( + resolved_source.alias, + destination_absolute, + display_style=resolved_source.display_style, + ) + + return { + "source_relative": resolved_source.relative, + "destination_relative": destination_relative, + "source_absolute": str(resolved_source.absolute), + "destination_absolute": str(destination_absolute), + "kind": "directory" if source_is_directory else "file", + } + + def _validate_directory_tree(self, resolved_source: ResolvedPath) -> None: + for root, dirnames, filenames in os.walk(resolved_source.absolute, followlinks=False): + dirnames[:] = [name for name in dirnames if not self._should_skip_name(name)] + root_path = Path(root) + for name in [*dirnames, *filenames]: + if self._should_skip_name(name): + continue + entry = root_path / name + if entry.is_symlink(): + raise AppError( + code="type_conflict", + message="Source directory must not contain symlinks", + status_code=409, + details={"path": resolved_source.relative}, + ) + + @classmethod + def _next_duplicate_destination(cls, source: Path, reserved_destinations: set[str]) -> Path: + parent = source.parent + candidate_index = 1 + while True: + candidate_name = cls._duplicate_name(source.name, source.is_file(), candidate_index) + candidate = parent / candidate_name + if not candidate.exists() and str(candidate) not in reserved_destinations: + return candidate + candidate_index += 1 + + @classmethod + def _duplicate_name(cls, original_name: str, is_file: bool, index: int) -> str: + if not is_file: + suffix = "" + base_name = original_name + else: + suffixes = Path(original_name).suffixes + suffix = "".join(suffixes) + base_name = original_name[: -len(suffix)] if suffix else original_name + copy_suffix = " copy" if index == 1 else f" copy {index}" + return f"{base_name}{copy_suffix}{suffix}" + + @staticmethod + def _should_skip_name(name: str) -> bool: + return name.startswith("._") + + @staticmethod + def _source_summary(paths: list[str], items: list[dict[str, str]]) -> str: + if len(paths) == 1: + return paths[0] + if not items: + return "0 items" + return DuplicateTaskService._items_label(len(items)) + + @staticmethod + def _destination_summary(items: list[dict[str, str]]) -> str: + if len(items) == 1: + return items[0]["destination_relative"] + return "same directory" + + @staticmethod + def _items_label(count: int) -> str: + return "1 item" if count == 1 else f"{count} items" + + def _record_history(self, **kwargs) -> None: + if self._history_repository: + self._history_repository.create_entry(**kwargs) + + @staticmethod + def _now_iso() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") diff --git a/webui/backend/app/tasks_runner.py b/webui/backend/app/tasks_runner.py index e536593..f124391 100644 --- a/webui/backend/app/tasks_runner.py +++ b/webui/backend/app/tasks_runner.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os +import shutil import threading from pathlib import Path @@ -69,6 +71,14 @@ class TaskRunner: ) thread.start() + def enqueue_duplicate_batch(self, task_id: str, items: list[dict[str, str]]) -> None: + thread = threading.Thread( + target=self._run_duplicate_batch, + args=(task_id, items), + daemon=True, + ) + thread.start() + def enqueue_archive_prepare(self, worker) -> None: thread = threading.Thread( target=worker, @@ -322,6 +332,97 @@ class TaskRunner: ) self._update_history_completed(task_id) + def _run_duplicate_batch(self, task_id: str, items: list[dict[str, str]]) -> None: + total_items = len(items) + current_item = items[0]["source"] if items else None + self._repository.mark_running( + task_id=task_id, + done_items=0, + total_items=total_items, + current_item=current_item, + ) + + completed_items = 0 + for index, item in enumerate(items): + source = item["source"] + destination = item["destination"] + try: + if item["kind"] == "directory": + self._duplicate_directory(source=Path(source), destination=Path(destination)) + else: + self._filesystem.copy_file(source=source, destination=destination) + completed_items = index + 1 + next_item = items[index + 1]["source"] if index + 1 < total_items else source + self._repository.update_progress( + task_id=task_id, + done_items=completed_items, + total_items=total_items, + current_item=next_item, + ) + except OSError as exc: + self._cleanup_partial_duplicate(Path(destination)) + self._repository.mark_failed( + task_id=task_id, + error_code="io_error", + error_message=str(exc), + failed_item=source, + done_bytes=None, + total_bytes=None, + done_items=completed_items, + total_items=total_items, + ) + self._update_history_failed(task_id, str(exc)) + return + + self._repository.mark_completed( + task_id=task_id, + done_items=total_items, + total_items=total_items, + ) + self._update_history_completed(task_id) + + def _duplicate_directory(self, source: Path, destination: Path) -> None: + destination.mkdir() + copied_directories: list[tuple[Path, Path]] = [(source, destination)] + try: + for root, dirnames, filenames in os.walk(source, topdown=True, followlinks=False): + root_path = Path(root) + target_root = destination / root_path.relative_to(source) + dirnames[:] = [name for name in dirnames if not name.startswith("._")] + + for name in dirnames: + source_dir = root_path / name + if source_dir.is_symlink(): + raise OSError("Source directory must not contain symlinks") + target_dir = target_root / name + target_dir.mkdir() + copied_directories.append((source_dir, target_dir)) + + for name in filenames: + if name.startswith("._"): + continue + source_file = root_path / name + if source_file.is_symlink(): + raise OSError("Source directory must not contain symlinks") + self._filesystem.copy_file( + source=str(source_file), + destination=str(target_root / name), + ) + + for source_dir, target_dir in reversed(copied_directories): + shutil.copystat(source_dir, target_dir, follow_symlinks=False) + except Exception: + self._cleanup_partial_duplicate(destination) + raise + + def _cleanup_partial_duplicate(self, path: Path) -> None: + if not path.exists(): + return + if path.is_dir(): + shutil.rmtree(path) + return + path.unlink() + def _update_history_completed(self, task_id: str) -> None: if self._history_repository: self._history_repository.update_entry(entry_id=task_id, status="completed") diff --git a/webui/backend/data/archive_tmp/ae8c717c-02a9-4179-b7f1-307675d64ee3.zip b/webui/backend/data/archive_tmp/b00fba24-e752-405f-98a7-6f2c21fa3a1c.zip similarity index 61% rename from webui/backend/data/archive_tmp/ae8c717c-02a9-4179-b7f1-307675d64ee3.zip rename to webui/backend/data/archive_tmp/b00fba24-e752-405f-98a7-6f2c21fa3a1c.zip index ceeb345c37319e84f48aaff001d2d796e22c3ec6..4a3ca91f51572a6e7ef5bce31729fd493fd6b794 100644 GIT binary patch delta 3274 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 delta 11693 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 LNH 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 delta 5189 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 RUax99S=Dx0;3AL7{{RVN$_fAg diff --git a/webui/backend/tests/golden/__pycache__/test_api_tasks_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_api_tasks_golden.cpython-313.pyc index c7c125698e0aac975f83bf0c6d521c9290fed381..f5998071f16f983d9c07ee8e95183bc6bed487ff 100644 GIT binary patch delta 810 zcmZwEPfXKL90&0C)=t{48;ouA56EDk+c4~(K_w!h@g|5#@kT=GXmD&}^{t35BpMQ< zo?nb6M)0EXs*PUc=D~O*N@(!k!Ni-%h+Iv4?;U1hY?^OAzxT`g*S;NPv-e&&D1akBTAC>o-{8`5Y1e^WGE>0ZNE z5;OQxd=O8^!;1?b9lp>AIcB{)JilyLN=4JLcEuLaEcsxKNZqL+vUWa;EW3uBjC0*- zsQ(Q16>Ihl+Cg(<4yw>trm;jK9l-qqg+6qexS?fDyOgDS)>;1D8DAaJ@%CW_rf@zP zi?xn1SFBa-ise}IyC?CU7{PsdZ00{*iTuy{8?whO;0f}Mplu177ajDv`n^c67fE?) z!Bd9mt7be!^;FH%GED`D`W}!)so8^{9UK$OTiVd`^B;v1_)GGl`+o3&4<=kQ`IUpm z-H9Wk9GrF=nMohC@VneTT*sSveR6}|^b`%I@s{X~(K?NZmd>p<$cJhDXvp!ody_IBc7ECUQ30s{N|(}6L8&0hkTOuF zl!>yWEL2D;gbGWAQ4y&KDk>FqVl1|)(Og34$_K!7NBhZ${cMYZE+XTf3S>mDFsslK z@X4{bs7#e8RQcXaAKHA|POn_!(`?QSsd-NoS8X>IJS}=!@|19Og^`Q%C;JduyjHYf zM4T5b5Vxf)7&lnHt2^tBU3SO2p7;l{gJQ@4 diff --git a/webui/backend/tests/golden/test_api_duplicate_golden.py b/webui/backend/tests/golden/test_api_duplicate_golden.py new file mode 100644 index 0000000..4c0feb2 --- /dev/null +++ b/webui/backend/tests/golden/test_api_duplicate_golden.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import asyncio +import sys +import tempfile +import time +import unittest +from pathlib import Path + +import httpx + +sys.path.insert(0, str(Path(__file__).resolve().parents[3])) + +from backend.app.dependencies import get_duplicate_task_service, get_task_service +from backend.app.db.task_repository import TaskRepository +from backend.app.fs.filesystem_adapter import FilesystemAdapter +from backend.app.main import app +from backend.app.security.path_guard import PathGuard +from backend.app.services.duplicate_task_service import DuplicateTaskService +from backend.app.services.task_service import TaskService +from backend.app.tasks_runner import TaskRunner + + +class FailOnSecondCopyFilesystemAdapter(FilesystemAdapter): + def __init__(self) -> None: + super().__init__() + self._copy_calls = 0 + + def copy_file(self, source: str, destination: str, on_progress: callable | None = None) -> None: + self._copy_calls += 1 + if self._copy_calls == 2: + raise OSError("forced duplicate failure") + super().copy_file(source=source, destination=destination, on_progress=on_progress) + + +class DuplicateApiGoldenTest(unittest.TestCase): + def setUp(self) -> None: + self.temp_dir = tempfile.TemporaryDirectory() + self.root = Path(self.temp_dir.name) / "root" + self.root.mkdir(parents=True, exist_ok=True) + self.repo = TaskRepository(str(Path(self.temp_dir.name) / "tasks.db")) + path_guard = PathGuard({"storage1": str(self.root), "storage2": str(self.root)}) + self._set_services(path_guard=path_guard, filesystem=FilesystemAdapter()) + + def tearDown(self) -> None: + app.dependency_overrides.clear() + self.temp_dir.cleanup() + + def _set_services(self, path_guard: PathGuard, filesystem: FilesystemAdapter) -> None: + runner = TaskRunner(repository=self.repo, filesystem=filesystem) + duplicate_service = DuplicateTaskService(path_guard=path_guard, repository=self.repo, runner=runner) + task_service = TaskService(repository=self.repo) + + async def _override_duplicate_service() -> DuplicateTaskService: + return duplicate_service + + async def _override_task_service() -> TaskService: + return task_service + + app.dependency_overrides[get_duplicate_task_service] = _override_duplicate_service + app.dependency_overrides[get_task_service] = _override_task_service + + def _request(self, method: str, url: str, payload: dict | None = None) -> httpx.Response: + async def _run() -> httpx.Response: + transport = httpx.ASGITransport(app=app) + async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: + if method == "POST": + return await client.post(url, json=payload) + return await client.get(url) + + return asyncio.run(_run()) + + def _wait_task(self, task_id: str, timeout_s: float = 2.0) -> dict: + deadline = time.time() + timeout_s + while time.time() < deadline: + response = self._request("GET", f"/api/tasks/{task_id}") + body = response.json() + if body["status"] in {"completed", "failed"}: + return body + time.sleep(0.02) + self.fail("task did not reach terminal state in time") + + def test_duplicate_single_file_success(self) -> None: + (self.root / "note.txt").write_text("hello", encoding="utf-8") + + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/note.txt"]}) + + self.assertEqual(response.status_code, 202) + detail = self._wait_task(response.json()["task_id"]) + self.assertEqual(detail["operation"], "duplicate") + self.assertEqual(detail["status"], "completed") + self.assertEqual(detail["done_items"], 1) + self.assertEqual(detail["total_items"], 1) + self.assertEqual(detail["source"], "storage1/note.txt") + self.assertEqual(detail["destination"], "storage1/note copy.txt") + self.assertEqual((self.root / "note copy.txt").read_text(encoding="utf-8"), "hello") + + def test_duplicate_single_directory_success(self) -> None: + (self.root / "Folder" / "nested").mkdir(parents=True) + (self.root / "Folder" / "alpha.txt").write_text("A", encoding="utf-8") + (self.root / "Folder" / "nested" / "beta.txt").write_text("B", encoding="utf-8") + + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/Folder"]}) + + self.assertEqual(response.status_code, 202) + detail = self._wait_task(response.json()["task_id"]) + self.assertEqual(detail["status"], "completed") + self.assertEqual(detail["done_items"], 1) + self.assertEqual(detail["total_items"], 1) + self.assertTrue((self.root / "Folder copy").is_dir()) + self.assertEqual((self.root / "Folder copy" / "alpha.txt").read_text(encoding="utf-8"), "A") + self.assertEqual((self.root / "Folder copy" / "nested" / "beta.txt").read_text(encoding="utf-8"), "B") + + def test_duplicate_multi_select_success(self) -> None: + (self.root / "a.txt").write_text("A", encoding="utf-8") + (self.root / "docs" / "nested").mkdir(parents=True) + (self.root / "docs" / "nested" / "b.txt").write_text("B", encoding="utf-8") + + response = self._request( + "POST", + "/api/files/duplicate", + {"paths": ["storage1/a.txt", "storage1/docs"]}, + ) + + self.assertEqual(response.status_code, 202) + detail = self._wait_task(response.json()["task_id"]) + self.assertEqual(detail["status"], "completed") + self.assertEqual(detail["done_items"], 2) + self.assertEqual(detail["total_items"], 2) + self.assertEqual(detail["source"], "2 items") + self.assertEqual(detail["destination"], "same directory") + self.assertEqual((self.root / "a copy.txt").read_text(encoding="utf-8"), "A") + self.assertEqual((self.root / "docs copy" / "nested" / "b.txt").read_text(encoding="utf-8"), "B") + + def test_duplicate_collision_resolution_for_files_and_directories(self) -> None: + (self.root / "report.txt").write_text("R", encoding="utf-8") + (self.root / "report copy.txt").write_text("existing", encoding="utf-8") + (self.root / "report copy 2.txt").write_text("existing", encoding="utf-8") + (self.root / "Album").mkdir() + (self.root / "Album copy").mkdir() + (self.root / "Album copy 2").mkdir() + + file_response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/report.txt"]}) + dir_response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/Album"]}) + + self.assertEqual(self._wait_task(file_response.json()["task_id"])["destination"], "storage1/report copy 3.txt") + self.assertEqual(self._wait_task(dir_response.json()["task_id"])["destination"], "storage1/Album copy 3") + self.assertTrue((self.root / "report copy 3.txt").exists()) + self.assertTrue((self.root / "Album copy 3").is_dir()) + + def test_duplicate_skips_top_level_macos_sidecar(self) -> None: + (self.root / "._note.txt").write_text("sidecar", encoding="utf-8") + + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/._note.txt"]}) + + self.assertEqual(response.status_code, 202) + detail = self._wait_task(response.json()["task_id"]) + self.assertEqual(detail["status"], "completed") + self.assertEqual(detail["done_items"], 0) + self.assertEqual(detail["total_items"], 0) + self.assertFalse((self.root / "._note copy.txt").exists()) + + def test_duplicate_skips_macos_sidecars_inside_directory_tree(self) -> None: + (self.root / "docs" / "nested").mkdir(parents=True) + (self.root / "docs" / "keep.txt").write_text("keep", encoding="utf-8") + (self.root / "docs" / "._skip.txt").write_text("skip", encoding="utf-8") + (self.root / "docs" / "nested" / "._nested.txt").write_text("skip", encoding="utf-8") + (self.root / "docs" / "nested" / "real.txt").write_text("real", encoding="utf-8") + + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/docs"]}) + + self.assertEqual(response.status_code, 202) + self._wait_task(response.json()["task_id"]) + self.assertTrue((self.root / "docs copy" / "keep.txt").exists()) + self.assertTrue((self.root / "docs copy" / "nested" / "real.txt").exists()) + self.assertFalse((self.root / "docs copy" / "._skip.txt").exists()) + self.assertFalse((self.root / "docs copy" / "nested" / "._nested.txt").exists()) + + def test_duplicate_failure_removes_partial_directory_artifact(self) -> None: + (self.root / "docs").mkdir() + (self.root / "docs" / "a.txt").write_text("A", encoding="utf-8") + (self.root / "docs" / "b.txt").write_text("B", encoding="utf-8") + path_guard = PathGuard({"storage1": str(self.root), "storage2": str(self.root)}) + self._set_services(path_guard=path_guard, filesystem=FailOnSecondCopyFilesystemAdapter()) + + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/docs"]}) + + self.assertEqual(response.status_code, 202) + detail = self._wait_task(response.json()["task_id"]) + self.assertEqual(detail["status"], "failed") + self.assertEqual(detail["error_code"], "io_error") + self.assertFalse((self.root / "docs copy").exists()) + + def test_duplicate_path_safety_stays_intact(self) -> None: + response = self._request("POST", "/api/files/duplicate", {"paths": ["storage1/../escape.txt"]}) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error"]["code"], "path_traversal_detected") + + +if __name__ == "__main__": + unittest.main() diff --git a/webui/backend/tests/golden/test_api_history_golden.py b/webui/backend/tests/golden/test_api_history_golden.py index 899bb98..0c28869 100644 --- a/webui/backend/tests/golden/test_api_history_golden.py +++ b/webui/backend/tests/golden/test_api_history_golden.py @@ -12,7 +12,7 @@ import httpx sys.path.insert(0, str(Path(__file__).resolve().parents[3])) -from backend.app.dependencies import get_archive_download_task_service, get_copy_task_service, get_file_ops_service, get_history_service, get_move_task_service, get_task_service +from backend.app.dependencies import get_archive_download_task_service, get_copy_task_service, get_duplicate_task_service, get_file_ops_service, get_history_service, get_move_task_service, get_task_service from backend.app.db.history_repository import HistoryRepository from backend.app.db.task_repository import TaskRepository from backend.app.fs.filesystem_adapter import FilesystemAdapter @@ -20,6 +20,7 @@ from backend.app.main import app from backend.app.security.path_guard import PathGuard from backend.app.services.archive_download_task_service import ArchiveDownloadTaskService from backend.app.services.copy_task_service import CopyTaskService +from backend.app.services.duplicate_task_service import DuplicateTaskService from backend.app.services.file_ops_service import FileOpsService from backend.app.services.history_service import HistoryService from backend.app.services.move_task_service import MoveTaskService @@ -77,6 +78,7 @@ class HistoryApiGoldenTest(unittest.TestCase): artifact_root=self.artifact_root, ) copy_service = CopyTaskService(path_guard=self.path_guard, repository=self.task_repo, runner=runner, history_repository=self.history_repo) + duplicate_service = DuplicateTaskService(path_guard=self.path_guard, repository=self.task_repo, runner=runner, history_repository=self.history_repo) move_service = MoveTaskService(path_guard=self.path_guard, repository=self.task_repo, runner=runner, history_repository=self.history_repo) task_service = TaskService(repository=self.task_repo) history_service = HistoryService(repository=self.history_repo) @@ -90,6 +92,9 @@ class HistoryApiGoldenTest(unittest.TestCase): async def _override_copy_service() -> CopyTaskService: return copy_service + async def _override_duplicate_service() -> DuplicateTaskService: + return duplicate_service + async def _override_move_service() -> MoveTaskService: return move_service @@ -102,6 +107,7 @@ class HistoryApiGoldenTest(unittest.TestCase): app.dependency_overrides[get_file_ops_service] = _override_file_ops_service app.dependency_overrides[get_archive_download_task_service] = _override_archive_service app.dependency_overrides[get_copy_task_service] = _override_copy_service + app.dependency_overrides[get_duplicate_task_service] = _override_duplicate_service app.dependency_overrides[get_move_task_service] = _override_move_service app.dependency_overrides[get_task_service] = _override_task_service app.dependency_overrides[get_history_service] = _override_history_service @@ -209,6 +215,20 @@ class HistoryApiGoldenTest(unittest.TestCase): self.assertEqual(history[0]['status'], 'failed') self.assertEqual(history[0]['error_code'], 'io_error') + def test_duplicate_completed_history_item(self) -> None: + (self.root1 / 'report.txt').write_text('hello', encoding='utf-8') + + response = self._request('POST', '/api/files/duplicate', {'paths': ['storage1/report.txt']}) + + self.assertEqual(response.status_code, 202) + self._wait_task(response.json()['task_id']) + + history = self._request('GET', '/api/history').json()['items'] + self.assertEqual(history[0]['operation'], 'duplicate') + self.assertEqual(history[0]['status'], 'completed') + self.assertEqual(history[0]['source'], 'storage1/report.txt') + self.assertEqual(history[0]['destination'], 'storage1/report copy.txt') + def test_single_file_download_writes_ready_history_item(self) -> None: (self.root1 / 'report.txt').write_text('hello download', encoding='utf-8') diff --git a/webui/backend/tests/golden/test_api_tasks_golden.py b/webui/backend/tests/golden/test_api_tasks_golden.py index ccaa171..180245a 100644 --- a/webui/backend/tests/golden/test_api_tasks_golden.py +++ b/webui/backend/tests/golden/test_api_tasks_golden.py @@ -263,6 +263,30 @@ class TasksApiGoldenTest(unittest.TestCase): self.assertEqual(body["status"], "ready") self.assertEqual(body["destination"], "docs.zip") + def test_get_task_detail_duplicate_completed(self) -> None: + self._insert_task( + task_id="task-duplicate", + operation="duplicate", + status="completed", + source="storage1/report.txt", + destination="storage1/report copy.txt", + created_at="2026-03-10T10:00:00Z", + started_at="2026-03-10T10:00:01Z", + finished_at="2026-03-10T10:00:03Z", + done_items=1, + total_items=1, + current_item="storage1/report.txt", + ) + + response = self._get("/api/tasks/task-duplicate") + + self.assertEqual(response.status_code, 200) + body = response.json() + self.assertEqual(body["operation"], "duplicate") + self.assertEqual(body["status"], "completed") + self.assertEqual(body["done_items"], 1) + self.assertEqual(body["total_items"], 1) + def test_get_task_detail_requested_archive_download(self) -> None: self._insert_task( task_id="task-download-requested", diff --git a/webui/backend/tests/unit/__pycache__/test_task_repository.cpython-313.pyc b/webui/backend/tests/unit/__pycache__/test_task_repository.cpython-313.pyc index 8de61b3eb3dc649019f9b367c481c44e516a0370..433237f4a7ef5315603d5ceaee9bef78b4651f43 100644 GIT binary patch delta 713 zcmZ9I&ubGw6o6;;cQ(7xX4B2KNZF!kx=|Wa8>m=>Qp7`xJq)EDge4?Pnl??sWTR~_ zRRponLuC--K@jv%j}rVRyag-?IVpISKo`6PXBHIc9KQMH&6_uG=J@8%h1j{StALJY zM_($3W1nLuaJxj8ufZdLAp)2~5c5bw+$7i*ut1FuO{6A-CQ%bZlc`CeDb(c9BGi=7 zRBDmXqDaMBIqK-05sYi9MDODPfahO{Eo4;$5r=pr+~&!;n!O~7bUP0z=%{hzotWe~ zB$4T;LEejrLk`I-11WhfOaV4111Iue5Gl19)k?A8dk03-uj4}5%Vn|GsAGT5-}4Ww z3qiYBZ|t+3PdvUxOW_%YG{aSfFqUFAMd1i|jQPeg;|jwK7Ur?R{2s#`MU%b^*tU%m zbRML9uj#u*?9r~=fBm_Bp}FH$nr@+5tv~TfgGI6|TV$ju_y0cOVhUoV%M?#RKW@Hp zUOByZx*JdT&FQ`|+qdS2GB9R`Krqyo@=zluy5)RVw4P#h6|0?S?{p_Jr>ozT#r}W6 zt+!LB)|oQj8R;!fa@iHfSi~I69n2)fki}BO z0%S3RS*$=73z)?QWU+!->_8S9n8g8Pv4dHhKo$p>#TCq1#GTHi$+MY*Nr-22mOwHS zWANs4Lh;Oen#@HiKue3%K!o~ceK9pg#-zz%;_n!FCc8@bvju>}qbARnFlAJqd{shm z@^1-4#^A}ilAWq?K&e|Sxrv#1Madvl;vhl None: + created = self.repo.create_task( + operation="duplicate", + source="storage1/report.txt", + destination="storage1/report copy.txt", + ) + + task = self.repo.get_task(created["id"]) + + self.assertEqual(task["operation"], "duplicate") + self.assertEqual(task["status"], "queued") + def test_mark_cancelled_transitions_requested_download_task(self) -> None: created = self.repo.create_task( operation="download",