@@ -176,6 +176,85 @@ public async Task SingleSong_WriteIndex_GeneratesCorrectIndexFile()
176176 }
177177 }
178178
179+ [ TestMethod ]
180+ public async Task NestedListCsv_AutoIndex_SkipsExistingItemsOnRerun ( )
181+ {
182+ Console . ResetColor ( ) ;
183+ Console . OutputEncoding = Encoding . UTF8 ;
184+ SockseekLog . SetupExceptionHandling ( ) ;
185+ SockseekLog . AddConsole ( ) ;
186+ SockseekLog . SetConsoleLogLevel ( LogLevel . Debug ) ;
187+
188+ var tempRoot = Path . Combine ( Path . GetTempPath ( ) , "slsk-nested-index-" + Guid . NewGuid ( ) ) ;
189+ var sourceDir = Path . Combine ( tempRoot , "src" ) ;
190+ var musicRoot = Path . Combine ( tempRoot , "music" ) ;
191+ var outputDir = Path . Combine ( tempRoot , "out" ) ;
192+ Directory . CreateDirectory ( sourceDir ) ;
193+ Directory . CreateDirectory ( musicRoot ) ;
194+ Directory . CreateDirectory ( outputDir ) ;
195+
196+ var csvPath = Path . Combine ( sourceDir , "songs.csv" ) ;
197+ var listPath = Path . Combine ( sourceDir , "list.txt" ) ;
198+
199+ File . WriteAllText ( csvPath , "artist,title\n Test Artist,First Song\n Test Artist,Second Song\n " ) ;
200+ File . WriteAllText ( listPath , $ "\" { csvPath } \" \n ") ;
201+ File . WriteAllBytes ( Path . Combine ( musicRoot , "Test Artist - First Song.mp3" ) , TestHelpers . EmptyMp3Bytes ) ;
202+ File . WriteAllBytes ( Path . Combine ( musicRoot , "Test Artist - Second Song.mp3" ) , TestHelpers . EmptyMp3Bytes ) ;
203+
204+ var engineSettings = new EngineSettings { Username = "test_user" , Password = "test_pass" } ;
205+ var settings = new DownloadSettings ( ) ;
206+ settings . Extraction . Input = listPath ;
207+ settings . Extraction . InputType = InputType . List ;
208+ settings . Output . ParentDir = outputDir ;
209+ settings . Output . NameFormat = "{filename}" ;
210+
211+ async Task RunAsync ( LocalFilesSoulseekClient client )
212+ {
213+ var app = new DownloadEngine ( engineSettings , TestHelpers . CreateMockClientManager ( client , engineSettings ) ) ;
214+ app . Enqueue ( new ExtractJob ( listPath , InputType . List ) , settings ) ;
215+ app . CompleteEnqueue ( ) ;
216+ await app . RunAsync ( CancellationToken . None ) ;
217+ }
218+
219+ try
220+ {
221+ await RunAsync ( LocalFilesSoulseekClient . FromLocalPaths ( useTags : false , slowMode : false , musicRoot ) ) ;
222+
223+ var listIndexPath = Path . Combine ( outputDir , "list" , "_index.csv" ) ;
224+ var csvIndexPath = Path . Combine ( outputDir , "songs" , "_index.csv" ) ;
225+ Assert . IsTrue ( File . Exists ( listIndexPath ) , "The outer list should auto-create an index file." ) ;
226+ Assert . IsTrue ( File . Exists ( csvIndexPath ) , "The nested CSV should auto-create an index file." ) ;
227+
228+ var firstRunCsvIndex = File . ReadAllLines ( csvIndexPath ) . Where ( line => line . Length > 0 ) . ToArray ( ) ;
229+ Assert . AreEqual ( "filepath,artist,album,title,length,tracktype,state,failurereason" , firstRunCsvIndex [ 0 ] ) ;
230+ Assert . AreEqual ( 3 , firstRunCsvIndex . Length , string . Join ( "\n " , firstRunCsvIndex ) ) ;
231+ Assert . IsTrue ( firstRunCsvIndex . Any ( line => line . Contains ( ",Test Artist,,First Song," ) && line . EndsWith ( ",0,1,0" ) ) ,
232+ string . Join ( "\n " , firstRunCsvIndex ) ) ;
233+ Assert . IsTrue ( firstRunCsvIndex . Any ( line => line . Contains ( ",Test Artist,,Second Song," ) && line . EndsWith ( ",0,1,0" ) ) ,
234+ string . Join ( "\n " , firstRunCsvIndex ) ) ;
235+
236+ var downloadedBeforeRerun = Directory . GetFiles ( outputDir , "*.mp3" , SearchOption . AllDirectories ) ;
237+ Assert . AreEqual ( 2 , downloadedBeforeRerun . Length , string . Join ( "\n " , downloadedBeforeRerun ) ) ;
238+
239+ await RunAsync ( LocalFilesSoulseekClient . FromLocalPaths ( useTags : false , slowMode : false , failDownloads : 10 , musicRoot ) ) ;
240+
241+ var secondRunCsvIndex = File . ReadAllLines ( csvIndexPath ) . Where ( line => line . Length > 0 ) . ToArray ( ) ;
242+ Assert . AreEqual ( 3 , secondRunCsvIndex . Length , string . Join ( "\n " , secondRunCsvIndex ) ) ;
243+ Assert . IsTrue ( secondRunCsvIndex . Any ( line => line . Contains ( ",Test Artist,,First Song," ) && line . EndsWith ( ",0,3,0" ) ) ,
244+ string . Join ( "\n " , secondRunCsvIndex ) ) ;
245+ Assert . IsTrue ( secondRunCsvIndex . Any ( line => line . Contains ( ",Test Artist,,Second Song," ) && line . EndsWith ( ",0,3,0" ) ) ,
246+ string . Join ( "\n " , secondRunCsvIndex ) ) ;
247+
248+ var downloadedAfterRerun = Directory . GetFiles ( outputDir , "*.mp3" , SearchOption . AllDirectories ) ;
249+ Assert . AreEqual ( 2 , downloadedAfterRerun . Length , "The rerun should skip existing indexed tracks instead of downloading again." ) ;
250+ }
251+ finally
252+ {
253+ if ( Directory . Exists ( tempRoot ) )
254+ Directory . Delete ( tempRoot , true ) ;
255+ }
256+ }
257+
179258 [ TestMethod ]
180259 public async Task SingleSong_WriteIndex_UsesNameFormattedPath ( )
181260 {
0 commit comments