From efa07b05c6429814b8f70aba61af5c3bef669e7e Mon Sep 17 00:00:00 2001 From: trav Date: Mon, 14 Sep 2026 13:25:15 -0700 Subject: [PATCH] v0.20.4: the song that wasn't there trav couldn't play the first track of his playlist. Shuffle off gave him song two; shuffle on gave him a random one. The player was right both times: library.json named a song whose file was not on the device, and skipping an unplayable file advances one slot. Tapping any song that *is* there plays exactly that song, shuffle or not. The file was missing because a gvfs-MTP mount can hold a phantom directory -- one it lists happily while the device has no such folder. Every write into it fails EIO, and mkdir(exist_ok=True) sees the phantom and does nothing, so it never heals; only remounting clears it. That folder was new because Round 52's retag moved the file under a new artist. So a copy that raises OSError now costs that song, not the sync, and the song is left out of the m3u and library.json. Aborting cost 2,400 songs for one folder; counting it present anyway put a song in the manifest that isn't on the device, which is the bug you could hear. The names come back in the summary and a dialog rather than vanishing. Planning is a worker now. plan_andtunes_sync is pure and writes nothing, but it walks every file on the device, and over MTP that is thousands of round trips -- on the GUI thread it froze the window and GNOME offered to kill LinTunes, which is how a sync got force-quit halfway through. And the sync stopped going quiet at the end. "Album art 501/501" is emitted before the last album, and then _write_index, _ship_buttons and _prune_empty_dirs ran silently: 501 exists() stats over MTP, ~840 KB of writes and a full tree walk, with the progress line frozen. They report now, and _write_index reads Art/ in one listing instead of a stat per album. andTunes 0.2.2: the wheel scrolls the other way in lists, and smoothly -- a detent adds to a pixel debt that a Choreographer callback pays off a fraction per frame, so one detent eases to a stop and a fast spin blends into one movement instead of teleporting a row at a time. Volume is unchanged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SKXUgsBBwe3qaHEjeV8ubP --- CLAUDE.md | 15 ++ andtunes/TASKS.md | 5 +- .../java/me/teafry/andtunes/ListActivity.java | 6 +- .../java/me/teafry/andtunes/WheelScroll.java | 68 +++++++ andtunes/build.py | 4 +- lintunes/__init__.py | 2 +- lintunes/android/andTunes.apk | Bin 62871 -> 66967 bytes lintunes/android/andTunes.json | 2 +- lintunes/andtunes/sync.py | 117 +++++++++-- lintunes/gui/main_window.py | 51 +++-- tasks-done.md | 37 ++++ tests/test_round55.py | 186 ++++++++++++++++++ 12 files changed, 457 insertions(+), 36 deletions(-) create mode 100644 andtunes/app/src/main/java/me/teafry/andtunes/WheelScroll.java create mode 100644 tests/test_round55.py diff --git a/CLAUDE.md b/CLAUDE.md index 9552632..1ef4a60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -347,6 +347,21 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal device diff, which had been re-copying every such file over MTP every sync. `plan.stale` still carries the device's own spelling — that is what `_delete_stale` unlinks by. + **A gvfs-MTP mount can hold a *phantom* directory** — one it lists happily + while the device has no such folder — and every write into it fails `EIO` + forever, because `mkdir(exist_ok=True)` sees the phantom and does nothing. + Only remounting clears it (`gio mount -u mtp://…` then `gio mount`). Two + rules came out of it (Round 55): a copy that raises `OSError` costs **that + song**, not the sync (it lands in the `unwritable` summary list), and the + song is then **left out of the m3u and `library.json`** — a manifest naming + a file that isn't there is worse than a short one, because the app skips to + the next track and the user sees the wrong song play. **Planning is a worker** + too (`AndTunesPlanWorker`): `plan_andtunes_sync` is pure, but it walks every + file on the device, and over MTP that froze the window long enough for GNOME + to offer to kill LinTunes mid-sync. Anything after the last album + (`_write_index`, buttons, pruning) must keep emitting progress — it was + minutes of silence with the line stuck on `Album art 501/501`, and + `_write_index` reads `Art/` in **one listing**, never a stat per album. Since Round 50 the app exists: `andtunes/app/` is plain Java against the Android framework, built by `andtunes/build.py` (aapt2 → javac → R8 → zipalign → apksigner) — **no Gradle, no Kotlin**, because platform 33 + diff --git a/andtunes/TASKS.md b/andtunes/TASKS.md index 3db06ee..5271602 100644 --- a/andtunes/TASKS.md +++ b/andtunes/TASKS.md @@ -11,7 +11,10 @@ Legend: `[ ]` todo · `[~]` in progress · `[x]` done. `Library.group()` keyed albums case-folded but artists raw-case, so one artist spelled two ways on one album ("RJD2" / "Rjd2") took the whole library down with `Couldn't read library.json`. Both maps fold case now, and `Artist` has a -`key` the way `Album` always did.** What's left is the *Parked* list, pulled in +`key` the way `Album` always did. 0.2.2 (round 55, 2026-09-14) reversed the +wheel in lists and made it scroll smoothly (`WheelScroll` — a pixel debt paid +off per frame, not a row per detent); volume on the other screens is +unchanged.** What's left is the *Parked* list, pulled in as trav finds he needs it. Built with `python3 andtunes/build.py` — plain Java, no Gradle, nothing downloaded (see `README.md`, *Toolchain*). Phases are andTunes' own; which LinTunes round diff --git a/andtunes/app/src/main/java/me/teafry/andtunes/ListActivity.java b/andtunes/app/src/main/java/me/teafry/andtunes/ListActivity.java index d6ac0fb..d09ebd4 100644 --- a/andtunes/app/src/main/java/me/teafry/andtunes/ListActivity.java +++ b/andtunes/app/src/main/java/me/teafry/andtunes/ListActivity.java @@ -52,6 +52,7 @@ public class ListActivity extends BaseActivity { private String mode, id; private TextView titleBar; private ListView list; + private WheelScroll wheel; private EditText searchBox; private final RowAdapter adapter = new RowAdapter(); private final ArrayList rows = new ArrayList<>(); @@ -84,6 +85,7 @@ public class ListActivity extends BaseActivity { list.setBackgroundColor(Ui.WHITE); list.setAdapter(adapter); list.setOnItemClickListener((parent, view, position, rowId) -> onRow(rows.get(position))); + wheel = new WheelScroll(list); column.addView(list, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); @@ -95,6 +97,7 @@ public class ListActivity extends BaseActivity { @Override protected void onDestroy() { Library.forget(onLoaded); + wheel.stop(); super.onDestroy(); } @@ -103,8 +106,9 @@ public class ListActivity extends BaseActivity { adapter.notifyDataSetChanged(); // re-mark the playing row } + /** Wheel up moves the list down, the way pushing the page up does. */ @Override protected void onWheel(int dir) { - list.scrollListBy(-dir * Ui.dp(this, Ui.ROW_DP)); + wheel.push(dir * Ui.dp(this, WheelScroll.STEP_DP)); } private String defaultTitle() { diff --git a/andtunes/app/src/main/java/me/teafry/andtunes/WheelScroll.java b/andtunes/app/src/main/java/me/teafry/andtunes/WheelScroll.java new file mode 100644 index 0000000..fcff0ab --- /dev/null +++ b/andtunes/app/src/main/java/me/teafry/andtunes/WheelScroll.java @@ -0,0 +1,68 @@ +package me.teafry.andtunes; + +import android.view.Choreographer; +import android.widget.ListView; + +/** + * Smooth scrolling for the R1's wheel. + * + *

The wheel arrives as discrete detents, and moving a whole row per detent + * reads as a teleport — the list jumps a track at a time instead of gliding + * the way a finger drag does. So a detent doesn't scroll: it adds to a pixel + * debt, and a Choreographer callback pays a fraction of that debt off + * every frame. One detent eases to a stop; a fast spin piles debt up faster + * than it drains and becomes one continuous movement. + * + *

Frame-driven rather than animated per detent, because a new detent has to + * blend into the motion already underway — restarting an animation each time + * is what makes wheel scrolling stutter. + */ +final class WheelScroll implements Choreographer.FrameCallback { + /** Fraction of the outstanding debt paid per frame: the easing curve. */ + private static final float FRICTION = 0.28f; + /** Below this the remainder is paid in one go rather than halved forever. */ + private static final float SETTLE_PX = 1.5f; + /** How far one detent travels. Deliberately not a row height. */ + static final int STEP_DP = 52; + + private final ListView list; + private float debt; + private boolean running; + + WheelScroll(ListView list) { + this.list = list; + } + + /** One detent, {@code px} signed pixels in the direction to travel. */ + void push(float px) { + debt += px; + if (!running) { + running = true; + Choreographer.getInstance().postFrameCallback(this); + } + } + + @Override public void doFrame(long frameTimeNanos) { + float step = Math.abs(debt) <= SETTLE_PX ? debt : debt * FRICTION; + int px = Math.round(step); + if (px != 0) { + list.scrollListBy(px); + debt -= px; // what actually moved, so hitting either end ends it + } else { + debt = 0; + } + if (Math.abs(debt) >= 0.5f) { + Choreographer.getInstance().postFrameCallback(this); + } else { + debt = 0; + running = false; + } + } + + /** Drop any motion still owed (the screen is going away). */ + void stop() { + debt = 0; + running = false; + Choreographer.getInstance().removeFrameCallback(this); + } +} diff --git a/andtunes/build.py b/andtunes/build.py index 06fbff1..808e8fb 100644 --- a/andtunes/build.py +++ b/andtunes/build.py @@ -26,8 +26,8 @@ import sys import zipfile from pathlib import Path -VERSION_NAME = "0.2.1" -VERSION_CODE = 3 +VERSION_NAME = "0.2.2" +VERSION_CODE = 4 PACKAGE = "me.teafry.andtunes" MIN_SDK = 26 diff --git a/lintunes/__init__.py b/lintunes/__init__.py index ccdd8f9..911530f 100644 --- a/lintunes/__init__.py +++ b/lintunes/__init__.py @@ -1,3 +1,3 @@ """LinTunes — iTunes-style music library manager and player for Linux.""" -__version__ = "0.20.3" +__version__ = "0.20.4" diff --git a/lintunes/android/andTunes.apk b/lintunes/android/andTunes.apk index de60aee9cabaa11ae027e4dd58aca4e73052e1fd..5f2e8fabc211684785118b6a86558aba94c00a9e 100644 GIT binary patch delta 31299 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ⅅ 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 IK)%lZ525HgqW}N^ delta 30836 zcmV)0K+eCHiv*YJ0}@b60|Wp7000O80DzwX)vXYwkrF9?{*YrUk?jN$S^kI}Cr*NG z2o#=l!i11Pu|3!Xh5|>nM52k62x;Zu22xQ`QUC?NAO%B3O+m>plnez0zk}qtXYXpS zHd;qW^J?$D_nh~9?7erDmW=$874f7XbHgIb5`F$~#wS4mEP=Pcd*Ckk5_|`K27env zPhrv|&t{R6oJ*OV4apgJmlhh5Bi$1gs!(TP4Ok5#b({}|mz z@)=p&f@u)ncVg$bE2D9bJ3k=ZmZX!K9j2K$PubRhgYNo+oHxIb3S8ZkXrklo@6@rk-FhbjXPRv zm0CoGEoVAV(>0clF)Ny_uhse7xp7R@MOMWIeseGJ+h^X_Zkw(jX+MkdA-?PE-y8A) z8jCMzG_?A4IAJlUc2C9I%Jd# zS5n;_jTcz-pXC-_#&;K`O|G}d+vHZ%@h0rHo?-o|nR<&_=gTuJ^r`s~9`9?_CH&VJ z2fMMs_dr)%xob_upP}jTeHb76>$1wXr6*HW_j^lMOLrc@PF>X)54bjU9j?Bx3bS2* zt7nQRHQ>Yfyt(0#q4Hf8u=jRm@X*NSi0omJK|i&I*wHI)S?LY-5n zt??=er^x^DiO$qvxbD4t3ZItdwcPUwnMxyzmY!?o^Tf;OJC7Hf=YbSRgB+L!V=2#@ z0tIjtG{EQJ31|*_UJHCbXk2qtVoni%k!O6)aa}O?@IN`c`7AO%&3D+ogBP@-U_Qgq zm9|_)?ev;HZMm#tciwU*^$ZT|&e-`&TrXPgqUDxizGi&c;j%X0lFcj3_cfbu%E@=g z@ylE8L`*+zdlJ_&G1hE-A%E4z@SGT?57V}WX-6;a7v?vRD&nbO2+y{;wdra-dB5p!#%)53ihcREZG1IXc#;yLiBFX4Uh9J8DR-e!ONWiDO8Pv>DgXaE4b%m4rj0001EY+-YA zWpgfMWq6F833yc1`TsxX-dU2#5H2?f%ZTP?!ZH!mNdTFk$SP`afp9?}NFm~a%Qz7Q zTmc1Tl_l(pvM&N*3wzjcw`y(OZL4jy{#x5=wbt5&|8vegNhTnD{`x$8-*eux-?M+b z?|a@e8FSmjP`p!uTOw}dwM!ly`u0DsZ#}MR{u|fa8@Y3S>4(Q^UxyShaY$zy{a@?X zp`+==9|4>u@CA@-!Slek5%{lwz7DhlI>+JVCGc%$SUMM$&xL2_!kW3TWiITP3;X86 z!MSi`E}WbTZ_b5}=7Kp7I?RJX^WcAxd9ZFCyfzQMnFkllhd%S+?)mW8e3(2Ro}3SV zm=AxP4=jMZ1<-y0lrDgq7Qk%_VEh7@xd7%bfF%oH)dE<(05&gxtqWl90ywe&jxT^0 z7r<)^;1>(v*9+jw1@QL;@cjZXs=!|b;VNiV1zoG)>MHn272Hq-!>V9J6^wtXf-zMv zwhHd8g8QprLKQq#1y5AL^eUKL1#_#QstT4=!SX6tTLl}d;Q1=3u7W*PaIgxFRl$o@ z@M;yjQ3dZ*!G~4wX%&241%Il7udCo6Rq*dBU?H?z2z?g9?F(V%LfEwsez6e#x)AI| z&}9*nErPL&;F(3RV-dW%2)=(@1cAlSZZVWCh6#&d)nYih7(Q4G<`QVT1bQ!l$3sn6v`cuYlt#;5RGahZT^!5@LTVq1Q?nz7if; z39DDa-j(q2O2}LVg{z?3Dj2&8o?Zp(SHZih;M-MTKMR*U3xl48`<{gr&%&W+;r(af zn`fcfbI|8GxaB#R`y4#~9GrX(-hK}L{2XMjhKpB2uhlSgH9WW)=B$RTtKrCMcyl%U zel>iz8mu)?xCXkdfm?sp!2N4r>Ka(Q1`e))pRIx4tpTwX^43CpE%aRrtMn@wqjm7tb-;RPz8*TPhwInF z1M6Y&dN{ZqzFrSS8({PXn6?3)-vB?~0N-zb)*GSQMwqY>YBqnu*BhbvCg`~d?$`v2 zHo<{S@ZU{v#b%he8CGnDUvGxYEzo%jjM)Nnw!qJ}z~8sPh0nwF&%^!C!{+DV^z-nK z=i%b5aP?LgxfSl(3aPCyVJken6`t7&3%A0`t*~}0RBwepZ3W*p$leBp+n~obaJRwe zZIIdq6Sl#WZSa5eHkiK+R&Ilh+hE%^sM!X`x54Y%;GJ#ot8MVvHu%dn_}4bb*baHy zq2qSwv>keEhpV?kYCAl*9Uk2dv$n&E?XY1xY~Bvrw!^ONuy;Eg+78FI!wcKtwe9fs zc6e_){AxRVwjKVo9sarS?(FrgYAsfJ0_u&^4oRKxaac&QrRtcLfi z;aAo0MK%1r8qQQhcn9R|fHphe${o;e2i&v+ZruU*?10C2z_cB(UrAjH`jiYG8T|EU1CyHL$J*HrK%R8rWR} z`)lAt4ZKnV@72KXYT!>b@J$VTUjyb|u=Yaxz0hed^x6yk_rl=4Fm^9Ix))~bg~fYe z&t88xycb^F3vcd)5BI`v_rhQH!hiOHe;+j42a$bn@jfWs2fg>fP5a=MeK2+(JiHI) z?Sp0eVEsO**#|G~gE#iUd;8$```|D8;2-eh_Xw2&scG@gU4T2n!Fws)MlgAnboV z2*(e?TLmURULDNIf;t*VX2rfSa*Bt`)5Tp*l!-rt%Ay{w-)*phJ zL-6V$c>56i;ShXx2>gd3`!HN|7}_3&%ML@yVYu=z^f?SS9EPEXVdP=B?J(SX7{(oj z$%o;o!!YMCEIkaH4#Td)aOg0cJ`8_v9)@=h!ygXAmxtlI!|?CJkXZ|MEjYDsaV^Aa zp<6BVsD+=@!jM`RRSS34LS-#HS_@Cs!mL_YRtxKEVOuTis)bXv@Omx0R|~(Xg|BKM zd<0q_fp$mW(j!oM1g<{qfm4dIvs`XN1@MAxbY~AI10BPh2&9q=qOA&3Qr$}c}HQz zQP^}8b{~bKN8#{EZx>J${8f~!tJ z-&4^46qKKWF{dDP3LZNJQ%}M2Q?U9JY(51wr{LHrc=Z(g@)Ue@3O+pre>??WpMvjC z!I@K#eH!vlL)X)A^=TM*8b+UnyH7*vG(2(|rksYSPs7~Pu;etXIt}YjL-lDmd>URj z4L>^#AD)KKPQ#a{;oE=H;C}(KUVzRoz_l;Hz!$)M0e<=d-2MXG^8!5i0zCZ!l)ea8 zy$HoXC!iF#0_X?a0*nNH3XB3q0~Nrnz!=~*;CA2+;7(vHa2Iela1U@VkOc0-)1fMX z`+;%51Hgm8cwmCmyMTv)hk-|cM}fzH$AL+}WMB&L1n?v<6_|epJOw-rOb2EFGl5ya zY+w%X3@{g%2WV9ZttzkxSPU!ymIBLw<-iJHC9n#37I+R=4Xgpy0$QA81F#X;1Z)Pj z;OT{1fo;Hcpc>c#>;!fJyMaAG4X_v32kZw900)6Xz+s?P>zqe{W597hPjgNIr-2uM z7lD_6mw{J+SAl=mfY*Va0dD|r0&f972i^vL0sInp2Y45F4|pH=0QeC22>2NI74U1| zH^3*rr@(K4-vPe|J_9}n{(z?!{~z#2;7`Dpz@LG?0DlF(0=@?R27DuRXW;L^cfdb@ z?}2{;KLGy%{tf&G_%CoqD&qo#07HN&z$d^jARr(pAVYsZrht%uECFEwO$1~MxI(~i z0Tlvn6);A?ZNi)9rq=bZ)b#P0&|T9n&xETq{nkwAspwut6Gf>klXTc3XhE6|7(`{xo&>hq0 z&S~_OX>@OR)RPuAr$*LeqPT8`ULP0KXpnw~z(OVdK9k#)7yb;)vCrIizr`+%-Dl2)&z zm+HKZ&O1!gt9AKNxu$iR!nE?+=<;QmZRNV2)j)sO>b|toX>JCJ&Wt#p(r|*>3hR(WQl1|8Vsne9Er72DOUft5_x+0CfDvj=`=>hs& z_)6#fiH;wt%l)_R`*2;ai#NA?v%NHuwq|={E6T*WtGepkA{`xFwqto1z zHkW_y)8k*J&y~7y7?+l2T-v&jbjR5ZbZ6b3aeAFzr_Z6fyyJD=8#VowE@!;XTSwQ; zpYi(J)x}?#mZtA)Z#*P@{cN~ipLauNdv#4n>&pb)mqD7Y`;PMb8>Po#TH1G+mG(_% zr=^$Y-km!A+_ZkpO0RK z!d@M{GOezRXW%AHTWM*grlpbde4u}(TR+p#wpbb+PoooQbkDRhuT7)n*?PUE2c*S! zPMdr8%xoxUL|PiTmT#>4zG-!(o@w~*6Q5}qlc{NG=B1Uh^clFJ?mIryFo(CLr8$t6 z_jp?T%g;2-y|>ch-%m^b$ukY#;)}HSuhPLTWfMds!N|Sx1nFw+=hSjt>-qR zZ<7`;&zHLRj&q@Z-Pq1;Xiv|y^gGkO^|fjB4wwtW>egD?+8LTw@2I(+{CGg`b>*6t zd)i1%-#Hg<(ewjzVT7h1oeMwJ^whL?Ip6ANC4*_Yb6UKd8>2KmFD2NnJuUAS z`s}FHX)chlhC~ygagYHQav^_Rfi9R_ETrsvrIHn{!eGCU=Y3CdHRfsTAmkZUMi)Gk za)XP>KZS?S7#?0=$}@Z{orTon9;U01`q|U86H@<*u|(kr;)c}2mh*y;IGz`U)X7XG zZ!Tgy^Mr>BG-eqd7HBk0iIcccp-DSa>T;8KSV;8JFV^W7>-39t`lWx2=Zx@B@|WuP zWsGNn@NlBWc^VfeG&!il|54~u<;eIF!;2qgc;O8?Z0d6JOfO!xb2$ktA<=8^a;EZ# z4lmN-#TwV>_}dJL<5{S1Ig1q@V5#BppH=9?Z+cvK#u8!<@j!6r)t-BcB=MNYuu^V z(@soYQP_^x6dvGpgVeXzZu(eP$|rj(rNp@wEW~ z<|+LzKsGC-ErX|2ID=-~t-`Hzd=9$}iFxeNSfenP(+ZpO3yr@~Xz^QxHlOS8Uk$0# zr&RfwwBThGZpp6=2y_im``uLcyD1Nm zVM=_Lu<3urH!;D|>1{IDEF`w(ZG|2AK;aNRR~X}u3fu8#g@gE7VLp!=5@!%JC0;aj(K5yrpnBUnwl$0mDnT*?>Iut9;E#@r;nzmT`u} zLLOAuh6#qWIYc=sB!0#(6lU_V!gBs9q#dF?QB!}~AbRt@@azn~6!IMQ#-TMAvrkCu zpztDQsrY=xDJ;B~u!^54T*#XW7x8n2_w$v)Df~^B6ENX2 zU4OA&H^n6QQb_dbE9PCESGa^73K#H-!lk@xNLz}Rzl1APKTGJw78PI0?+s~Fxso9U zbfrHZ35g#NG^BlHxyqN|x7?+|zvFI&dwG9CVGU0j(#F!0-wJ6D+@;d@AjLA_;c|`7 zYdo&;gu)N_m+pWc=W<-(IzCe9wZr8>mCj|n!ofPj^jnc!}k=<#PdOzd4&gI}{rQ6qf1&ULe1?Y^3bTL6)@bSY z3l;u`X@eJcG#y{|sR5mG|p5PmP@ot#(h zqr693L*CDgQ2lv~lYD8&ySiG%pQO`G;<(07xl4HQTZ9*`R#>L?rAdE!940Y_km1Eo zF}!ph!oLi8|91kzl=pvL`BUrB+ux?@{cWmFKTYqS(-=q_L*5m7Cs;L#Tu7tTvm@ms8af-ToDpq!omy3G!`j5&HXCfI5sJKnV%`VmpsEucaOrmcv#^* zJfg5aa}^F(da=ycM(L^3RkV==<- zyN9J3Z!)C*>a}w<4=Y@w)34zJ<_hl~_g#f${90j{-zeW6$#-S1CRAb0za` zr8}(^Uc*HS-zHz-&nZyowRfv--&Va2w(53n)$QBLb+ms{dT~E4QhM<(RlaSy{_Q%v zUFVnBQ{iJ=qr=x~yiViwhSaCMeAPTjUqjy4d-d+*m+H=QC-0~`&YgOEck1@;q$zhQ zJ^L3V6~4=Z3R|&C;W(ZZUiys+?`Dq<*9h+(<%q)j)V{Hk56D#YKBCr`#rt=1^%Pcw^I81HZ@+m^mp1V^?IeZd*ivAJ5+yn^8}A7tmZkT z#~)Dn_UL>y98%#L9p0QrxGzeUy){(cTXs7v7Llx|GoM}53A0@zftoSrtu$03?FS-CNf2 zCusiS^YG_szVurma4x^@zoqBNzd`3;b{_sd&0l^V{wJEhGOd4)ruDDiOCGLD<4;ZF zmur9ivuWkePUELE|GD$jKUectpNGFp^VewpfV22JG++Ahh}?7Y|4Q@Mr?r1!TKm4x z{0-;fD`y<-7xZKNmYTmgt^F(0_;JnOlEzP;UpHy~^O`^CtoD!5{B7r%pGnQ%uJd1Z zR{ke7zgqLHv-rC-U-~uu*!+7@^LKh~J8FMTu1~A~P0inRp7wqEiigryN;<)uUH*SG zf6saHw|&*an)C4cYW`l`x87%!KT7lWou~f$HGjV5`}O@&{rb`T6>0S!NaO!T^VjO} znSWOK|Iqx6=i!fe&BLwd;qTUbKZd;iQU7(2-`$co1R-)n4z^ul+2n{^N~|zBq9uPN zmgKae#PUfCoX`F$W(cR0Aon@aRuJ+v^(8HN=MnOi`urrFn}kVI16501JyH(2&+%c= zTEs{?mtheW`65QSu(6AiP6&%^g!=eTGEnwpB2!)MSetmnbJ<-_ROIalWZ!L zjXO+&AW7?GQnk3fbFfVrDp7ROl6LVB3H(IJ7cnzJfpD7@ z6E!E0vhsvU(uwGQW%>(*NtmSb9)@aHdb_2q_&&>Pz0~iL)(y%!D#yLfr%TGwZrsLh z*4^hreR3Jr7anCgabOG7}kBk{3MQtFq_6 z#Q1`6u>6i3@-jFh^E{1vM-Dj|7D?v~o+dB2x>u$1gBVXJ=-!z_(tU@C56Cppj1AZY zsrWo`r${>MbhB+bmu9J&fkVmC6Qdnx-O~v=k@O+2LoaS^;UPZCK`Q~EseT#8B z72x(QAnEu-A3|nRGv8c9zHbq3rwefVUeJiMjC|iR%HkJO*6U(QER%m&uq=KFWxXy@ z>tvS|%XB-%ar?&W^3jyKd}nde>MDz0NLjB7)5>U6x7;y>H-qFo>qUBQ$QZfOqqmn zJ*R9LcC8|kRw!-tm5P6uDPx zNWDWOoovyTkeM*^NLpdxCuB-A2>H79$wJd4ou;b1nD2cdMC^Z)W_~$FLe1+|Gf;OJ zQK=nUqttHkIU%w{b2FbK#YyWAGGrdk8E#%hR7@phmB^7bdJr|o5cU0(l=DZ{QxURA z#s5N|P|BH32}w)ZC+r`R)-xD{OxIUR!W=--nvX%)jQHOWNhc@<5%MSeA(GZynJ!dm zRfrI=&m#T{%?5wI-1BB&P*+;QETq!9L&%a+&S()Q_D#fpo1t@<3A4pHIYLxAw+oxF znRMEV&cwb+TC*{T?MPY+C2mbxb5xF57(R!bZNa z6t}RDq|-?h6E^eBrzkZ|Vt>5fO33}$l(|eozJ%}7GctddN!XX~OX2zxD%aI2yo`kJ z%QJ47N}BgBWy%{-g)LzC)oo; z0zy$^)W2=i$>t+$xV|o`wl;(V`GG~01{|V+MWn2uB0zaXE6Q(+;4X0}zoVt9RnGA8 z+b&SGwh72uZ^d13ks8wq+=|wUvT-X~sNNdtYi57r+Ohg@sS!}&@`@Ie*(G#0LX_Ql zAqmq*ml7YtDlWub5Y;6Wapf8LP0X}!k`TFR-(nGNv(`j?3$gR$mvq&a>_zPVqM?QI zlyZiMh>BZ>iYac0S*yOcao(~Q<2Jj9sId^ckIYq~aupE!zh*QEd2=}CZ$i?!NQ7t~ zLf?Pg3gs&^5TR`2CO!=QVDhC&5Tkxi+O77krKBwBVH6eKJ}T6z_Nz7JSK~KIt!F=B zBWW2@DzGx;NSgMy9ZmUOB32MF3)HGCQR8b8YiYNMHIJBQM5EXyc5W;yVtQ-7KGv{( z9elT`{c$0FVyBbV0##eN(T3QOhEO4~eR6-jr?)Meq%{veSw^MvA4+5!o%V8s{wmkK zpV%t7-u<}#LQ4J3Nm^Y*fRI0GOdw*qM#^a}N=P~_MVXqHhMZykLR^0vO8o&M{%)e& z-$vz^vu=oJhBO3EPu%|c1x;Yq!=2P=(X`H&UU;LMr_Y|CQd1hvfd|w+yo{7puJ(W7 zw2>CJkq|%BjkQQxRT9k#Uyx3Hd=xPkIsQV8ztH0^-yewHMLbT#XsyPzfRHE^Cb7Tg z7$J(NBx2+>=6sQ3WKzT!BE|&Ki1Sg7kwNU{h|#WbjyLW5W7kKF4MwBb({@FySHu|B zIQF1j7P~xR9Bf>2wcR_`A!5vH9J_zcE{Qo2<4oh&rFJxy7cmwzj-6xMu~5W(NW~V{ z#Xix&*i%1uiYPxL&lowH!^9;NG3bn*KcZX|Q$+u?a65|Vla{`OBCbdaUq%s~($Zf_ z5gpR_7gI!G8b3x67o5@aQ4ALyDIzD0UqlgEY2o%1VVo$+>y!!-7RkE~D+3 z&h&nx3*DzDxbN{9{-M!@f&O1F2|T3VNZqPs`5Ux8DrJ?9`VB&kr)Go5s+aktP2*ba zo1f6{1Y$aUORd*t;OVAPZf<`+!%J@y6;e`?cFV94F>?$lDHWKN))*lOi(qb01bfCV zZX#kv6XDpSNEm)1qNRvANh@EbF%m{IBIYC|7nP_JKa(7hspKD9YGYZIR<0^7Vq8wr z@vGPhqZ3iHid5Vcrbs$ZQlfMLxo!o*sA&FRE^3#Q>oh(CE?4cnOL~7N8X1f=uD~|N z8r_HmD~u~BHGGUUyAd@eQDJr`<+PK#h!vCCo42DIa!2=M5?k%)hLRqZP{9>ch;HPY zD@exg5vGX!DO>3db^1k)+Lsels@G*@}n|wk4B-(8-+SI z3Y8MYll^2U(J0ilQK)~5-rE|^66u@ojw)8Cw3Ld=N5xgx9bJT5(H{5Kb~Kg?d_<%i zrYQMuZW`StjqY40FHj*oNySp-cfcLcL!Hv2a0et9G`b`53@DI$XS}np0@-R$ZqVE0 zy@_pgGNmRTB}r6FAYx1w@(d_YXFxF#v!$xFSk)?&nfwk2m`l!-*e0#a6))KLuENX1(V%Sc)mY8|%WoVh@^p%hECpp<}`bqSH7P|iF< zWa~b(7Y>!`U7CMq7w~l9t7drXc$m?Xa?z_^*Yhih0{YJI-jtL%J#h937><)Ux>nEKM}SeCLc^rh08tSn3kxxZPa(l#ocCq!I*gE}jWXNU9bR_Dg$O21qk zH&XeVb?blD?M$Bdq*tCXuXmow89UeZX9O(VJ8q!VoZn&AZ>p|ExLrW%4c0eUe;!1JKFD;;eNQY zt|94c;SkX*7yCS)~A;Ug_F)YUcsGx4es{ql@;$}6)M|)Er})}?2#pZ4wA@{b6$*$8RAB} zX)^v1|IU`F5?MZJ%j#ucnv8$Im%^QL4fdlYKl34Oj2U8N(s`Fpai{ddt?sW%_d+Vg z^)W+?kSR{6xSVhXiR>&TUt|-?PGr~D`G$YAPkxHs%YFVjI$hWtcgocyodXP1RB&5< zcoFXFE$Y6RQ5pYEoG@Zpb?Hv%92@z%&N?bO-H?ShXJ1w)ai=v>$``Kvbwb)9OJW&` ztN^*j=bwLmCT0j*+Wv}gpST9O1BjfY^S-p1KGDw$TQY3WEYnD{-5Kh9ZmO(qb?bko zrLy(hpuQW*YI(76)LLClS@c@$Q6)wYJ5^%%uR~ZAy*uTbC)DQB$=iHq>)Y zI$e+SGxyb}^4d5hZk=N{D;GDAvUaK!8c^CngRRQj4~B_rDL1aC-0V-e?>dJ0f1>SQ z>3y}WPD08@?TFb?yt5T`j#<>%ku869j>)gmOr+aBP340&SlRZ@jUTAu>ukw4&+y*h zyYa!QuRWDTV>M+xuf;9CnFK$PHhrfLc7faXI!dep?BWuu1$M6zs|of%WxedFY@mJf ziLg+_@|4}PZ!0295V7nA-U1N|=KGf7_AMmB7$YijX>F`kR_S6RVq9b-<5z#kQ=W)8 zBP4D4!aixC?n5lQtnW|M4T3yf>~H1ylB3QSd2YxPQjY&^Gf=(}<+THGcMeu^8@Rh} zR+OMLoI^Cc8hc#C*Fx9<`yauR!FR?7#w&|>dm%lG-WKkWLF$BSqV%yQQh&2;v!i%j zd8-a5BE~4a=t4qGN}HI(2H1a(#cqlSPvfeOz1qGv);A&+HjeFP-x4d0h((QKFSBom zT^bSV8kgI~?j9?Qhy{()wz4mYwTy^|8^>mgOJYqT;a$p=!$!~l!*Tl z(V@vqIVP|7pop7=wvdITOdBTDB+(>C$+Tg@P4b&8#eJ&~ch>-9sg{2_wYo-9?Z}D@X0fu`G9B>$B?-!Q69t__q6c=O3wQ(0OZe-IjlxQ_Anpoj(N)C47*d zxzsD6ox7wB?)oTI5pF^x+)AXJr^IW-0+A+{2{l8yyN7Qg zY3-7JplUPD;L`)I%=`UK8@EdRc=K&Pyb=x#`(+^iIiJ*t-<}dkmFeF>xDxL-N!n>S z+S0l#B17$w?e%|eK&;h%7(sAUuAk?|AhDgZM zs@Bx$H=&pj43*8ljP4?Xx0`)oR$Bj-@r`n-^iD+Co^*dMf6^KuG6{!*>)e@xi3V0< zH**&h<34?}`hGz|{@|$KLiycL-|a5-?-JGS5Jr8w|5~vK@(vX_$3q-4}eTIv@L z#r7u$ze8@GOcnLHB2vQ1?^$#I8L(1?;=PiGxsBk2sSR|b{bx6v6LZ=hR`{-ZP zbAC=GQBgq3x)C|DH&H-dMo%Rx&mfeMj1Odr=`NLNpQ9ytVsbMZgL2>aTkv$d`b*0Z{GQfiM76*rS|j;bD)5zWXa z<=lUOyg50pa+Hy0PVOPwP%7K!oY1lIYZNHTU3wEq=a`O%KNb{ zYV>6<#TMM`KSZvSK7iB#-D8PlsS=FehOEH`34oMw@1 zLhQE@yz{j_=Lcdy2lcBK5lTcX93f6DGeUny5er1PMC!b>5%MGm;V#!}sWM_T6}D;n z?7s&`rLQ4(xl5&Wmk8;7%~!v#)$M6f{o5lA`<8c~+f?6IW@`O2UG`PKyJE?bZ&kh! z(g)&f`%AJ()FKcUAYTP8pAM$`hV0WD^p*bCS+>q~6*zZQYkj3FCjQ zdim?;b@G?IC(R~oRG6N*@{E#NqW$mS9kPGUptecH-q3CHzaKC~r`u#(E)nr_T71d>XmRHs zX>t2TEnbAXFY>=ze64#hf_tbX=d|$M|GyTNi1W9w$2l#ugNeWe^=-@#EW^FC1F}^y zLK%sSKc7j(r;2-n`iu|cWONjrpZe_?U8{KCFh!?=hPHIj_x7#p=0&#Dbs~S_LS?tJ z)VcHtk?f02kJ9>1wv-6|tmldQYq?{b+Cdb%Uhq7>et2U z^voakLm9>}W4MwbjK+Q__Ar0Stl?BT3xtv8$I@WCE2mtHsJAl(w9R(v4DUB`%C0WQ z?Ky%DerchP^~`4@qx`op%I_mbTtsJeZ!CZLD(cUbeN+}{@AhnP{q0#q*mbvOqSBg# zXMdk1Qt``aE2M5VfLK<jv+v=I?A zPQ-T!Z3fm-;*xC-NRja6cX9Y>?8#Q-rVgJp}v3;eCsP_rmsqBAuadQ04l-+OudHzq7 zJW)<4kQ}msQMcy>-s+VX{6-FsLVEMSPi1;s4 zdecBE(~8F4TW%P8M}E744^ z&j)?7&yw%eeccnX?#j&f0|60xzRr4=5TSa@T|)Q}#W6$l+Fv|k*BCuoNo$$N()R4S zH5tT{$1PIUV$tA7Dc8)_8QyQuN!h`N+w<0QyeS*69dFeR?lp!^mP|LI=NQ~xw|e`t zQEcRze#C#ISRi72Z{(U;h)HrZ+L-e0f1i{JRa)uib&>M2yi~|>5n5L9)z5W#&t^wE z6*R*Y-b|J{r<~;wGD-^kX?-!%)=0kU%ZKwcS+p2W7=(^Ko(p)8z-hnM7-%6Wz(nft+{_=vEbUs!0D(ANC z&a{8CQ`Gy`cB8)6xC8Gb!NpWs8>K{Jlp4~<=~Xq0Dy_}x7SYbFbpA_kLcXN)0)429 zPg?R-w~T#}KIjm#m%^yDHYrWKx%gHjdroCQfp@;wjgO_ge^+?pVy%#;o>A$XL3&Ii z<7=@*_K(RSJ$gU#d~}b^K;7rM&b>%$hbeE2o%gVCqxXNa ztFvv&Z`GNQskx@SxsK2ZDes(hD{ac0CG~qmUCqtY>^o=QCK7dgffR6%*Nd^QI#<-D=%wxuiQ*&pHQVNl%?FAR)WqS^iIo%2?dAk&&J(! zx5}T&ie?Qb<-CGy`2$p1{Rj}wayLCd(s`NvcvQ8dwmv{^aQX7(XJo1%;b4E#d5K3< zd^EV3l=H0EMACV+QO$asbgd86*XoV!R`P=L)cxYaW5Su`Tkb{Ef%1*_lN9O;hp|S)<`4QAJk{oA$g0rHAN(AxRL7*G>W~C zhE_%V_Zhi?OwAu22>VmHJp%N_OHON`X|!b zEG`kL_)c+1ghlz9@kB)XwD36GHRFl;H(_5c%ia)>We>z%GtP_cmmYuX)j0XA8uhm{ zhS{EUw^K;@B;-4LEvoxECy84yfuu7Hi~FqW^*Iqtv&n8q+tbf|;UOh?q23U<6%SHU za1G_7AH=Pgh?`T4w+py-yEMO3zk0zRYbImm8;^+3uXd0=Y6mH-d*NZYx89Akrb^8+ z>i4X!ewF5G^>161Qu%*hI`2`4TX7d|vluz%CqmxG+NL|YvhIC}s@JW!kC^FJq|g%4 zS^S9MNXf{Es(B#!~=2-~6#}P$x6heTgU4JDKzQX*_h6&%g51upXk)I;wx)9GrbtKTOIR$Jy@@ zynGLnwBA4GtwICu11*1;jpYjKVWvi~|@i zpUNXDpOj`pT0cYoCbyignaXl6Z%j8Tt#-Z zo)p*J*1(VYGD%sjq=jhFB9hjF$~KX7BDjQoZQ_S049Zn^0d3UkYoU6OAPbimmDWG> z{eODtcWYa}1m?P0bt!k9@y1c?~yL@NH7unf;%>Fjcr zXc?-(D$r8J&s6fVYe~EQZQ5R?^+dH>mT?(x^M&bITzuN^S%_p-P_+s^(r=?|f5Inc zkF?N)eK8~GMgjrlZ`@B>5(*N^XKg+S!i@FLXOw?`9u>v{Qayhw%(k)1BmUXSzJ9gR z%(IC4JU$WAv!W@d-AJPxjg0 zpv-@oqrYWid9o}%gKpTk#nULTyEsj7OJ>nm-Q-*>ZExhWOzy!nYh+_Im9OGL?bG4u zFkjB_eAL~ZlW}`3)#u{i-qTS&`I#x}6XBz|v9z1iJgkyz^`6jBZTi~`@3;38;oqtm zkfrtZB7Q}L3d0u3_@!itu=07y)1!Y(BEE@6#1Hq!ucoIz?280ikaU{J_a{El zqTt#tPj{qWhA$}3(F|oVHi_lCFDK}i=?hj`|00vDOzG*l1!@22B$g_ltz{%K!pau= z1eMl5r9_w^Lr?O4FeqZblvZ9t-5ATc*Pbkjm`3^TSwzBRhHTAoe>fcVd+qeAHr;t$(PT{WAhVmHw-U|4!rncwYvHkYDDNI%&Z*1=n`Z$fn?0yVb6x{d~TRy0<;b zQ_>Kvb}i{Nu9>peONpaX$1J+}GwFZwLXh%((-^jQ1~FgCc~M+IS?vsY>Z>{&5hB&| zXW}&x^GW?(g?&{}9$m04gy0_B-7P?Hcemi~5Zqmc;O_43?*0V{?(Xgm!S(W=hgguVQ>gnBkRd?@RD~^=AU07&4@0!9d$i=I9vwSg!*>{99|9GNrcH1XR@^2Qc zD=X$0?$5;CRvKz)&_k%1QiI>_YW;B#oDNWmv_@j3IXwMKU!xGsTi_V@Bqrf1+W1mF z0X~550&%$bUb#FX!1Z>C*9uXrl&gnmV$9JHC7KIs8gO9Hay&;BP*1`-SGuMaE4Q4B zb2dhk+kjJK{RdyVlJ^^U2CC^Sj@mtXK*$(5D$EX2+GlMs!u3S_L%(3Ne0FccRTvr`tQX%<5r*@B1iSosaV`}BJqK(-#j8yX>v|MstFrmO?$2^|Nr*k{)0)NX5#AM^Pk^ufNABhk(bdZa zMVfa~a(Nb<5y((6v{2O}5gtY^X&PcYwZlH}O zbXdwpcSXES8jC8TBe-Eg{h5q9`pm$vdr&!p5kEy^=Z;lq2Ca^M$MX-<15?@KxwVfV z4->`yk4DLRd^ydW+Bn|@i7^XO93lz@%lHKazKGg?v7NK>a(`_p6+&{YO?in%W1YH$ z$5byN=v_p}XGif!=d_tFgJJF#te_vXB?^gd7tKf0m$W zBEFHoZ`C57w#qvuUwIqTtL&B3;!R#g_F?OUI7mU-!d6a(D}UeKyox9Jla}bTRO@G#1i+i`M7gaWQH>D?>=2Yi0sswy;o(qo%G_zT zBVfrq`@)y>QYri;5|Liz{ay7AWHi-zp^hLfAZ>MQr$MwBcr&dWhHFUCCE*>^YnySh zoc@jDSVt6GNl9`9w~#!L|Ue(smdNSkI?KQ<+W3)wM3iST+Wvg&D7 z=Z-`EKv7|(HZyfiEWop|{u2&|FUX`bewuK9OW_U+l)##>qX2%T(hjP=-+Ro1Ij07w8Z_jN=}l%YMj8V zR}GnYRu~?D-ty=WFW;8st%BB?XOY>?=@BUuh<+R^$MuH>OV-K7Lu z=dtl#N6~3qy@eb16m(NbKwn%i!Cb*Nm(%3m%-RK-BvrqeMPN*=-t$n6aN+Ssop6T2 z;tlh}O4-H)+~1PQa!l-d$dkB_8~Y3bS&u8(aOdjtP=@WZBHimYWcFl}FYP+DGkW;U zjW`nO&qMVTWUM47B^9wuZr<(94f zgUuHw{EVS-j_spZ``4WO#-cS;Ntx%$4>mT#rlK`~)W>Fh`B{QOSKQTn&IiFQA1F^h z*Tl;)bXJ~I1sh%p8@xy#+ov99)cfN5A@f3uUum3-)$HuLHLH;{-@f$QL}Q@;vxkPd zg6RCeIM3sFG9;7bfqgY)iZ=Z+v+WmqGy7|23X4?8Wyw+DZO!iW-=1j$UfO%J?B&&; zrfC73=u{KGshd~f4n}mn>XP2TfPsiR<(JX7tbnAV) z2^*^{E?w*9p1?z8&tPW{$CQuJ0vFT9yS{}!1m&Ou#$$QMq4|e6YX{x1-fAhIp0;#7 z-T6p-*Fw5z12AK426VE-!hNSHEEoUi_Jfpk#Nj!N1(}_*|;qy-HR@#k}0FR+p>Slm3!Wu zsLJKAe>Jwu-(Ofoi(MD^dOy@;&1`+Tx-7JG$I%AO55=)Dhi4@5u(<%Cj7kxS84gf^ z?wi6>=1!|8P{2c$GTx9D5RpiSSH zYN@jRdR@Fe~+ zDuA$@mx>54eQN8tTW&};k~%vcc7gB3V#j+ZoBeH6?-rc7u27D=XX?kvqUwzAk|NC# zZSo=VMOasQADouI&=bGg1@2zI7?wTI-`+u3of_LD;*jtfyeaX!dBGaC(8 zV>3}z0(s4DE??eu z(d>nva9Xur5Vn5|a_*QQzXJ$+l>88F!&F;)J)q(`{Gw}8UxssLTqf!gxhcL&P+(|h zU8Wzo7ZEuIT70>?dS8IIXlF$ohySJJn&d+-&N{sL6b?>=$&ngofbGhMc&?0;xadZl ztB#HAD$bUrdqC#47uh!T+Q?ry4=7!dH6EwL74hQbys2u?PzcYK8W7F(hQ58-9@IsZU}M>z5sR1$l`oYG7VSRWEZKhVbXj+VA*eI8Gt1 zNJP+sl=K<034e^HzGRv4u2>>6ya>Um{0P+63n7Y# ze;qxV(EI)9D>mFP34GCF-CI8Vvj9STn}f(Qu*eBWxCZ^557Inq@N=)1L)XqR;)u6) z!MreVkYo{J2r>zMGxRxHF85|GJ2ed<9-A8TlFKpo`-OKS15os6^lVO)tLorxm81uuP01#wZ zhLZ0p9F;^qa7IaKX3Lw?m&TRvEMZR>CCD0i9MGY+(8Z?u)ND zM;GNM05k}ehv&MQX=C*=@o;cFWAep7kf8Gn9p`kRf^r#-Tot1HIl~@pk1Z3U8@@m* z=b5DT?h<@MI$P97I|WZb($HRQCq_UAKxRh#Y*=6m&?Xpq=T5h$2R z&>U-HGE%5s=GKL3or+Vz=MCN2%}D>_+u2!fi^j$`Cw;65ACQz?-PkmbA40WVXU`1| zBoVWXu3fwKLdImD!#+)`_M7JCTJJTrlIJ+K1TY=Tm;`Ni{?-0?LeJ!Nk2{YVT(Jhy zwH9lbde=uhKDD`ML`YMJ0F9$~C$htyXaTA8_O%VB_~MQf=WhhDcyCz3__j0B&lfM6HDQlez3FLFZ5My+v)PExuc| zfY$vFirPDc-Od678aL|Im5cER9^jwPinjgF4|8@@4T30MBv~oyE-hjBmLY)-3xaMW z8)o~%#MThCU69~~y8Ou{G-NV50r?n6nszwBn_Kv z7?mh2!;Dc3>xMggBz2Nny;q(EMaNf&$7qBn|3$N-rdkwf7(ruqyd?jXR3PD6Gu?1d zcs&+jH>$>vwu?LdcbX!p_30&eLMd20v-Tw>#WrE-$p=K0sBJ$FxdY^73_kfMyz2dj zakBXlc0INLf^`t&6U$cfvlGgkfYW*?+M&15IO^C58PfItu46#8E6736+?b_~FwKu~ z{!208Eu^J8@13U9t-HM34Y;q9U)l_L_?#%kDz+v^ZL~-0EFtw9d`Zft@N4qlXSUmR zsHT*3#lD}}jJ)`nHEgRoon671%)*(dzdN5PcA}*yYTmz)UqfyN*wqDa>^?QX_2913 zGTGhU4=@SV__Fv)RyPSCOL>EHzOx_qfka;pi+vIv$2B|G*G30~F9G-s7u3^%^QtC_ zXvIxA{%bfT{?ZbEw{mF6yXTVYad4LqZsH#>&*}z5mZ`V;0lw|91w!BSX(xO_7&hV; zeX|!h+6nRm)`aAaXwD&PvRd+3J=R#{It!n%KXJ z-CNNQ_^Ju@+PdFsx<>pF^SD|z*Q|jFW0L7#5icc2v6b9CPhlgPMznL!Wb^-czi(|qR*R;MXLTvx6uP+!l4;II?Gpa@UNk5fq z=74*dY@?fvLSZ{-FLb?xZMLumD63nq)81+zYD;#cLAKhk=LJJHT8a`_%^KdMGk0V# zym1?4%NL{IPHlWr`L(e^JF1s(@o&#paRwv~k`1+3#06YVlROYcUftM_77KZZH~r>t z&-0}oQcUp5zKhuHz&z=u+|pci(m#aK*I=pPokPtq$SfvYNEE)rj=hj*6l_}9@s+@D z{Cmr#|L1e11P7{N2GP`SqD$q__D$8&%;UZjSIcy72UZ=jZFH%WVgCN8n0uEUp_Z-2 zknJv^Spk$2Y(bU(_~t^ss3l*dTNmVjVbJ^E4g0o=@9b!U%bsXsIEr;LT1fR;2AFR! ztUpM)Nw)XRtTU|h&2*`@w@5wsIS&?jl{lN5EVJ5&9_jkw>_AXnf>k&pw+zcjYa#EFI1bqbLvJDJDQ{?!f5K^>&J;aS}^8?cQiEoW7GYZ|-9c(9Mm3z}Zj+Les zuP`5$0o82op*IzyoYN=2fhy(!d6`$}ZE!dN(!QACWQ@0`3VyP+#}ii?ZnA8wMkR1iD{io;AR zk%0Iae93PYVT}j;j-%h@pvP10(r2p@fiegg|9D<7%bPzTxmHsIc=U;glQJ1oG+&Z! zp6ubDN5v`Za09#q>`(ipI0UvQ+}%va)6m{MpCJn9n!-V#rC_(ywf?K^ov_5FkNdZQ zusHwYxtQgBGOM-`9lQhL5?GI!l-7%m?)dR^_Lc3x=}{ZJ^3* zapfPK$pwq8ep^c_i}XuOAyqUo)1<>}job#ew>sX&NWS`mf{MI+vG_$gPM4d9wY$Tn zk9@Hnwv`>|MO-afHDnP>;he|wwl+n3)$S~VIc0p_#SLkPBj${6{u%j9_fPPaM2K@A zy9>y~<7jx^`rdZ%N0!6kDN3By z{zsgcSNawC3{3PAJ@QQtW|Kz86{Ru2>mkZ7U28;$Q*~mIme9Fy#FXn-yYha0Lp5H| zmj~X^LY>VUZ{($~qEwdMmrEOj1_>zXvT}sk!PUdZPiwa`H+wU^q&fn}b0fuFvn6^{ zrn4wf(A;@mmY$R@o1@&W1c#>5bo+^hjP zKYkq}h?%e)jhhHcF@a@3nzAE~SVL(*^`_1BJ5$0-n|(1kP@ z2k|3aS_Vw=bT=7cAec1wpkJ?TiH_4|tPzCZ0VpS&5!?s3^Rx%P z;~3cB;{wh}sW=s9#v^X}s5uNcj2u|H-MzZDSgjnKHd@`gwkp$f_Jf93aO5doUX)q& zyJkLphd87EI4qI+wRW~Xa4z=zBry4FTH|yvnt$EBqoD#)<$&bUh6K{C3 z#Ogu>A*&q1alo#FeUEMNA447T`)&3EpEpWa?L&N28MJZy2gKQ{D9Ya)*1_yZWO!ms z$x+(}E3cpu_~o+P$#-u^{4+`eCW(sELkj_c5Rfv zZ+d**!sqBrA8k<8`&H$IX4sQWuN^c`$@-uhHaCKfxJ9y9rV7m}zFzhe5p1Q>xuE2V zvy!=u$EYTlUparjTgTBTUE8VrxY15zo8!@eNH8mVZ$Lp8JCQQMuq9-$s({>^^ zLeL{js>T+RH!*=1PutC38mUe(ZknKCVKiP|vlmm>S@8M%l^sG1wa@A`VhvR_lC4MF zVnB-5#y}K9hoOB>a`$LTs#SHoQ9x%cj%f|7gA~lrcxN>+k0kR3X&JOq^luqsdZ(H? zv$MKPK326f(f)<+bw&$rZAv{P%eZWpkF4_J>?H~q?y8}K5H>LIY#@s31#unEJPIlJ z)~3=^_Ak)Nlg;8LUZ{(9Faif!b4AzU)#D2@AxZJ=@40`SA6g*WyYgTEnsTq#b1Luf|bZyaz>=dla$qglnEIKhSa|?tI4i_L&##6jzi`L@)YurGuPDLjMP&ZWs zVYkUrv9iT?RE99+;7Kufwx6iHR(WUhxO5gpV)Xm%;<{%)nw$KnvK-S!qfYM;w>S1- zX;u5!em$Vs{Z@$kHM97I%i6t&YYoV{Z2_C*-z2Wft!Tm6*}`Ad3*su^=CpZfm0F%b zvQ@zQp+)yI`Q~wb*1|=i7VD=2vt40gmXq*W4KP z)1VLco#~I9ey^I91%@yQC6B9sSR|gF_dRGs#=Kh2Tlp#6-qGN*&2T;>WxTvGhmK|` zyu$K@Oyipy;c+Hyc90gws~@1lei`hR=zghMzLf5c|MOFk=X9t)S=XD&`o7vN_3sgn z#CinYjazU!pC+qLv~-eNpa9b4I!s;NEXM4i*?iZ2Rw0p3h`X0V#6P5~bmhq{9zmiI zAN9T)_KKB1s>)G)N8%0=mE-j@brL`xRUhJcT4xU&_2sPIMIKdCmz+ZnT zr%)@E2t_~l8#698$%WeE=mJl0dG+{3s(B#g~R90L6s-)ePoT6gRP; zAs2t#&ofaF2at}JDiI?V0q3RUPM69NEY!T6_{J~98ki$B2`BNVKjlin&++zaEn2RD zAr(t*_8))wskC`m+0UnjQnXNyhp*IbC3%+@h|SD4e+(4zvAK1O_mrZWY7|jKmWT&J zLP-`B6%l6xk@|l8MZ#-Gn9cm*t>?VPEs8jsNrNXA1N`av8XP8fW^^Q^P^BeS1jb>Q zVVK^EjD`0>$!{i-|E;KBm-`m?po>kxRwssgUn|IXFWnG}vp)(Wr-Ok@Nbf?XZGa?E!UmFj$b#<=5G4PFp_S6j5dQhiI9XpB_Me}9w(J+d(ZyNH zTXclXzwnG4-qj){FguU_saGAHTI_H-A@<-QZaAcnQs(M;{qPmS`(QrkLpL`gMg=*< zD%4JWVuRP5qHu*mTT>)_T+qF(tnA*QJJThG%Y@c+KN!BjMO&Q&6H}?!TQ(yv0&qV$ zIA??PP=j0$krYTsTh^ykOR+|D7^@5uu<#Q^O6)D7B2w4x=|RzP-G)g*tPC|^4zoAq zttZg5fZ`afD4sZxQo}*G+G4B46Uw$}LHMl2XShDvhpKNZh2b`pxc23%-a%>T0FE!LY`WebS)S$uh#4zL}az%5NJ z40qM)wY6GiA+cPJpJ5!ex_9rtDW+2;AGRE8Uewqy#V3tPC${#O14Iatv0%29WKOV! zboX7R)EHI+tLwL)ELQhQFx7JLAe^Etsx)-O8OUlJ(Rxj7vEIIk`s+%z+#;k9^jQZM zK4D7#$yJh-Sgwo6=dS#MixV$KH$XdnoU0b8oU2BUNBVXo~u)+YIE{T>J z1NjHua?RqwDeD4_EdW>bFMl`Duw#6&Az7E+2^`@^XzsFxFto2jp(lzE6H+?0r{X!I zcmHzMQ9>tXJ;7*2AEslnJE|#PN=EK-^km+kZJI70jB+i~TW=PNH`yp&h3b;)0u=&Q zyS2HG=7k@zbpkh$XBJ%~Ch^&ldpH8BM5e!R%d8O}n9^W&A>e_}jqeri)qOfga?qGX zZm7jVcIcX^+h#=$M7o1W<+{E$O-7Rz5|wc0WWoIDxZq@~wu`YOT;9LI>61%$QI$h`6cA{_yWr0Xxe5YLjeqXKb%qfa@7cs z^XCniXUdUA%pS9Lj1ncv8L@WTXg#@tXu(3&0{9op8TAWlk1(3F`tq&1zdQQN-;PkQ zXjMHKRU@fe{kR~VPses8*wKm6dN7GvMgCa5Ghq(?IrgO{aQV~PSMwB>HkZM zKhQqfC9{5kKGS+f`d&NfA>aam%Leg2J zfzKi|bsp2$7X<|`Vi5=G;~DK0p8FM%Wm_l8ANQCR$ULVg9pm3rD*M7d5BJ>PYMCHM ziMwHg;OVB0tF-1}Q(UPDj<6hEpv!;udlQN=sq&KF(xo4^@{3+Yy!?q(XV3<)kMWXd zxvG)*_I|j6w5d5~*LdtY5Kt3^!>o9grDx zdUy%Cm6K#FhOivJauHfz>-GWn<w z4yZu^F>6XoAjcQIfA_ub{&gSG%uawKb=!dM-r?Twy`_t;jt8U+Zw3#6+C_N)bMEt| zwkh^GNncTlcgL?+1NboJ_L2oOpRSZwJPtpo!&2}KB5k~6Kgho7ay|`8@3=sdA~cVh zhvC$62B;^_mNYFY2#59{|An~9A-+mrEJd&-{nLVC-xEYQU~54MI=fUrJ(jvurFc)q z3m@28_$^7*2m0;fuq5s4!Axy-k;zMB&tuaEIhr1 z;6&&>avD#|@JDpNI}C6WwdJ^E^N(xX;t@vZ-DuoKNnBZQvtOsayJ_^v2n=@@kn=ym zev#)d2C#nH`TBPik!O?Sa=XZX5XQS7;CpFHWeN;x?3liGIz(^er(!CI1YzUbqP)bY z6b&+axL;(x0}9x@Z=4;4BRsp?dDVH;H}PdS%{^H2KD5 zOvGOWbiE!%(%8K*yFOV((xhDd8yStHi2+Td-kj*OFnC_87E{-NdOY(}Hq0z-z@LTw}dMb-)WSb^3RMOzE#5 zGa@PUo{;XTd-dKKjl*U)jp(oEv?KrO|CwY3HjSlU?*u;IKL?phzm}<|;eHGVFjKrQ zPz>xS7~QA52mKydLW}f_=PUleXnF>gmh zpFcHE0;#{~b5?AP(ke7iIkWo#So00f9KJt$9CMxnd-Ff-ecJ~Y(f#ZY z@SwdanQM5K0J7?$Q+%_u-mVX6{+0Mbq(KQVy?q|nKL_?(aAb6PC&@s4fX0UmsBbB` z)Qm50b9LZPO2h5!3FKQ+4axK-01L8vCsf_CiWHlC<;S=kxcvNr=^8^w-xwdwa{=IY z-Fw6bJXONSxW-43fWg$PBl%XUXfTJE%)yP2U${lPxsjWfKGgh6Ua4)%r02 zR_uIzfz;hqYxfoCQCkyC>TX3Aod2s0wW0}z3H?ak&3yIUFF&H5@FTEQ6kzR!y=HUx zO?2G)ehT)1^&ev!`X^x<4A_6PN$o$!drTA3=ShH3+X6{+s4btu&VUqFBm782cF}am zrGo=_6g(JfQpWxK?~J)%P9D$lF*83O`r{htlB7S>B@U19QTm0}QNFu^l_^H&NQ}+y zT^mXXDh9aI-+kytzJGI&bwvMod$d>ld!3jOqYmR=PI&nxQvNdJP)W!3q;c!bCmqe1 zg1j2YlWH^iR$KvN1^p%Q${K7kasPgEFl6ysVo&1-$8O(?fZ!`{`a5enA3rbuSpd8{ zp-<Lo!()59VYe2U7Rk#S6{f$Rr!LZiPq&|@yuY!3J`6**@$N7V4; za4HJSNBj~7TV)>9j|n$F`c12^XAsLSMzR?*mgxP;8L=SHO1& zGiit`kNIcNCdkFMMf*%ghC&Qv5@`g;w9~it(_JZDi#ro_?*n zNk)}F!^7CBZLlmq8P;b4_}i-#YT5Rm_O$mpp>B1MBMk9ME?Q>HFpa}1I=H?wn@xn!Eejlnte@#Nl!3pw+m z)nE%ZP%hk8a_R0=|9_4wvXzc5j&Ytast374>&XTI9C|pidbcu|HW3$`c&VhRG5LUpI{ZE+?!Ehb(?s*u z%LJe73ep-(?{5+Y(%U3?MPp5^)hX7cen$$Fc9OT<1#cq^5&xPFDb^6t zkQ9Njw#5BxfvlafUa8UeSx!W^1^vGl;QXdg2gxUeVgu_kBLq-&x}naSL2TC1Q{bUK zxQbbKY`(G)nuZ%;-JRrYdg0U)xi!)zGYPYHIP0>O=K6McnWYKK0pdl~B)Il89qgt@ z7PkxI-{c@szg**P-PdhBF`2(v499=LQ!Vz^O=yahanvlavMZih^D)$V3beUdnP%P?K+P4349`p5v8_q!Y&%p)4$pdo&+6`Y zdpYSMU8Hzl)2^Kuuvc{)nQabWEiTr2AVR}frfzw;Y$v|~Av|SpXP1r{)}J#;Urc-j z``^}*#SR-z_`OaFD8-a>Vnc1cNF4}_b)}(#<7AmIsbDJ#qKF1&Nd5jP_$v<*9C*uK zx3141pvqn?_Q+|xOqu-3sX$_-uMk_GxfPSLjQ8+*``QSN@#?O*9T?Bc{LyyDuCo6x zp`uFfsqL2b@Mv*)P=@AW(3Webnq4SE#`Jss?n{->fb3*5ke|Z3DiQHMCo1tT1}ND5 z_8n?j`%B1WTwKmy-BiQjeg8YXwxEdH$mY9rwIyfnQ7R3q4LACPfh~bJ^m+?nA>{mT z-A3_qPp7Jx{wyc7_iDzs>pk|?nsu%8e_TG9eo|O{ZmaB(u=2XAO&yi1Y3c8KtPlBI ze`Pe>LsJjq0}!-%4zPSF@XQ;r48MJ@Hui!_b2G?eNy>*|ynI5jGH|F@WW+^Vhq|S>6ds+M~7m?V`T6EBaZ%DyX86~?u=%V#=0~qx+e%w*j<_7lxu&kA8syjn$+u+EVA8zXUFYE=*Pl z%qY2xOty&)FiIH7CLQO-;k~Cmruo%#-_6u<%0$v``zovC!%L-Rs7?%l12PxsQ&?EJ z$H!IduRkU(c&3A67$^q%HPVaK#u9JNO!ylrfhcOWe?CZ`i)z=$=E$dA;I*lGXsKkuiK$V zNgBKdZeWQMhzi?YqfUQCu{AB#@gFz!hzX*ADoa7lSH7m0exK!?JS|-bbW;ut$XUc0 zw7Dx%^o;#Hou3RxXk4_CuWlzf33Xc^@mJr8!_=*kD2xZbxvV-fI=Ro#cnfJb+UG6F z`T2-Pl9S!KG$>w?LjX8kRDXzPkdPuJY$gUR^UFAcvHMwJ7s)=t7>eULZ%naKP1sn{ zF}4QZ)&H3|FI*zDGo)fzkkiH(5J5&NHhoc;yh~eZ@+U&nNlh}qUoG2X<-b34TBkC6 zChtlACzQqfoCE7xi&emIpprOm_TpNu^Bfq5A(wB<4H5R?!434qCZNeGs!oObqKf(% zDz(d&8E|xEi;-w-D%BrWQ$Gui{bYz;4+>|k=NEvcpgJs4N0HK@Q5ze)XEr&A6#Edu ztiL3Mric1~l4C`87ZV5Hs5H2KX#WcG2<#R>&_%*}&Q%t}wp*mkh}Ia+#p-pFI9u=z z05*a=#|o((o4>F@G{8O6f+u9(QU8A}R-kwVk#O)x4oG*VninU*fqQd}d!gE5KfYK! zRFTRu_~Uu%#>U7JXXT0HF7!0nG=6oZ`X&cuvk>ZZE!1pDsn)x!n2V)Ap#RA`Ft>x{ z^`o3OO>PADt5y7Fhjag`iUUzCgzbmH1mXfQPv~hq{#qz}GD9|X7-py+IYqXTa3k~M z^r{`A17KJHbmjW*!~Jer@=M7Gk{o9aZF(Qed2UsANrIbdoikFS-hLO(OrQv*c+A$P z2y|=Q?_O1hT3`|(0`7P)x&>q`jbr_rFGzW&Ga+k_78@fGQRQ*WkZ zdKGvIzQ5zWu09DD^Q{U7oF>~<-rke_1f#T05ta5CcmwEqk1$Pi#)PEcTA VpV;*Ox&ExCSV4kQL0 bool: + return self._busy + + def start(self): + if self._busy: + return + self._busy = True + threading.Thread(target=self._run_guarded, daemon=True).start() + + def _run_guarded(self): + try: + self._run() + finally: + self._busy = False + + def _run(self): + playlists, tracks, root, device = self._args + try: + plan = plan_andtunes_sync(playlists, tracks, root, device, + copy_art=self._copy_art, + data_dir=self._data_dir) + free = shutil.disk_usage(device.root).free if device else 0 + except OSError as e: + name = device.name if device else "device" + self.failed.emit(f"Couldn't read the {name}: {e}") + return + self.planned.emit(plan, free) + + class AndTunesSyncWorker(QObject): """Runs an AndTunesPlan on a daemon thread, reporting through signals. @@ -343,6 +395,7 @@ class AndTunesSyncWorker(QObject): self._done = 0 self._plays = 0 # tracks whose play counts came back self._unconverted = 0 # conversions ffmpeg couldn't do + self._unwritable: list[tuple[str, str]] = [] # (song, why) per refusal try: self._check_cancel() @@ -369,6 +422,7 @@ class AndTunesSyncWorker(QObject): "plays": self._plays, "refused": len(plan.refused), "unconverted": self._unconverted, + "unwritable": list(self._unwritable), }) except _Cancelled: self._drop_partial() @@ -481,19 +535,36 @@ class AndTunesSyncWorker(QObject): self._emit(label) continue dest = plan.root / item.rel - dest.parent.mkdir(parents=True, exist_ok=True) - self._dest = dest - # Manual chunked copy: byte-accurate progress, and no copystat - # (gvfs-MTP rejects it). - with fsrc, open(dest, "wb") as fdst: - while True: - chunk = fsrc.read(CHUNK) - if not chunk: - break - fdst.write(chunk) - self._done += len(chunk) - self._emit(label) - self._check_cancel() + # One song the device won't take is one song, not the sync. A + # gvfs-MTP mount can hold a *phantom* directory — one it lists but + # the device doesn't have — and every write into it fails EIO + # forever, since mkdir(exist_ok=True) sees the phantom and does + # nothing. Aborting here cost trav 2,400 songs for one folder; and + # counting the track present anyway (the older sin) put a song in + # library.json that isn't on the device, which the app then skips + # mid-playlist. So: report it, and leave it out of the manifest. + try: + dest.parent.mkdir(parents=True, exist_ok=True) + self._dest = dest + # Manual chunked copy: byte-accurate progress, and no copystat + # (gvfs-MTP rejects it). + with fsrc, open(dest, "wb") as fdst: + while True: + chunk = fsrc.read(CHUNK) + if not chunk: + break + fdst.write(chunk) + self._done += len(chunk) + self._emit(label) + self._check_cancel() + except OSError as e: + fsrc.close() + self._drop_partial() + self._unwritable.append((track_display(item.track), + e.strerror or str(e))) + self._done += item.size + self._emit(label) + continue self._dest = None self._present.add(item.track_id) @@ -535,7 +606,9 @@ class AndTunesSyncWorker(QObject): with holes in it. """ plan = self._plan - for entry in plan.playlists: + total = len(plan.playlists) + for i, entry in enumerate(plan.playlists, start=1): + self._emit(f"Writing playlist {i}/{total}") rows = [row for row, tid in zip(entry.entries, entry.track_ids) if tid in self._present] dest = plan.root / entry.rel @@ -544,9 +617,16 @@ class AndTunesSyncWorker(QObject): # Which covers are on the device right now, rather than which ones # this run wrote — so a cancelled sync still points at art an earlier - # sync left behind. + # sync left behind. One listing of Art/, not a stat per album: over + # MTP that was 500-odd round trips with the progress line frozen on + # the last album, which reads exactly like a hang. + self._emit("Writing the library") + try: + on_device = {p.name for p in (plan.root / ART_DIR).iterdir()} + except OSError: + on_device = set() have_art = {key for key in plan.albums - if (plan.root / art_relpath(key)).exists()} + if Path(art_relpath(key)).name in on_device} entries = [(item.track, item.rel, art_relpath(k) if (k := album_key(item.track)) in have_art else None) @@ -568,6 +648,7 @@ class AndTunesSyncWorker(QObject): file costs a default image, not the sync. """ plan = self._plan + self._emit("Tidying up") folder = assert_inside(plan.root, plan.root / BUTTONS_DIR) for src in default_buttons(): dest = folder / src.name diff --git a/lintunes/gui/main_window.py b/lintunes/gui/main_window.py index 7e647db..2a2f5d1 100644 --- a/lintunes/gui/main_window.py +++ b/lintunes/gui/main_window.py @@ -167,6 +167,7 @@ class MainWindow(QMainWindow): self._sync_cancel.clicked.connect(self._confirm_cancel_sync) self.statusBar().addPermanentWidget(self._sync_cancel) self._andtunes_worker = None + self._andtunes_planner = None self._install_worker = None self._sync_device_name = "" # Export shares the status-bar progress widgets with device sync — @@ -465,8 +466,8 @@ class MainWindow(QMainWindow): def _busy_worker(self): """The transfer currently owning the status-bar progress widgets.""" - for worker in (self._andtunes_worker, self._export_worker, - self._url_worker): + for worker in (self._andtunes_planner, self._andtunes_worker, + self._export_worker, self._url_worker): if worker is not None and worker.busy(): return worker return None @@ -548,17 +549,26 @@ class MainWindow(QMainWindow): "Pick them in Connections → Rabbit Sync Settings…") return + # Planning walks the whole device over MTP — off the GUI thread, or + # the window freezes and GNOME offers to kill it mid-sync. root = andtunes_layout.root_for(device) - try: - plan = andtunes_sync.plan_andtunes_sync( - playlists, self._manager.library.tracks, root, device, - copy_art=bool(self._prefs.device_sync.get("copy_art", True)), - data_dir=self._manager.data_dir) - free = shutil.disk_usage(device.root).free - except OSError as e: - QMessageBox.warning(self, "Sync failed", - f"Couldn't read the {device.name}: {e}") - return + self._andtunes_planner = andtunes_sync.AndTunesPlanWorker( + playlists, self._manager.library.tracks, root, device, + copy_art=bool(self._prefs.device_sync.get("copy_art", True)), + data_dir=self._manager.data_dir, parent=self) + self._andtunes_planner.planned.connect(self._on_andtunes_planned) + self._andtunes_planner.failed.connect(self._on_sync_failed) + self._sync_device_name = device.name + self._sync_label.setText(f"Checking the {device.name}") + self._sync_progress.setRange(0, 0) # busy bar: no total to show yet + self._sync_label.show() + self._sync_progress.show() + self._andtunes_planner.start() + + def _on_andtunes_planned(self, plan, free): + device = plan.device + playlists = plan.playlists + self._hide_sync_widgets() if plan.needs_ffmpeg and not exporter.web_support.ffmpeg_available(): conversions = plan.conversions names = "\n".join(f" • {device_sync.track_display(i.track)}" @@ -618,12 +628,29 @@ class MainWindow(QMainWindow): msg += f", {summary['refused']} left out (can't play there)" if summary.get("unconverted"): msg += f", {summary['unconverted']} couldn't be converted" + unwritable = summary.get("unwritable") or [] + if unwritable: + msg += f", {len(unwritable)} the device wouldn't take" if summary.get("plays"): msg += f"; play counts back for {summary['plays']} song(s)" # The journal landed in the data dir; fold it in now rather than # waiting for the file watcher. self._manager.reload_from_disk() self.statusBar().showMessage(msg, 10000) + if unwritable: + # Worth a dialog: these songs are *not* in the library the app + # reads, so they'd otherwise just be quietly absent from the + # playlist. A stale gvfs mount is the usual cause. + names = "\n".join(f" • {name} — {why}" for name, why in unwritable[:8]) + more = (f"\n … and {len(unwritable) - 8} more" + if len(unwritable) > 8 else "") + QMessageBox.warning( + self, "Some songs wouldn't copy", + f"The {summary['device']} refused " + f"{len(unwritable)} song(s):\n\n{names}{more}\n\n" + "They've been left out of the library on the device, so the " + "app won't list a song it can't play. Unplugging the " + "device and plugging it back in usually clears this.") # ---- connections: install the andTunes app ---- diff --git a/tasks-done.md b/tasks-done.md index 3ece031..930cb52 100644 --- a/tasks-done.md +++ b/tasks-done.md @@ -1,5 +1,42 @@ ## Done +### Round 55 (2026-09-14) — the song that wasn't there (v0.20.4, andTunes 0.2.2) + +trav couldn't play the first track of his playlist: shuffle off gave him song +2, shuffle on gave him a random one. The player was right both times — +`library.json` named a song whose file was not on the device, and skipping an +unplayable file advances one slot. Verified on the Rabbit: tapping any song +that *is* there plays exactly that song, shuffle or not. + +- [x] **The phantom directory.** `Media/Dionne Farris/` existed as far as gvfs + was concerned — it would list the album inside it — and did not exist at + all per `adb`. Every write into it failed `EIO`, and + `mkdir(exist_ok=True)` saw the phantom and did nothing, so it could never + heal while the mount lived. Freshly made folders worked fine, so it was + stale cache, not a broken mount. Only remounting clears it. That folder + was new because Round 52's retag moved the file under a new artist. +- [x] **A refused copy costs that song, not the sync**, and the song is left + out of the m3u and `library.json`. Aborting cost 2,400 songs for one + folder; counting it present anyway put a song in the manifest that isn't + on the device, which is what trav was hearing. The names come back in + the summary and a dialog, so it is never silent. +- [x] **Planning is a worker** (`AndTunesPlanWorker`). It is pure and writes + nothing, but it walks every file on the device, and over MTP that is + thousands of round trips — on the GUI thread it froze the window and + GNOME offered to kill LinTunes, which is how a sync got force-quit + halfway. `disk_usage` rides along, being one more MTP call. +- [x] **The tail of the sync says what it's doing.** "Album art 501/501" is + emitted *before* the last album, and then `_write_index`, + `_ship_buttons` and `_prune_empty_dirs` ran silently — 501 `exists()` + stats over MTP, ~840 KB of writes and a full tree walk with the progress + line frozen. They report now, and `_write_index` reads `Art/` in one + listing instead of a stat per album. +- [x] **The wheel scrolls smoothly, and the other way** (`WheelScroll`). A + detent adds to a pixel debt that a Choreographer callback pays off a + fraction per frame, so one detent eases to a stop and a fast spin blends + into one movement instead of teleporting a row at a time. Direction + flipped in lists only; volume on the other screens is unchanged. + ### Round 54 (2026-09-14) — one artist spelled two ways (v0.20.3, andTunes 0.2.1) trav synced to the Rabbit, the sync reported success, and andTunes answered diff --git a/tests/test_round55.py b/tests/test_round55.py new file mode 100644 index 0000000..db03ab5 --- /dev/null +++ b/tests/test_round55.py @@ -0,0 +1,186 @@ +"""Round 55: a song the device wouldn't take, and a window that stayed alive. + +trav's Rabbit played the wrong song whenever he tapped the first track of his +playlist — shuffle off gave him song 2, shuffle on gave him a random one. The +player was right both times: `library.json` listed a song whose file was not +on the device, and skipping an unplayable file advances one slot. + +The file was missing because a gvfs-MTP mount can hold a **phantom directory** +— one it lists but the device doesn't have. Every write into it fails EIO, and +`mkdir(exist_ok=True)` sees the phantom and does nothing, so it never heals. +Two things were wrong with how sync met that: it aborted the whole run, and +(worse, when it didn't) the track still went into the manifest. + +What's pinned: + +* a copy that raises OSError costs **that song**, not the sync, and the song + is left out of the m3u and `library.json` — the app can only list what it + can play — and comes back in the summary so it isn't silent; +* planning is a **worker**, because it walks every file on the device and over + MTP that froze the window long enough for GNOME to offer to kill LinTunes; +* the phases after the last album (`_write_index`, buttons, pruning) **say so** + — they were minutes of silence with the progress line stuck on the last + album, which reads exactly like a hang. +""" + +import json + +import pytest + +from lintunes.andtunes.sync import ( + AndTunesPlanWorker, AndTunesSyncWorker, plan_andtunes_sync, +) +from lintunes.models import Track +from lintunes.models.playlist import Playlist, PlaylistType + + +@pytest.fixture(autouse=True) +def _private_cache(tmp_path, monkeypatch): + monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path / "cache")) + + +DATA = b"x" * 64 + + +def _track(tid, name, artist="Band", album="Record", *, tmp_path): + path = tmp_path / "local" / f"{tid}.mp3" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(DATA) + return Track(track_id=tid, name=name, artist=artist, album=album, + location=str(path), total_time=60_000, kind="MPEG audio file") + + +def _playlist(*tids): + return Playlist(name="Mix", persistent_id="AAAA0001", + playlist_type=PlaylistType.REGULAR, track_ids=list(tids)) + + +def _root(tmp_path): + return tmp_path / "device" / "Music" / "andTunes" + + +def _run(plan): + worker = AndTunesSyncWorker(plan) + out = {"labels": []} + worker.finished.connect(lambda s: out.setdefault("finished", s)) + worker.failed.connect(lambda m: out.setdefault("failed", m)) + worker.progress.connect(lambda done, total, label: out["labels"].append(label)) + worker._run() + return out + + +def _manifest(root): + return json.loads((root / "library.json").read_text(encoding="utf-8")) + + +def _wedge(monkeypatch, bad_name: str): + """Make open(..., 'wb') raise EIO for one destination, like a phantom dir.""" + real = open + + def fake(file, mode="r", *a, **kw): + if "w" in str(mode) and str(file).endswith(bad_name): + raise OSError(5, "Input/output error", str(file)) + return real(file, mode, *a, **kw) + + monkeypatch.setattr("builtins.open", fake) + + +# ---- one song the device refuses ---- + +def test_a_refused_copy_costs_that_song_not_the_sync(tmp_path, monkeypatch): + tracks = { + 1: _track(1, "Wanted", tmp_path=tmp_path), + 2: _track(2, "Refused", tmp_path=tmp_path), + 3: _track(3, "Also Wanted", tmp_path=tmp_path), + } + plan = plan_andtunes_sync([_playlist(1, 2, 3)], tracks, _root(tmp_path)) + _wedge(monkeypatch, "Refused.mp3") + + out = _run(plan) + + assert "failed" not in out, out.get("failed") + summary = out["finished"] + assert summary["tracks"] == 2 + assert [name for name, _why in summary["unwritable"]] == ["Band - Refused"] + assert "Input/output error" in summary["unwritable"][0][1] + + +def test_a_refused_song_is_left_out_of_the_library_the_app_reads( + tmp_path, monkeypatch): + """The whole point: the app must never list a song it can't play.""" + tracks = { + 1: _track(1, "Wanted", tmp_path=tmp_path), + 2: _track(2, "Refused", tmp_path=tmp_path), + } + root = _root(tmp_path) + plan = plan_andtunes_sync([_playlist(1, 2)], tracks, root) + _wedge(monkeypatch, "Refused.mp3") + + _run(plan) + + manifest = _manifest(root) + assert [t["id"] for t in manifest["tracks"]] == [1] + assert manifest["playlists"][0]["tracks"] == [1] + m3u = (root / "Playlists" / "Mix.m3u").read_text(encoding="utf-8") + assert "Refused" not in m3u + assert not (root / "Media/Band/Record/Refused.mp3").exists() + + +def test_no_refusals_means_no_noise_in_the_summary(tmp_path): + tracks = {1: _track(1, "Fine", tmp_path=tmp_path)} + plan = plan_andtunes_sync([_playlist(1)], tracks, _root(tmp_path)) + + summary = _run(plan)["finished"] + + assert summary["unwritable"] == [] + assert summary["tracks"] == 1 + + +# ---- the tail of the sync says what it's doing ---- + +def test_the_phases_after_the_art_report_progress(tmp_path): + tracks = {1: _track(1, "Fine", tmp_path=tmp_path)} + plan = plan_andtunes_sync([_playlist(1)], tracks, _root(tmp_path)) + + labels = _run(plan)["labels"] + + assert any(l.startswith("Writing playlist") for l in labels), labels + assert "Writing the library" in labels, labels + assert "Tidying up" in labels, labels + # And the index phase is the *last* thing said, not a stale album count. + assert labels[-1] == "Tidying up" + + +# ---- planning is a worker ---- + +def test_planning_runs_off_the_gui_thread_and_reports_a_plan(tmp_path): + tracks = {1: _track(1, "Fine", tmp_path=tmp_path)} + worker = AndTunesPlanWorker( + [_playlist(1)], tracks, _root(tmp_path), None, copy_art=False) + got = {} + worker.planned.connect(lambda plan, free: got.update(plan=plan, free=free)) + worker.failed.connect(lambda m: got.update(failed=m)) + + worker._run() + + assert "failed" not in got, got.get("failed") + assert [i.track_id for i in got["plan"].items] == [1] + assert got["free"] == 0 # no device, so nothing to measure + + +def test_a_device_that_goes_away_while_planning_is_reported_not_raised( + tmp_path, monkeypatch): + tracks = {1: _track(1, "Fine", tmp_path=tmp_path)} + monkeypatch.setattr( + "lintunes.andtunes.sync.plan_andtunes_sync", + lambda *a, **kw: (_ for _ in ()).throw(OSError("device went away"))) + worker = AndTunesPlanWorker( + [_playlist(1)], tracks, _root(tmp_path), None, copy_art=False) + got = {} + worker.planned.connect(lambda plan, free: got.update(plan=plan)) + worker.failed.connect(lambda m: got.update(failed=m)) + + worker._run() + + assert "plan" not in got + assert "device went away" in got["failed"]