├── src └── MusicManager │ ├── MusicManager.Presentation.Test │ ├── Files │ │ ├── Corrupt.wma │ │ ├── TestAAC.m4a │ │ ├── TestMP3.mp3 │ │ ├── TestMP4.mp4 │ │ ├── TestWAV.wav │ │ ├── TestWMA.wma │ │ └── TestFlac.flac │ ├── Converters │ │ ├── PathToDirectoryNameConverterTest.cs │ │ ├── PathToFileNameConverterTest.cs │ │ ├── IsPlaylistItemPlayingMultiConverterTest.cs │ │ ├── ConverterHelperTest.cs │ │ ├── FilterOperatorToStringConverterTest.cs │ │ ├── TotalDurationConverterTest.cs │ │ ├── UIntToDisplayValueConverterTest.cs │ │ ├── MusicPropertiesProgressVisibilityConverterTest.cs │ │ ├── WindowTitleConverterTest.cs │ │ ├── MusicPropertiesEnabledConverterTest.cs │ │ ├── MusicTitleConverterTest.cs │ │ └── RatingToStarsConverterTest.cs │ ├── Services │ │ ├── SupportedFileTypesTest.cs │ │ ├── ListSortComparerTest.cs │ │ ├── SequenceEqualityComparerTest.cs │ │ └── StringListConverterTest.cs │ ├── TestHelper.cs │ ├── DisposableTest.cs │ ├── MusicManager.Presentation.Test.csproj │ ├── PresentationTest.cs │ ├── Controls │ │ └── SearchableTextBlockTest.cs │ └── Controllers │ │ └── PlaylistControllerIntegrationTest.cs │ ├── MusicManager.Domain │ ├── GlobalSuppressions.cs │ ├── Playlists │ │ ├── IRandomService.cs │ │ ├── PlaylistItem.cs │ │ ├── RandomService.cs │ │ ├── StatisticsHelper.cs │ │ └── PlayedItemsStack.cs │ ├── Log.cs │ ├── MusicFiles │ │ ├── IChangeTrackerService.cs │ │ ├── ServiceLocator.cs │ │ ├── Genres.cs │ │ └── Entity.cs │ ├── Transcoding │ │ ├── TranscodeStatus.cs │ │ ├── TranscodingManager.cs │ │ └── TranscodeItem.cs │ └── MusicManager.Domain.csproj │ ├── MusicManager.Applications │ ├── GlobalSuppressions.cs │ ├── Services │ │ ├── IClipboardService.cs │ │ ├── IPlaylistService.cs │ │ ├── IManagerStatusService.cs │ │ ├── ApplicationBusyContext.cs │ │ ├── IMusicPropertiesService.cs │ │ ├── ITranscoder.cs │ │ ├── IEnvironmentService.cs │ │ ├── TranscodingTaskEventArgs.cs │ │ ├── PlayerServiceExtensions.cs │ │ ├── ISelectionService.cs │ │ ├── IMusicFileContext.cs │ │ ├── ShellServiceExtensions.cs │ │ ├── IFileSystemWatcherService.cs │ │ ├── MusicTitleHelper.cs │ │ ├── ITranscodingService.cs │ │ ├── ChangeTrackerService.cs │ │ ├── SelectionService.cs │ │ ├── IPlayerService.cs │ │ ├── ManagerStatusService.cs │ │ ├── IFileService.cs │ │ ├── IShellService.cs │ │ ├── TranscodingService.cs │ │ └── PlayerService.cs │ ├── Views │ │ ├── IManagerView.cs │ │ ├── IMusicPropertiesView.cs │ │ ├── ITranscodingListView.cs │ │ ├── IInfoView.cs │ │ ├── IPlayerView.cs │ │ ├── IPlaylistView.cs │ │ └── IShellView.cs │ ├── DataModels │ │ ├── FilterOperator.cs │ │ ├── FolderBrowserDataModel.cs │ │ └── MusicFileDataModel.cs │ ├── Log.cs │ ├── Properties │ │ ├── PlaylistSettings.cs │ │ └── AppSettings.cs │ ├── MusicManager.Applications.csproj │ ├── ViewModels │ │ ├── TranscodingListViewModel.cs │ │ ├── InfoViewModel.cs │ │ └── PlayerViewModel.cs │ ├── TaskUtility.cs │ └── ApplicationsModule.cs │ ├── MusicManager.Presentation │ ├── GlobalSuppressions.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── PublishProfiles │ │ │ └── ClickOnceProfile.pubxml │ ├── Resources │ │ ├── Images │ │ │ └── MusicManager.ico │ │ ├── LayoutResources.xaml │ │ ├── DataResources.xaml │ │ └── BrushResources.xaml │ ├── Services │ │ ├── Metadata │ │ │ ├── AacReadMetadata.cs │ │ │ ├── AacSaveMetadata.cs │ │ │ ├── WmaReadMetadata.cs │ │ │ ├── WmaSaveMetadata.cs │ │ │ ├── MkvReadMetadata.cs │ │ │ ├── MkvSaveMetadata.cs │ │ │ ├── WavReadMetadata.cs │ │ │ ├── WavSaveMetadata.cs │ │ │ ├── Mp4SaveMetadata.cs │ │ │ ├── Mp4ReadMetadata.cs │ │ │ ├── FlacReadMetadata.cs │ │ │ ├── FlacSaveMetadata.cs │ │ │ ├── Mp3ReadMetadata.cs │ │ │ └── Mp3SaveMetadata.cs │ │ ├── PropertyNames.cs │ │ ├── ClipboardService.cs │ │ ├── ListSortComparer.cs │ │ ├── SequenceEqualityComparer.cs │ │ ├── StringListConverter.cs │ │ ├── EnvironmentService.cs │ │ ├── Transcoder.cs │ │ └── FileSystemWatcherService.cs │ ├── Controls │ │ ├── RatingItemState.cs │ │ ├── HorizontalFlyoutAlignment.cs │ │ ├── SuperToolTip.cs │ │ ├── InsertMarkerAdorner.cs │ │ └── SearchBox.cs │ ├── DesignData │ │ ├── MockView.cs │ │ ├── SampleMusicFile.cs │ │ ├── SampleInfoViewModel.cs │ │ ├── SampleMusicPropertiesViewModel.cs │ │ ├── MockSelectionService.cs │ │ ├── SamplePlayerViewModel.cs │ │ ├── MockShellService.cs │ │ ├── SampleManagerViewModel.cs │ │ ├── SampleShellViewModel.cs │ │ ├── SamplePlaylistViewModel.cs │ │ └── SampleTranscodingListViewModel.cs │ ├── Log.cs │ ├── Views │ │ ├── MusicPropertiesView.xaml.cs │ │ └── InfoWindow.xaml.cs │ ├── Converters │ │ ├── ConverterHelper.cs │ │ ├── PathToDirectoryNameConverter.cs │ │ ├── BitrateConverter.cs │ │ ├── WindowTitleConverter.cs │ │ ├── PathToFileNameConverter.cs │ │ ├── ItemsCountConverter.cs │ │ ├── UIntToDisplayValueConverter.cs │ │ ├── IsPlaylistItemPlayingMultiConverter.cs │ │ ├── MusicPropertiesProgressVisibilityConverter.cs │ │ ├── MusicPropertiesEnabledConverter.cs │ │ ├── StringListToStringConverter.cs │ │ ├── TotalDurationConverter.cs │ │ ├── DurationConverter.cs │ │ ├── MusicTitleConverter.cs │ │ ├── ErrorMessagesConverter.cs │ │ ├── RatingToStarsConverter.cs │ │ ├── TranscodeStatusToStringConverter.cs │ │ └── FilterOperatorToStringConverter.cs │ ├── Disposable.cs │ ├── Themes │ │ ├── Generic.xaml │ │ └── Generic │ │ │ └── SuperToolTip.xaml │ ├── App.xaml │ ├── MusicManager.Presentation.csproj │ └── PresentationModule.cs │ ├── MusicManager.Packaging │ ├── Images │ │ ├── StoreLogo.backup.png │ │ ├── LargeTile.scale-100.png │ │ ├── LargeTile.scale-125.png │ │ ├── LargeTile.scale-150.png │ │ ├── LargeTile.scale-200.png │ │ ├── LargeTile.scale-400.png │ │ ├── SmallTile.scale-100.png │ │ ├── SmallTile.scale-125.png │ │ ├── SmallTile.scale-150.png │ │ ├── SmallTile.scale-200.png │ │ ├── SmallTile.scale-400.png │ │ ├── StoreLogo.scale-100.png │ │ ├── StoreLogo.scale-125.png │ │ ├── StoreLogo.scale-150.png │ │ ├── StoreLogo.scale-200.png │ │ ├── StoreLogo.scale-400.png │ │ ├── SplashScreen.scale-100.png │ │ ├── SplashScreen.scale-125.png │ │ ├── SplashScreen.scale-150.png │ │ ├── SplashScreen.scale-200.png │ │ ├── SplashScreen.scale-400.png │ │ ├── LockScreenLogo.scale-200.png │ │ ├── Square44x44Logo.scale-100.png │ │ ├── Square44x44Logo.scale-125.png │ │ ├── Square44x44Logo.scale-150.png │ │ ├── Square44x44Logo.scale-200.png │ │ ├── Square44x44Logo.scale-400.png │ │ ├── Wide310x150Logo.scale-100.png │ │ ├── Wide310x150Logo.scale-125.png │ │ ├── Wide310x150Logo.scale-150.png │ │ ├── Wide310x150Logo.scale-200.png │ │ ├── Wide310x150Logo.scale-400.png │ │ ├── Square150x150Logo.scale-100.png │ │ ├── Square150x150Logo.scale-125.png │ │ ├── Square150x150Logo.scale-150.png │ │ ├── Square150x150Logo.scale-200.png │ │ ├── Square150x150Logo.scale-400.png │ │ ├── Square44x44Logo.targetsize-16.png │ │ ├── Square44x44Logo.targetsize-24.png │ │ ├── Square44x44Logo.targetsize-256.png │ │ ├── Square44x44Logo.targetsize-32.png │ │ ├── Square44x44Logo.targetsize-48.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-16.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-256.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-32.png │ │ ├── Square44x44Logo.altform-unplated_targetsize-48.png │ │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ │ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png │ │ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png │ │ ├── Square44x44Logo.altform-lightunplated_targetsize-256.png │ │ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png │ │ └── Square44x44Logo.altform-lightunplated_targetsize-48.png │ └── Package.appxmanifest │ ├── MusicManager.Applications.Test │ ├── Views │ │ ├── MockManagerView.cs │ │ ├── MockInfoView.cs │ │ ├── MockMusicPropertiesView.cs │ │ ├── MockTranscodingListView.cs │ │ ├── MockPlayerView.cs │ │ ├── MockPlaylistView.cs │ │ └── MockShellView.cs │ ├── Services │ │ ├── MockClipboardService.cs │ │ ├── MockEnvironmentService.cs │ │ ├── MockTranscoder.cs │ │ ├── MusicTitleHelperTest.cs │ │ ├── ManagerStatusServiceTest.cs │ │ ├── MockFileSystemWatcherService.cs │ │ ├── MockMusicFileContext.cs │ │ └── MockFileService.cs │ ├── DataModels │ │ ├── FolderItemTest.cs │ │ └── MusicFileDataModelTest.cs │ ├── ViewModels │ │ ├── ManagerViewModelTest.cs │ │ └── InfoViewModelTest.cs │ ├── MusicManager.Applications.Test.csproj │ ├── ApplicationsTest.cs │ └── MockPresentationModule.cs │ ├── MusicManager.Domain.Test │ ├── MusicFiles │ │ ├── MockChangeTrackerService.cs │ │ ├── GenresTest.cs │ │ ├── MockMusicFile.cs │ │ ├── MusicMetadataTest.cs │ │ └── EntityTest.cs │ ├── Playlists │ │ ├── MockRandomService.cs │ │ ├── StatisticsHelperTest.cs │ │ ├── RandomServiceTest.cs │ │ └── PlayedItemsStackTest.cs │ ├── MusicManager.Domain.Test.csproj │ ├── AssertUnobservedExceptions.cs │ ├── Transcoding │ │ ├── TranscodingManagerTest.cs │ │ └── TranscodeItemTest.cs │ └── DomainTest.cs │ ├── Directory.Packages.props │ ├── .editorconfig │ ├── Directory.Build.props │ └── CodeCoverage.runsettings ├── CONTRIBUTING.md ├── README.md ├── .gitignore ├── LICENSE └── .github └── workflows └── main.yml /src/MusicManager/MusicManager.Presentation.Test/Files/Corrupt.wma: -------------------------------------------------------------------------------- 1 | corrupt -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | I'm not ready to accept contributions at this time. Please post any feedback to the Issues list. Thank you! 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waf Music Manager 2 | Please see [jbe2277.github.io/musicmanager](https://jbe2277.github.io/musicmanager) for more information. -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Domain/GlobalSuppressions.cs -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Applications/GlobalSuppressions.cs -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestAAC.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestAAC.m4a -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestMP3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestMP3.mp3 -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestMP4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestMP4.mp4 -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestWAV.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestWAV.wav -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestWMA.wma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestWMA.wma -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation/GlobalSuppressions.cs -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.backup.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Files/TestFlac.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation.Test/Files/TestFlac.flac -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LargeTile.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SmallTile.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/StoreLogo.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/SplashScreen.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Wide310x150Logo.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 4 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Resources/Images/MusicManager.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Presentation/Resources/Images/MusicManager.ico -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-100.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-125.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-150.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square150x150Logo.scale-400.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/AacReadMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class AacReadMetadata : Mp4ReadMetadata 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/AacSaveMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class AacSaveMetadata : Mp4SaveMetadata 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/WmaReadMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class WmaReadMetadata : ReadMetadata 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/WmaSaveMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class WmaSaveMetadata : SaveMetadata 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Playlists/IRandomService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Playlists; 2 | 3 | public interface IRandomService 4 | { 5 | int NextRandomNumber(int maxValue); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-16.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-24.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-256.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-32.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-48.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IClipboardService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public interface IClipboardService 4 | { 5 | void SetText(string text); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IManagerView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface IManagerView : IView 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Controls/RatingItemState.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Controls; 2 | 3 | public enum RatingItemState 4 | { 5 | Empty, 6 | Partial, 7 | Filled 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IMusicPropertiesView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface IMusicPropertiesView : IView 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/ITranscodingListView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface ITranscodingListView : IView 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Log.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | 3 | namespace Waf.MusicManager.Domain; 4 | 5 | internal static class Log 6 | { 7 | public static Logger Default { get; } = LogManager.GetLogger("MusicManager.D"); 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/MusicFiles/IChangeTrackerService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.MusicFiles; 2 | 3 | public interface IChangeTrackerService 4 | { 5 | void EntityHasChanges(Entity entity); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/PropertyNames.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services; 2 | 3 | internal static class PropertyNames 4 | { 5 | public static string Artist => "System.Music.Artist"; 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Controls/HorizontalFlyoutAlignment.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Controls; 2 | 3 | public enum HorizontalFlyoutAlignment 4 | { 5 | Left, 6 | Center, 7 | Right 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/DataModels/FilterOperator.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.DataModels; 2 | 3 | public enum FilterOperator 4 | { 5 | Ignore, 6 | GreaterThanOrEqual, 7 | LessThanOrEqual 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Log.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | 3 | namespace Waf.MusicManager.Applications; 4 | 5 | internal static class Log 6 | { 7 | public static Logger Default { get; } = LogManager.GetLogger("MusicManager.A"); 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-16.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-256.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-32.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-unplated_targetsize-48.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IInfoView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface IInfoView : IView 6 | { 7 | void ShowDialog(object owner); 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Transcoding/TranscodeStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Transcoding; 2 | 3 | public enum TranscodeStatus 4 | { 5 | InProgress, 6 | Pending, 7 | Error, 8 | Completed 9 | } 10 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-16.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-24.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-256.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-32.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbe2277/musicmanager/HEAD/src/MusicManager/MusicManager.Packaging/Images/Square44x44Logo.altform-lightunplated_targetsize-48.png -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/MkvReadMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class MkvReadMetadata : ReadMetadata 4 | { 5 | protected override bool IsSupported => false; 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/MkvSaveMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class MkvSaveMetadata : SaveMetadata 4 | { 5 | protected override bool IsSupported => false; 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/WavReadMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class WavReadMetadata : ReadMetadata 4 | { 5 | protected override bool IsSupported => false; 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/WavSaveMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services.Metadata; 2 | 3 | internal class WavSaveMetadata : SaveMetadata 4 | { 5 | protected override bool IsSupported => false; 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/MockView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Presentation.DesignData; 4 | 5 | public class MockView : IView 6 | { 7 | public object? DataContext { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IPlaylistService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | internal interface IPlaylistService 6 | { 7 | void TrySelectMusicFile(MusicFile? musicFile); 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockManagerView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockManagerView : MockView, IManagerView 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Playlists/PlaylistItem.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Domain.Playlists; 4 | 5 | public class PlaylistItem(MusicFile musicFile) : Model 6 | { 7 | public MusicFile MusicFile { get; } = musicFile; 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IManagerStatusService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public interface IManagerStatusService : INotifyPropertyChanged 4 | { 5 | bool UpdatingFilesList { get; } 6 | 7 | int TotalFilesCount { get; } 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockInfoView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockInfoView : MockDialogView, IInfoView 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IPlayerView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface IPlayerView : IView 6 | { 7 | TimeSpan GetPosition(); 8 | 9 | void SetPosition(TimeSpan position); 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockMusicPropertiesView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockMusicPropertiesView : MockView, IMusicPropertiesView 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockTranscodingListView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockTranscodingListView : MockView, ITranscodingListView 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ApplicationBusyContext.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | internal sealed class ApplicationBusyContext(Action disposeCallback) : IDisposable 4 | { 5 | public void Dispose() => disposeCallback(this); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IMusicPropertiesService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | internal interface IMusicPropertiesService 6 | { 7 | void SelectMusicFiles(IReadOnlyList musicFiles); 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ITranscoder.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public interface ITranscoder 4 | { 5 | Task TranscodeAsync(string sourceFileName, string destinationFileName, uint bitrate, CancellationToken cancellationToken, IProgress progress); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Log.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | 3 | namespace Waf.MusicManager.Presentation; 4 | 5 | internal static class Log 6 | { 7 | public static Logger Default { get; } = LogManager.GetLogger("MusicManager.P"); 8 | 9 | public static Logger App { get; } = LogManager.GetLogger("App"); 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IEnvironmentService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public interface IEnvironmentService 4 | { 5 | IReadOnlyList MusicFilesToLoad { get; } 6 | 7 | string MusicPath { get; } 8 | 9 | string PublicMusicPath { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/ClipboardService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Presentation.Services; 5 | 6 | internal class ClipboardService : IClipboardService 7 | { 8 | public void SetText(string text) => Clipboard.SetText(text); 9 | } 10 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/TranscodingTaskEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public class TranscodingTaskEventArgs(string fileName, Task transcodingTask) : EventArgs 4 | { 5 | public string FileName { get; } = fileName; 6 | 7 | public Task TranscodingTask { get; } = transcodingTask; 8 | } 9 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/ListSortComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services; 2 | 3 | public class ListSortComparer(Comparison comparison, ListSortDirection sortDirection) : IComparer 4 | { 5 | public int Compare(T? x, T? y) => sortDirection == ListSortDirection.Ascending ? comparison(x, y) : comparison(y, x); 6 | } 7 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Views/MusicPropertiesView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Views; 2 | 3 | namespace Waf.MusicManager.Presentation.Views; 4 | 5 | public partial class MusicPropertiesView : IMusicPropertiesView 6 | { 7 | public MusicPropertiesView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/PlayerServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | public static class PlayerServiceExtensions 4 | { 5 | public static void Play(this IPlayerService playerService) 6 | { 7 | if (playerService.IsPlayCommand) playerService.PlayPauseCommand.Execute(null); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ISelectionService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.DataModels; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public interface ISelectionService 6 | { 7 | ObservableListViewCore MusicFiles { get; } 8 | 9 | ObservableList SelectedMusicFiles { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleMusicFile.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Presentation.DesignData; 4 | 5 | public class SampleMusicFile : MusicFile 6 | { 7 | public SampleMusicFile(MusicMetadata metadata, string fileName) : base(x => Task.FromResult(metadata)!, fileName) 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockClipboardService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Test.MusicManager.Applications.Services; 4 | 5 | public class MockClipboardService : IClipboardService 6 | { 7 | public Action? SetTextAction { get; set; } 8 | 9 | public void SetText(string text) => SetTextAction?.Invoke(text); 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IPlaylistView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | using Waf.MusicManager.Domain.Playlists; 3 | 4 | namespace Waf.MusicManager.Applications.Views; 5 | 6 | public interface IPlaylistView : IView 7 | { 8 | void FocusSearchBox(); 9 | 10 | void FocusSelectedItem(); 11 | 12 | void ScrollIntoView(PlaylistItem item); 13 | } 14 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/ConverterHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Converters; 2 | 3 | internal static class ConverterHelper 4 | { 5 | public static bool IsParameterSet(string expectedParameter, object? actualParameter) 6 | { 7 | return string.Equals(actualParameter as string, expectedParameter, StringComparison.OrdinalIgnoreCase); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicFiles/MockChangeTrackerService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Test.MusicManager.Domain.MusicFiles; 4 | 5 | internal class MockChangeTrackerService : IChangeTrackerService 6 | { 7 | public Action? EntityHasChangesAction { get; set; } 8 | 9 | public void EntityHasChanges(Entity entity) => EntityHasChangesAction?.Invoke(entity); 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicFiles/GenresTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Domain.MusicFiles; 3 | 4 | namespace Test.MusicManager.Domain.MusicFiles; 5 | 6 | [TestClass] 7 | public class GenresTest : DomainTest 8 | { 9 | [TestMethod] 10 | public void DefaultValuesTest() 11 | { 12 | Assert.IsTrue(Genres.DefaultValues.Count > 5); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Playlists/RandomService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Playlists; 2 | 3 | internal class RandomService : IRandomService 4 | { 5 | public int NextRandomNumber(int maxValue) 6 | { 7 | if (maxValue == int.MaxValue) throw new ArgumentOutOfRangeException(nameof(maxValue), "maxValue must be lower than Int32.MaxValue"); 8 | return new Random().Next(maxValue + 1); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IMusicFileContext.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public interface IMusicFileContext 6 | { 7 | MusicFile Create(string fileName); 8 | 9 | MusicFile CreateFromMultiple(IReadOnlyList musicFiles); 10 | 11 | void ApplyChanges(MusicFile musicFile); 12 | 13 | Task SaveChangesAsync(MusicFile musicFile); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | .vs/ 6 | *.suo 7 | *.user 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Rr]elease/ 12 | [Bb]in/ 13 | [Oo]bj/ 14 | 15 | # Test results 16 | TestResults/ 17 | 18 | # NuGet Packages Directory 19 | packages/ 20 | 21 | # Packaging 22 | AppPackages/ 23 | BundleArtifacts/ 24 | *.assets.cache 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockEnvironmentService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Test.MusicManager.Applications.Services; 4 | 5 | public class MockEnvironmentService : IEnvironmentService 6 | { 7 | public IReadOnlyList MusicFilesToLoad { get; set; } = []; 8 | 9 | public string MusicPath { get; set; } = ""; 10 | 11 | public string PublicMusicPath { get; set; } = ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ShellServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public static class ShellServiceExtensions 6 | { 7 | public static void ShowError(this IShellService shellService, Exception exception, string format, params object?[] args) 8 | { 9 | shellService.ShowError(exception, string.Format(CultureInfo.CurrentCulture, format, args)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Views/InfoWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Waf.MusicManager.Presentation.Views; 5 | 6 | public partial class InfoWindow : IInfoView 7 | { 8 | public InfoWindow() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | public void ShowDialog(object owner) 14 | { 15 | Owner = owner as Window; 16 | ShowDialog(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Playlists/MockRandomService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.Playlists; 2 | 3 | namespace Test.MusicManager.Domain.MusicFiles; 4 | 5 | public class MockRandomService : IRandomService 6 | { 7 | public Func? NextRandomNumberStub { get; set; } 8 | 9 | public int NextRandomNumber(int maxValue) => NextRandomNumberStub?.Invoke(maxValue) ?? throw new InvalidOperationException(nameof(NextRandomNumberStub) + " is not set"); 10 | } 11 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockPlayerView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockPlayerView : MockView, IPlayerView 7 | { 8 | public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(33); 9 | 10 | public TimeSpan GetPosition() => Position; 11 | 12 | public void SetPosition(TimeSpan position) => Position = position; 13 | } 14 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/MusicFiles/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace Waf.MusicManager.Domain.MusicFiles; 4 | 5 | public static class ServiceLocator 6 | { 7 | private static readonly ConcurrentDictionary services = new(); 8 | 9 | public static TId Get() where TId : class => (TId)services[typeof(TId)]; 10 | 11 | public static void RegisterInstance(TId instance) where TId : class => services[typeof(TId)] = instance; 12 | } 13 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IFileSystemWatcherService.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public interface IFileSystemWatcherService 6 | { 7 | NotifyFilters NotifyFilter { get; set; } 8 | 9 | string Path { get; set; } 10 | 11 | bool EnableRaisingEvents { get; set; } 12 | 13 | event FileSystemEventHandler? Created; 14 | 15 | event RenamedEventHandler? Renamed; 16 | 17 | event FileSystemEventHandler? Deleted; 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/MusicTitleHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public static class MusicTitleHelper 6 | { 7 | public static string GetTitleText(string? fileName, IEnumerable? artists, string? title) 8 | { 9 | artists ??= []; 10 | var result = string.IsNullOrEmpty(title) && !artists.Any() ? Path.GetFileNameWithoutExtension(fileName) : title; 11 | return result ?? ""; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleInfoViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.ViewModels; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Waf.MusicManager.Presentation.DesignData; 5 | 6 | public class SampleInfoViewModel : InfoViewModel 7 | { 8 | public SampleInfoViewModel() : base(new MockInfoView()) 9 | { 10 | } 11 | 12 | private class MockInfoView : MockView, IInfoView 13 | { 14 | public void ShowDialog(object owner) { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ITranscodingService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public interface ITranscodingService : INotifyPropertyChanged 6 | { 7 | ICommand ConvertToMp3AllCommand { get; } 8 | 9 | ICommand ConvertToMp3SelectedCommand { get; } 10 | 11 | ICommand CancelAllCommand { get; } 12 | 13 | ICommand CancelSelectedCommand { get; } 14 | 15 | event EventHandler? TranscodingTaskCreated; 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Disposable.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation; 2 | 3 | public abstract class Disposable : IDisposable 4 | { 5 | private int isDisposed; 6 | 7 | public void Dispose() 8 | { 9 | DisposeCore(true); 10 | GC.SuppressFinalize(this); 11 | } 12 | 13 | protected virtual void Dispose(bool isDisposing) { } 14 | 15 | private void DisposeCore(bool isDisposing) 16 | { 17 | if (Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0) Dispose(isDisposing); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/DataModels/FolderItemTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Test.MusicManager.Applications.DataModels; 5 | 6 | [TestClass] 7 | public class FolderItemTest : ApplicationsTest 8 | { 9 | [TestMethod] 10 | public void ConstructorTest() 11 | { 12 | var folderItem = new FolderItem("a", "b"); 13 | Assert.AreEqual("a", folderItem.Path); 14 | Assert.AreEqual("b", folderItem.DisplayName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicManager.Domain.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | Test.MusicManager.Domain 5 | Test.MusicManager.Domain 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Playlists/StatisticsHelperTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Domain.Playlists; 3 | 4 | namespace Test.MusicManager.Domain.MusicFiles; 5 | 6 | [TestClass] 7 | public class StatisticsHelperTest 8 | { 9 | [TestMethod] 10 | public void TruncatedMeanTest() 11 | { 12 | Assert.AreEqual(0, StatisticsHelper.TruncatedMean([], 0.25)); 13 | 14 | Assert.AreEqual(6.5, StatisticsHelper.TruncatedMean([ 5, 8, 4, 38, 8, 6, 9, 7, 7, 3, 1, 6 ], 0.25)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/MusicManager.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | Waf.MusicManager.Domain 5 | Waf.MusicManager.Domain 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ChangeTrackerService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | internal class ChangeTrackerService : IChangeTrackerService 6 | { 7 | private readonly HashSet entitiesWithChanges = []; 8 | 9 | public IEnumerable GetEntitiesWithChanges() => entitiesWithChanges.ToArray(); 10 | 11 | public void EntityHasChanges(Entity entity) => entitiesWithChanges.Add(entity); 12 | 13 | public void RemoveEntity(Entity entity) => entitiesWithChanges.Remove(entity); 14 | } 15 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Resources/LayoutResources.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 6 | 7 | 11 8 | 9 | 4 10 | 11 | 9 12 | 13 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/PathToDirectoryNameConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Converters; 3 | 4 | namespace Test.MusicManager.Presentation.Converters; 5 | 6 | [TestClass] 7 | public class PathToDirectoryNameConverterTest 8 | { 9 | [TestMethod] 10 | public void ConvertTest() 11 | { 12 | var converter = PathToDirectoryNameConverter.Default; 13 | Assert.AreEqual(@"C:\Users\Test\Music", converter.Convert(@"C:\Users\Test\Music\Test - File.mp3", null, null, null)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/MusicFiles/Genres.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.MusicFiles; 2 | 3 | public static class Genres 4 | { 5 | public static IReadOnlyList DefaultValues { get; } = [ 6 | "Blues", 7 | "Classical", 8 | "Comedy", 9 | "Country", 10 | "Dance", 11 | "Easy Listening", 12 | "Electronic", 13 | "Funk", 14 | "Hip Hop", 15 | "Jazz", 16 | "Oldies", 17 | "Pop", 18 | "R&B", 19 | "Reggae", 20 | "Rock", 21 | "Soundtrack", 22 | "Techno" 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Playlists/StatisticsHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Playlists; 2 | 3 | internal static class StatisticsHelper 4 | { 5 | public static double TruncatedMean(IReadOnlyList values, double truncateRate) 6 | { 7 | var count = values.Count; 8 | if (count < 1) return 0; 9 | 10 | int truncateCount = (int)(count * truncateRate); 11 | var truncatedValues = values.OrderBy(x => x).Skip(truncateCount).Take(count - 2 * truncateCount); 12 | double truncatedMean = truncatedValues.Average(); 13 | return truncatedMean; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/ViewModels/ManagerViewModelTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Applications.ViewModels; 3 | 4 | namespace Test.MusicManager.Applications.ViewModels; 5 | 6 | [TestClass] 7 | public class ManagerViewModelTest : ApplicationsTest 8 | { 9 | [TestMethod] 10 | public void ClearSearchCommandTest() 11 | { 12 | var viewModel = Get(); 13 | viewModel.SearchFilter.UserSearchFilter = "test"; 14 | viewModel.ClearSearchCommand.Execute(null); 15 | Assert.AreEqual("", viewModel.SearchFilter.UserSearchFilter); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Views/IShellView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | 3 | namespace Waf.MusicManager.Applications.Views; 4 | 5 | public interface IShellView : IView 6 | { 7 | double VirtualScreenWidth { get; } 8 | 9 | double VirtualScreenHeight { get; } 10 | 11 | double Left { get; set; } 12 | 13 | double Top { get; set; } 14 | 15 | double Width { get; set; } 16 | 17 | double Height { get; set; } 18 | 19 | bool IsMaximized { get; set; } 20 | 21 | event CancelEventHandler? Closing; 22 | 23 | event EventHandler? Closed; 24 | 25 | void Show(); 26 | 27 | void Close(); 28 | } 29 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicFiles/MockMusicFile.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Test.MusicManager.Domain.MusicFiles; 4 | 5 | public class MockMusicFile : MusicFile 6 | { 7 | public MockMusicFile(MusicMetadata? metadata, string? fileName) : base(x => Task.FromResult(metadata), fileName) 8 | { 9 | GetMetadataAsync().GetAwaiter().GetResult(); // Ensure that the metadata is loaded. 10 | } 11 | 12 | public static MusicFile CreateEmpty(string fileName) => new MockMusicFile(new MusicMetadata(new TimeSpan(0, 3, 33), 320000) 13 | { 14 | Artists = [], 15 | Title = "" 16 | }, fileName); 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/PathToDirectoryNameConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Windows.Data; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class PathToDirectoryNameConverter : IValueConverter 8 | { 9 | public static PathToDirectoryNameConverter Default { get; } = new(); 10 | 11 | public object? Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) => Path.GetDirectoryName(value as string); 12 | 13 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 14 | } 15 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockTranscoder.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Test.MusicManager.Applications.Services; 4 | 5 | public class MockTranscoder : ITranscoder 6 | { 7 | public Func, Task>? TranscodeAsyncAction { get; set; } 8 | 9 | public Task TranscodeAsync(string sourceFileName, string destinationFileName, uint bitrate, CancellationToken cancellationToken, IProgress progress) 10 | { 11 | return TranscodeAsyncAction?.Invoke(sourceFileName, destinationFileName, bitrate, cancellationToken, progress) ?? Task.CompletedTask; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Transcoding/TranscodingManager.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Transcoding; 2 | 3 | public class TranscodingManager 4 | { 5 | private readonly ObservableList transcodeItems; 6 | 7 | public TranscodingManager() 8 | { 9 | transcodeItems = []; 10 | TranscodeItems = new ReadOnlyObservableList(transcodeItems); 11 | } 12 | 13 | public IReadOnlyObservableList TranscodeItems { get; } 14 | 15 | public void AddTranscodeItem(TranscodeItem item) => transcodeItems.Add(item); 16 | 17 | public void RemoveTranscodeItem(TranscodeItem item) => transcodeItems.Remove(item); 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Resources/DataResources.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/SelectionService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.DataModels; 2 | using Waf.MusicManager.Domain.MusicFiles; 3 | 4 | namespace Waf.MusicManager.Applications.Services; 5 | 6 | internal class SelectionService : ISelectionService 7 | { 8 | public ObservableListViewCore MusicFiles { get; private set; } = null!; 9 | 10 | public ObservableList SelectedMusicFiles { get; } = []; 11 | 12 | public void Initialize(IEnumerable musicFiles) 13 | { 14 | MusicFiles = new(new SynchronizingList(musicFiles, x => new MusicFileDataModel(x))); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Playlists/RandomServiceTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Domain.Playlists; 4 | 5 | namespace Test.MusicManager.Domain.MusicFiles; 6 | 7 | [TestClass] 8 | public class RandomServiceTest : DomainTest 9 | { 10 | [TestMethod] 11 | public void NextRandomNumber() 12 | { 13 | var randomService = new RandomService(); 14 | for (int i = 0; i < 10; i++) Assert.IsTrue(randomService.NextRandomNumber(2) <= 2); 15 | 16 | AssertHelper.ExpectedException(() => randomService.NextRandomNumber(int.MaxValue)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/PathToFileNameConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Converters; 3 | 4 | namespace Test.MusicManager.Presentation.Converters; 5 | 6 | [TestClass] 7 | public class PathToFileNameConverterTest 8 | { 9 | [TestMethod] 10 | public void ConvertTest() 11 | { 12 | var converter = PathToFileNameConverter.Default; 13 | Assert.AreEqual(@"Test - File", converter.Convert(@"C:\Users\Test\Music\Test - File.mp3", null, null, null)); 14 | Assert.AreEqual(@"Test - File.mp3", converter.Convert(@"C:\Users\Test\Music\Test - File.mp3", null, "WithExtension", null)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IPlayerService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using Waf.MusicManager.Domain.MusicFiles; 3 | 4 | namespace Waf.MusicManager.Applications.Services; 5 | 6 | public interface IPlayerService : INotifyPropertyChanged 7 | { 8 | ICommand PlayAllCommand { get; } 9 | 10 | ICommand PlaySelectedCommand { get; } 11 | 12 | ICommand EnqueueAllCommand { get; } 13 | 14 | ICommand EnqueueSelectedCommand { get; } 15 | 16 | ICommand PreviousCommand { get; } 17 | 18 | ICommand PlayPauseCommand { get; } 19 | 20 | ICommand NextCommand { get; } 21 | 22 | bool IsPlayCommand { get; } 23 | 24 | MusicFile? PlayingMusicFile { get; } 25 | } 26 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/Mp4SaveMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class Mp4SaveMetadata : SaveMetadata 6 | { 7 | protected override void ApplyGenre(MusicProperties properties, IDictionary customProperties, IEnumerable genre) 8 | { 9 | ApplyAsOneItem(properties.Genre, genre); 10 | } 11 | 12 | private static void ApplyAsOneItem(IList target, IEnumerable source) 13 | { 14 | // The WinRT API does not support some of the multiple tags for MP4 files 15 | target.Clear(); 16 | target.Add(StringListConverter.ToString(source)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/BitrateConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Presentation.Properties; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class BitrateConverter : IValueConverter 8 | { 9 | public static BitrateConverter Default { get; } = new(); 10 | 11 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | return string.Format(CultureInfo.CurrentCulture, Resources.KiloBitPerSeconds, ((long)value!) / 1000); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException(); 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/WindowTitleConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Waf.MusicManager.Presentation.Converters; 5 | 6 | public class WindowTitleConverter : IMultiValueConverter 7 | { 8 | public static WindowTitleConverter Default { get; } = new(); 9 | 10 | public object Convert(object?[] values, Type? targetType, object? parameter, CultureInfo? culture) 11 | { 12 | var stringList = values.OfType().Where(x => !string.IsNullOrEmpty(x)).ToArray(); 13 | return string.Join(" - ", stringList); 14 | } 15 | 16 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/SequenceEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Presentation.Services; 2 | 3 | internal class SequenceEqualityComparer : IEqualityComparer> 4 | { 5 | public static SequenceEqualityComparer Default { get; } = new(); 6 | 7 | public bool Equals(IEnumerable? x, IEnumerable? y) 8 | { 9 | if (x == null && y == null) return true; 10 | if (x == null || y == null) return false; 11 | return x.SequenceEqual(y); 12 | } 13 | 14 | public int GetHashCode(IEnumerable sequence) 15 | { 16 | if (sequence == null || !sequence.Any()) return 0; 17 | return sequence.Select(x => x == null ? 0 : x.GetHashCode()).Aggregate((hash, next) => hash ^ next); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/Mp4ReadMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class Mp4ReadMetadata : ReadMetadata 6 | { 7 | protected override IEnumerable ReadGenre(MusicProperties properties, IDictionary customProperties) 8 | { 9 | return TryParseFromOneItem(base.ReadGenre(properties, customProperties)); 10 | } 11 | 12 | private static IReadOnlyList TryParseFromOneItem(IEnumerable source) 13 | { 14 | // The WinRT API does not support some of the multiple tags for MP4 files. 15 | return source.Count() == 1 ? StringListConverter.FromString(source.First()) : source.ToArray(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockPlaylistView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Domain.Playlists; 4 | 5 | namespace Test.MusicManager.Applications.Views; 6 | 7 | public class MockPlaylistView : MockView, IPlaylistView 8 | { 9 | public Action? FocusSearchBoxStub { get; set; } 10 | 11 | public Action? FocusSelectedItemStub { get; set; } 12 | 13 | public Action? ScrollIntoViewStub { get; set; } 14 | 15 | public void FocusSearchBox() => FocusSearchBoxStub?.Invoke(); 16 | 17 | public void FocusSelectedItem() => FocusSelectedItemStub?.Invoke(); 18 | 19 | public void ScrollIntoView(PlaylistItem item) => ScrollIntoViewStub?.Invoke(item); 20 | } 21 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/FlacReadMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class FlacReadMetadata : ReadMetadata 6 | { 7 | protected override IEnumerable ReadConductors(MusicProperties properties, IDictionary customProperties) 8 | { 9 | return TryParseFromOneItem(base.ReadConductors(properties, customProperties)); 10 | } 11 | 12 | private static IReadOnlyList TryParseFromOneItem(IEnumerable source) 13 | { 14 | // The WinRT API does not support some of the multiple tags for MP3 files. 15 | return source.Count() == 1 ? StringListConverter.FromString(source.First()) : source.ToArray(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/MusicManager.Applications.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0-windows10.0.19041 4 | Test.MusicManager.Applications 5 | Test.MusicManager.Applications 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/FlacSaveMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class FlacSaveMetadata : SaveMetadata 6 | { 7 | protected override void ApplyConductors(MusicProperties properties, IDictionary customProperties, IEnumerable conductors) 8 | { 9 | ApplyAsOneItem(properties.Conductors, conductors); 10 | } 11 | 12 | private static void ApplyAsOneItem(IList target, IEnumerable source) 13 | { 14 | // The WinRT API does not support some of the multiple tags for MP3 files; it aborts saving the metadata without error :-( 15 | target.Clear(); 16 | target.Add(StringListConverter.ToString(source)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Services/SupportedFileTypesTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Presentation.Services; 4 | 5 | namespace Test.MusicManager.Presentation.Services; 6 | 7 | [TestClass] 8 | public class SupportedFileTypesTest 9 | { 10 | [TestMethod] 11 | public void FileTypesTest() 12 | { 13 | Assert.IsTrue(SupportedFileTypes.MusicFileExtensions.Contains(".mp3")); 14 | Assert.IsTrue(SupportedFileTypes.PlaylistFileExtensions.Contains(".m3u")); 15 | 16 | AssertHelper.ExpectedException(() => SupportedFileTypes.GetReadMetadata(".foo")); 17 | AssertHelper.ExpectedException(() => SupportedFileTypes.GetSaveMetadata(".foo")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/StringListConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Waf.MusicManager.Presentation.Services; 4 | 5 | public static class StringListConverter 6 | { 7 | public static string ToString(IEnumerable? list, string? separator = null) => string.Join(GetSeparator(separator), list ?? []); 8 | 9 | public static IReadOnlyList FromString(string text, string? separator = null) 10 | { 11 | return (text ?? "").Split([GetSeparator(separator).Trim(' ')], StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim(' ')).Where(x => !string.IsNullOrEmpty(x)).ToArray(); 12 | } 13 | 14 | private static string GetSeparator(string? separator) => !string.IsNullOrEmpty(separator) ? separator : CultureInfo.CurrentCulture.TextInfo.ListSeparator + " "; 15 | } 16 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/PathToFileNameConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Windows.Data; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class PathToFileNameConverter : IValueConverter 8 | { 9 | public static PathToFileNameConverter Default { get; } = new(); 10 | 11 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | if (ConverterHelper.IsParameterSet("WithExtension", parameter)) return Path.GetFileName((string)value!); 14 | return Path.GetFileNameWithoutExtension((string)value!); 15 | } 16 | 17 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/IsPlaylistItemPlayingMultiConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Domain.Playlists; 3 | using Waf.MusicManager.Presentation.Converters; 4 | 5 | namespace Test.MusicManager.Presentation.Converters; 6 | 7 | [TestClass] 8 | public class IsPlaylistItemPlayingMultiConverterTest 9 | { 10 | [TestMethod] 11 | public void ConvertTest() 12 | { 13 | var item1 = new PlaylistItem(null!); 14 | var item2 = new PlaylistItem(null!); 15 | 16 | var converter = IsPlaylistItemPlayingMultiConverter.Default; 17 | Assert.IsTrue((bool)converter.Convert([ item1, item1 ], null, null, null)); 18 | Assert.IsFalse((bool)converter.Convert([ item1, item2 ], null, null, null)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/ItemsCountConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Presentation.Properties; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class ItemsCountConverter : IValueConverter 8 | { 9 | public static ItemsCountConverter Default { get; } = new(); 10 | 11 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | int itemsCount = (int)value!; 14 | return itemsCount == 1 ? Resources.OneItem : string.Format(CultureInfo.CurrentCulture, Resources.NumberOfItems, itemsCount); 15 | } 16 | 17 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 18 | } 19 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Properties/PlaylistSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using System.Waf.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Applications.Properties; 5 | 6 | [DataContract] 7 | public sealed class PlaylistSettings : UserSettingsBase 8 | { 9 | [DataMember(Name = "FileNames")] private readonly List fileNames = []; 10 | 11 | [DataMember] public string? LastPlayedFileName { get; set; } 12 | 13 | [DataMember] public TimeSpan LastPlayedFilePosition { get; set; } 14 | 15 | public IReadOnlyList FileNames => fileNames; 16 | 17 | public void ReplaceAll(IEnumerable newFileNames) 18 | { 19 | fileNames.Clear(); 20 | fileNames.AddRange(newFileNames); 21 | } 22 | 23 | protected override void SetDefaultValues() { } 24 | } 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Test.MusicManager.Presentation; 4 | 5 | [TestClass] 6 | public static class TestHelper 7 | { 8 | private static readonly HashSet tempFiles = []; 9 | 10 | public static string GetTempFileName(string? extension = null) 11 | { 12 | var tempFile = Path.Combine(Path.GetTempPath(), "tmp" + Path.GetRandomFileName()); 13 | if (!string.IsNullOrEmpty(extension)) tempFile += extension; 14 | tempFiles.Add(tempFile); 15 | return tempFile; 16 | } 17 | 18 | [AssemblyCleanup] 19 | public static void Cleanup() 20 | { 21 | foreach (var x in tempFiles.Where(File.Exists)) 22 | { 23 | try { File.Delete(x); } 24 | catch { } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/UIntToDisplayValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Waf.MusicManager.Presentation.Converters; 5 | 6 | public class UIntToDisplayValueConverter : IValueConverter 7 | { 8 | public static UIntToDisplayValueConverter Default { get; } = new(); 9 | 10 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 11 | { 12 | var number = (uint)value!; 13 | return number != 0 ? number : ""; 14 | } 15 | 16 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) 17 | { 18 | var displayValue = value as string; 19 | return string.IsNullOrEmpty(displayValue) ? 0 : uint.Parse(displayValue, CultureInfo.CurrentCulture); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/IsPlaylistItemPlayingMultiConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Domain.Playlists; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class IsPlaylistItemPlayingMultiConverter : IMultiValueConverter 8 | { 9 | public static IsPlaylistItemPlayingMultiConverter Default { get; } = new(); 10 | 11 | public object Convert(object[]? values, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | var playingPlaylistItem = (PlaylistItem)values![0]; 14 | var currentPlaylistItem = (PlaylistItem)values[1]; 15 | return playingPlaylistItem == currentPlaylistItem; 16 | } 17 | 18 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 19 | } 20 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/AssertUnobservedExceptions.cs: -------------------------------------------------------------------------------- 1 | namespace Test.MusicManager.Domain; 2 | 3 | public class AssertUnobservedExceptions 4 | { 5 | private Exception? unobservedTaskException; 6 | 7 | public void Initialize() 8 | { 9 | unobservedTaskException = null; 10 | TaskScheduler.UnobservedTaskException += TaskSchedulerUnobservedTaskException; 11 | } 12 | 13 | public void Cleanup() 14 | { 15 | GC.Collect(); 16 | GC.WaitForPendingFinalizers(); 17 | TaskScheduler.UnobservedTaskException -= TaskSchedulerUnobservedTaskException; 18 | if (unobservedTaskException != null) throw new InvalidOperationException("Detected an unobserved exception.", unobservedTaskException); 19 | } 20 | 21 | private void TaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) => unobservedTaskException = e.Exception; 22 | } 23 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/ConverterHelperTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Converters; 3 | 4 | namespace Test.MusicManager.Presentation.Converters; 5 | 6 | [TestClass] 7 | public class ConverterHelperTest 8 | { 9 | [TestMethod] 10 | public void IsParameterSetTest() 11 | { 12 | Assert.IsFalse(ConverterHelper.IsParameterSet("test", null)); 13 | Assert.IsFalse(ConverterHelper.IsParameterSet("test", new object())); 14 | Assert.IsFalse(ConverterHelper.IsParameterSet("test", "")); 15 | Assert.IsFalse(ConverterHelper.IsParameterSet("test", "tes")); 16 | 17 | Assert.IsTrue(ConverterHelper.IsParameterSet("test", "test")); 18 | Assert.IsTrue(ConverterHelper.IsParameterSet("test", "TEST")); 19 | Assert.IsTrue(ConverterHelper.IsParameterSet("TEST", "test")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/FilterOperatorToStringConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Applications.DataModels; 3 | using Waf.MusicManager.Presentation.Converters; 4 | 5 | namespace Test.MusicManager.Presentation.Converters; 6 | 7 | [TestClass] 8 | public class FilterOperatorToStringConverterTest 9 | { 10 | [TestMethod] 11 | public void ConvertTest() 12 | { 13 | var converter = FilterOperatorToStringConverter.Default; 14 | 15 | Assert.IsNull(converter.Convert(null, null, null, null)); 16 | Assert.AreEqual("", converter.Convert(FilterOperator.Ignore, null, null, null)); 17 | Assert.AreEqual("<=", converter.Convert(FilterOperator.LessThanOrEqual, null, null, null)); 18 | Assert.AreEqual(">=", converter.Convert(FilterOperator.GreaterThanOrEqual, null, null, null)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleMusicPropertiesViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.ViewModels; 2 | using Waf.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | 5 | namespace Waf.MusicManager.Presentation.DesignData; 6 | 7 | public class SampleMusicPropertiesViewModel : MusicPropertiesViewModel 8 | { 9 | public SampleMusicPropertiesViewModel() : base(new MockMusicPropertiesView(), null!) 10 | { 11 | MusicFile = new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 3, 45), 320000) 12 | { 13 | Artists = [ @"Culture Beat" ], 14 | Title = @"Serenity (Epilog)", 15 | Genre = [ "Electronic", "Dance" ] 16 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity"); 17 | } 18 | 19 | 20 | private class MockMusicPropertiesView : MockView, IMusicPropertiesView 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Services/ListSortComparerTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Services; 3 | 4 | namespace Test.MusicManager.Presentation.Services; 5 | 6 | [TestClass] 7 | public class ListSortComparerTest 8 | { 9 | [TestMethod] 10 | public void CompareTest() 11 | { 12 | var comparer = new ListSortComparer(string.CompareOrdinal, ListSortDirection.Ascending); 13 | Assert.IsTrue(comparer.Compare("a", "z") < 0); 14 | Assert.AreEqual(0, comparer.Compare("a", "a")); 15 | Assert.IsTrue(comparer.Compare("b", "a") > 0); 16 | 17 | comparer = new ListSortComparer(string.CompareOrdinal, ListSortDirection.Descending); 18 | Assert.IsTrue(comparer.Compare("a", "z") > 0); 19 | Assert.AreEqual(0, comparer.Compare("a", "a")); 20 | Assert.IsTrue(comparer.Compare("b", "a") < 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/MusicPropertiesProgressVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class MusicPropertiesProgressVisibilityConverter : IMultiValueConverter 8 | { 9 | public static MusicPropertiesProgressVisibilityConverter Default { get; } = new(); 10 | 11 | public object Convert(object?[] values, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | var isMetadataLoaded = values[0] is bool b && b; 14 | var loadError = values[1]; 15 | 16 | return !isMetadataLoaded && loadError == null ? Visibility.Visible : Visibility.Collapsed; 17 | } 18 | 19 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 20 | } 21 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/MusicPropertiesEnabledConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class MusicPropertiesEnabledConverter : IMultiValueConverter 8 | { 9 | public static MusicPropertiesEnabledConverter Default { get; } = new(); 10 | 11 | public object Convert(object?[] values, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | var musicFile = (MusicFile)values[0]!; 14 | // values[1] = musicFile.IsMetadataLoaded; only used to update the Binding 15 | return musicFile != null && musicFile.IsMetadataLoaded && musicFile.Metadata.IsSupported; 16 | } 17 | 18 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 19 | } 20 | -------------------------------------------------------------------------------- /src/MusicManager/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/StringListToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Presentation.Services; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class StringListToStringConverter : IValueConverter 8 | { 9 | public static StringListToStringConverter Default { get; } = new(); 10 | 11 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) => StringListConverter.ToString((IEnumerable)value!, GetSeparator(parameter)); 12 | 13 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => StringListConverter.FromString((string)value!, GetSeparator(parameter)); 14 | 15 | private static string? GetSeparator(object? commandParameter) => ConverterHelper.IsParameterSet("ListSeparator", commandParameter) ? null : Environment.NewLine; 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Properties/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using System.Waf.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Applications.Properties; 5 | 6 | [DataContract] 7 | public sealed class AppSettings : UserSettingsBase 8 | { 9 | [DataMember] public double Left { get; set; } 10 | 11 | [DataMember] public double Top { get; set; } 12 | 13 | [DataMember] public double Height { get; set; } 14 | 15 | [DataMember] public double Width { get; set; } 16 | 17 | [DataMember] public bool IsMaximized { get; set; } 18 | 19 | [DataMember] public string? CurrentPath { get; set; } 20 | 21 | [DataMember] public double Volume { get; set; } 22 | 23 | [DataMember] public bool Shuffle { get; set; } 24 | 25 | [DataMember] public bool Repeat { get; set; } 26 | 27 | protected override void SetDefaultValues() 28 | { 29 | Volume = 0.5; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/DisposableTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation; 3 | 4 | namespace Test.MusicManager.Presentation; 5 | 6 | [TestClass] 7 | public class DisposableTest 8 | { 9 | [TestMethod] 10 | public void DisposeCalled() 11 | { 12 | var disposable = new MockDisposable(); 13 | Assert.AreEqual(0, disposable.DisposeCalled); 14 | disposable.Dispose(); 15 | Assert.AreEqual(1, disposable.DisposeCalled); 16 | disposable.Dispose(); 17 | Assert.AreEqual(1, disposable.DisposeCalled); 18 | } 19 | 20 | 21 | private class MockDisposable : Disposable 22 | { 23 | public int DisposeCalled { get; set; } 24 | 25 | protected override void Dispose(bool isDisposing) 26 | { 27 | if (isDisposing) DisposeCalled++; 28 | base.Dispose(isDisposing); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/MockSelectionService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.DataModels; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Presentation.DesignData; 5 | 6 | public class MockSelectionService : ISelectionService 7 | { 8 | private readonly ObservableCollection innerMusicFiles; 9 | 10 | public MockSelectionService() 11 | { 12 | innerMusicFiles = []; 13 | SelectedMusicFiles = []; 14 | MusicFiles = new(innerMusicFiles); 15 | } 16 | 17 | public ObservableListViewCore MusicFiles { get; } 18 | 19 | public ObservableList SelectedMusicFiles { get; } 20 | 21 | public void SetMusicFiles(IEnumerable musicFiles) 22 | { 23 | innerMusicFiles.Clear(); 24 | musicFiles.ToList().ForEach(x => innerMusicFiles.Add(x)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/ManagerStatusService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services; 2 | 3 | internal class ManagerStatusService : Model, IManagerStatusService 4 | { 5 | private bool updatingFilesList; 6 | private int totalFilesCount = -1; 7 | 8 | public bool UpdatingFilesList 9 | { 10 | get => updatingFilesList; 11 | private set => SetProperty(ref updatingFilesList, value); 12 | } 13 | 14 | public int TotalFilesCount 15 | { 16 | get => totalFilesCount; 17 | private set => SetProperty(ref totalFilesCount, value); 18 | } 19 | 20 | public void StartUpdatingFilesList() 21 | { 22 | UpdatingFilesList = true; 23 | TotalFilesCount = -1; 24 | } 25 | 26 | public void FinishUpdatingFilesList(int totalFilesCount) 27 | { 28 | UpdatingFilesList = false; 29 | TotalFilesCount = totalFilesCount; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/TotalDurationConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Globalization; 3 | using System.Waf.UnitTesting; 4 | using Waf.MusicManager.Presentation.Converters; 5 | using Waf.MusicManager.Presentation.Properties; 6 | 7 | namespace Test.MusicManager.Presentation.Converters; 8 | 9 | [TestClass] 10 | public class TotalDurationConverterTest 11 | { 12 | [TestMethod] 13 | public void ConvertTest() 14 | { 15 | var converter = TotalDurationConverter.Default; 16 | Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resources.AboutDuration, "0:03"), converter.Convert([ true, TimeSpan.FromSeconds(3) ], null, null, null)); 17 | Assert.AreEqual("0:04", converter.Convert([ false, TimeSpan.FromSeconds(4) ], null, null, null)); 18 | 19 | AssertHelper.ExpectedException(() => converter.ConvertBack(null, null, null, null)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/App.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/UIntToDisplayValueConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Converters; 3 | 4 | namespace Test.MusicManager.Presentation.Converters; 5 | 6 | [TestClass] 7 | public class UIntToDisplayValueConverterTest 8 | { 9 | [TestMethod] 10 | public void ConvertTest() 11 | { 12 | var converter = UIntToDisplayValueConverter.Default; 13 | Assert.AreEqual(3u, converter.Convert(3u, null, null, null)); 14 | Assert.AreEqual("", converter.Convert(0u, null, null, null)); 15 | } 16 | 17 | [TestMethod] 18 | public void ConvertBackTest() 19 | { 20 | var converter = UIntToDisplayValueConverter.Default; 21 | Assert.AreEqual(3u, converter.ConvertBack("3", null, null, null)); 22 | Assert.AreEqual(0u, converter.ConvertBack("", null, null, null)); 23 | Assert.AreEqual(0u, converter.ConvertBack(null, null, null, null)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/TotalDurationConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Presentation.Properties; 4 | 5 | namespace Waf.MusicManager.Presentation.Converters; 6 | 7 | public class TotalDurationConverter : IMultiValueConverter 8 | { 9 | public static TotalDurationConverter Default { get; } = new(); 10 | 11 | public object Convert(object?[] values, Type? targetType, object? parameter, CultureInfo? culture) 12 | { 13 | var isTotalDurationEstimated = (bool)values[0]!; 14 | var totalDuration = (string)new DurationConverter().Convert(values[1], null, null, null); 15 | if (isTotalDurationEstimated) totalDuration = string.Format(CultureInfo.CurrentCulture, Resources.AboutDuration, totalDuration); 16 | return totalDuration; 17 | } 18 | 19 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 20 | } 21 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/Mp3ReadMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class Mp3ReadMetadata : ReadMetadata 6 | { 7 | protected override IEnumerable ReadGenre(MusicProperties properties, IDictionary customProperties) 8 | { 9 | return TryParseFromOneItem(base.ReadGenre(properties, customProperties)); 10 | } 11 | 12 | protected override IEnumerable ReadConductors(MusicProperties properties, IDictionary customProperties) 13 | { 14 | return TryParseFromOneItem(base.ReadConductors(properties, customProperties)); 15 | } 16 | 17 | private static IReadOnlyList TryParseFromOneItem(IEnumerable source) 18 | { 19 | // The WinRT API does not support some of the multiple tags for MP3 files. 20 | return source.Count() == 1 ? StringListConverter.FromString(source.First()) : source.ToArray(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Metadata/Mp3SaveMetadata.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.FileProperties; 2 | 3 | namespace Waf.MusicManager.Presentation.Services.Metadata; 4 | 5 | internal class Mp3SaveMetadata : SaveMetadata 6 | { 7 | protected override void ApplyGenre(MusicProperties properties, IDictionary customProperties, IEnumerable genre) 8 | { 9 | ApplyAsOneItem(properties.Genre, genre); 10 | } 11 | 12 | protected override void ApplyConductors(MusicProperties properties, IDictionary customProperties, IEnumerable conductors) 13 | { 14 | ApplyAsOneItem(properties.Conductors, conductors); 15 | } 16 | 17 | private static void ApplyAsOneItem(IList target, IEnumerable source) 18 | { 19 | // The WinRT API does not support some of the multiple tags for MP3 files; it aborts saving the metadata without error :-( 20 | target.Clear(); 21 | target.Add(StringListConverter.ToString(source)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/MusicManager.Presentation.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0-windows10.0.19041 4 | Test.MusicManager.Presentation 5 | Test.MusicManager.Presentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MusicTitleHelperTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Test.MusicManager.Applications.Services; 5 | 6 | [TestClass] 7 | public class MusicTitleHelperTest 8 | { 9 | [TestMethod] 10 | public void GetTitleTextTest() 11 | { 12 | var fileName = @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.mp3"; 13 | 14 | Assert.AreEqual("Culture Beat - Serenity", MusicTitleHelper.GetTitleText(fileName, [], null)); 15 | Assert.AreEqual("Culture Beat - Serenity", MusicTitleHelper.GetTitleText(fileName, [], "")); 16 | 17 | Assert.AreEqual("", MusicTitleHelper.GetTitleText(fileName, [ "Culture Beat" ], null)); 18 | Assert.AreEqual("", MusicTitleHelper.GetTitleText(fileName, [ "Culture Beat" ], "")); 19 | 20 | Assert.AreEqual("Serenity (Epilog)", MusicTitleHelper.GetTitleText(fileName, [ "Culture Beat" ], "Serenity (Epilog)")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/MusicPropertiesProgressVisibilityConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using System.Windows; 4 | using Waf.MusicManager.Presentation.Converters; 5 | 6 | namespace Test.MusicManager.Presentation.Converters; 7 | 8 | [TestClass] 9 | public class MusicPropertiesProgressVisibilityConverterTest 10 | { 11 | [TestMethod] 12 | public void ConvertTest() 13 | { 14 | var converter = MusicPropertiesProgressVisibilityConverter.Default; 15 | Assert.AreEqual(Visibility.Visible, converter.Convert([ false, null ], null, null, null)); 16 | Assert.AreEqual(Visibility.Collapsed, converter.Convert([ true, null ], null, null, null)); 17 | Assert.AreEqual(Visibility.Collapsed, converter.Convert([ false, new ArgumentException() ], null, null, null)); 18 | 19 | AssertHelper.ExpectedException(() => converter.ConvertBack(null, null, null, null)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/DurationConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Waf.MusicManager.Presentation.Converters; 5 | 6 | public class DurationConverter : IValueConverter 7 | { 8 | public static DurationConverter Default { get; } = new(); 9 | 10 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 11 | { 12 | var duration = (TimeSpan)value!; 13 | if (duration < TimeSpan.FromHours(1)) 14 | { 15 | return string.Format(CultureInfo.CurrentCulture, "{0}:{1:00}", (int)duration.TotalMinutes, duration.Seconds); 16 | } 17 | else 18 | { 19 | return string.Format(CultureInfo.CurrentCulture, "{0}:{1:00}:{2:00}", (int)duration.TotalHours, duration.Minutes, duration.Seconds); 20 | } 21 | } 22 | 23 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 24 | } 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/MusicTitleConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | using Waf.MusicManager.Applications.Services; 5 | 6 | namespace Waf.MusicManager.Presentation.Converters; 7 | 8 | public class MusicTitleConverter : IMultiValueConverter 9 | { 10 | public static MusicTitleConverter Default { get; } = new(); 11 | 12 | public object Convert(object?[] values, Type? targetType, object? parameter, CultureInfo? culture) 13 | { 14 | if (values[0] == DependencyProperty.UnsetValue) return DependencyProperty.UnsetValue; 15 | 16 | var fileName = (string)values[0]!; 17 | var artists = values[1] as IEnumerable; 18 | var title = values[2] as string; 19 | 20 | return MusicTitleHelper.GetTitleText(fileName, artists, title); 21 | } 22 | 23 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 24 | } 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/ViewModels/InfoViewModelTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Test.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Applications.ViewModels; 4 | 5 | namespace Test.MusicManager.Applications.ViewModels; 6 | 7 | [TestClass] 8 | public class InfoViewModelTest : ApplicationsTest 9 | { 10 | protected override void OnCleanup() 11 | { 12 | MockInfoView.ShowDialogAction = null; 13 | base.OnCleanup(); 14 | } 15 | 16 | [TestMethod] 17 | public void BasicInfoViewModelTest() 18 | { 19 | var viewModel = Get(); 20 | var ownerWindow = new object(); 21 | 22 | bool isShowDialogCalled = false; 23 | MockInfoView.ShowDialogAction = view => 24 | { 25 | isShowDialogCalled = true; 26 | Assert.IsTrue(viewModel.ShowWebsiteCommand.CanExecute(null)); 27 | }; 28 | 29 | viewModel.ShowDialog(ownerWindow); 30 | Assert.IsTrue(isShowDialogCalled); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/ManagerStatusServiceTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Test.MusicManager.Applications.Services; 5 | 6 | [TestClass] 7 | public class ManagerStatusServiceTest : ApplicationsTest 8 | { 9 | [TestMethod] 10 | public void BasicManagerStatusServiceTest() 11 | { 12 | var service = new ManagerStatusService(); 13 | Assert.IsFalse(service.UpdatingFilesList); 14 | Assert.AreEqual(-1, service.TotalFilesCount); 15 | 16 | service.StartUpdatingFilesList(); 17 | Assert.IsTrue(service.UpdatingFilesList); 18 | Assert.AreEqual(-1, service.TotalFilesCount); 19 | 20 | service.FinishUpdatingFilesList(42); 21 | Assert.IsFalse(service.UpdatingFilesList); 22 | Assert.AreEqual(42, service.TotalFilesCount); 23 | 24 | service.StartUpdatingFilesList(); 25 | Assert.IsTrue(service.UpdatingFilesList); 26 | Assert.AreEqual(-1, service.TotalFilesCount); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/ErrorMessagesConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | using Waf.MusicManager.Presentation.Properties; 5 | 6 | namespace Waf.MusicManager.Presentation.Converters; 7 | 8 | public sealed class ErrorMessagesConverter : IMultiValueConverter 9 | { 10 | public static ErrorMessagesConverter Default { get; } = new(); 11 | 12 | public object Convert(object[]? values, Type? targetType, object? parameter, CultureInfo? culture) 13 | { 14 | if (values?.FirstOrDefault() is IEnumerable> errorMessages) 15 | { 16 | string message = errorMessages.LastOrDefault()?.Item2 ?? ""; 17 | return string.Format(CultureInfo.CurrentCulture, Resources.ErrorMessage, errorMessages.Count(), message); 18 | } 19 | return DependencyProperty.UnsetValue; 20 | } 21 | 22 | public object[] ConvertBack(object? value, Type[]? targetTypes, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 23 | } 24 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/MusicManager.Applications.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0-windows 4 | true 5 | Waf.MusicManager.Applications 6 | Waf.MusicManager.Applications 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IFileService.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications.Services 2 | { 3 | public record FolderItem(string? Path, string DisplayName); 4 | 5 | public interface IFileService 6 | { 7 | static IReadOnlyList PlaylistFileExtensions { get; } = [ ".m3u", ".wpl" ]; 8 | 9 | bool IsFileSupported(string fileName); 10 | 11 | bool DirectoryExists(string path); 12 | 13 | Task GetParentPath(string path); 14 | 15 | Task GetFolderFromPath(string path); 16 | 17 | Task> GetSubFoldersFromPath(string path); 18 | 19 | Task GetDisplayPath(string path); 20 | 21 | Task> GetFiles(string directory, bool deep, string userSearchFilter, string applicationSearchFilter, CancellationToken cancellation); 22 | 23 | Task DeleteFile(string fileName); 24 | 25 | Task> ReadPlaylist(string playlistFileName); 26 | 27 | Task SavePlaylist(string playlistFileName, IReadOnlyList fileNames); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/IShellService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Properties; 2 | 3 | namespace Waf.MusicManager.Applications.Services; 4 | 5 | public interface IShellService : INotifyPropertyChanged 6 | { 7 | AppSettings Settings { get; } 8 | 9 | object ShellView { get; } 10 | 11 | object? ContentView { get; set; } 12 | 13 | object? MusicPropertiesView { get; set; } 14 | 15 | object? PlaylistView { get; set; } 16 | 17 | Lazy? TranscodingListView { get; set; } 18 | 19 | object? PlayerView { get; set; } 20 | 21 | IReadOnlyCollection TasksToCompleteBeforeShutdown { get; } 22 | 23 | bool IsApplicationBusy { get; } 24 | 25 | event CancelEventHandler? Closing; 26 | 27 | void ShowError(Exception exception, string displayMessage); 28 | 29 | void ShowMusicPropertiesView(); 30 | 31 | void ShowPlaylistView(); 32 | 33 | void ShowTranscodingListView(); 34 | 35 | void AddTaskToCompleteBeforeShutdown(Task task); 36 | 37 | IDisposable SetApplicationBusy(); 38 | } 39 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/DataModels/FolderBrowserDataModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Waf.MusicManager.Applications.DataModels; 4 | 5 | public class FolderBrowserDataModel : Model 6 | { 7 | private string userPath = ""; 8 | private string currentPath = null!; 9 | private IReadOnlyList subDirectories = []; 10 | private FolderItem? selectedSubDirectory; 11 | 12 | public string UserPath 13 | { 14 | get => userPath; 15 | set => SetProperty(ref userPath, value ?? ""); 16 | } 17 | 18 | public string CurrentPath 19 | { 20 | get => currentPath; 21 | set => SetProperty(ref currentPath, value ?? ""); 22 | } 23 | 24 | public IReadOnlyList SubDirectories 25 | { 26 | get => subDirectories; 27 | set => SetProperty(ref subDirectories, value); 28 | } 29 | 30 | public FolderItem? SelectedSubDirectory 31 | { 32 | get => selectedSubDirectory; 33 | set => SetProperty(ref selectedSubDirectory, value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/WindowTitleConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Presentation.Converters; 4 | 5 | namespace Test.MusicManager.Presentation.Converters; 6 | 7 | [TestClass] 8 | public class WindowTitleConverterTest 9 | { 10 | [TestMethod] 11 | public void ConvertTest() 12 | { 13 | var converter = WindowTitleConverter.Default; 14 | 15 | Assert.AreEqual("Waf Music Manager", converter.Convert([ null, null, "Waf Music Manager" ], null, null, null)); 16 | 17 | Assert.AreEqual("Culture Beat - Waf Music Manager", converter.Convert([ "Culture Beat", null, "Waf Music Manager" ], null, null, null)); 18 | 19 | Assert.AreEqual("Culture Beat - Serenity (Epilog) - Waf Music Manager", converter.Convert([ "Culture Beat", "Serenity (Epilog)", "Waf Music Manager" ], null, null, null)); 20 | 21 | AssertHelper.ExpectedException(() => converter.ConvertBack(null, null, null, null)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 jbe2277 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockFileSystemWatcherService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Test.MusicManager.Applications.Services; 4 | 5 | public class MockFileSystemWatcherService : IFileSystemWatcherService 6 | { 7 | public NotifyFilters NotifyFilter { get; set; } 8 | 9 | public string Path { get; set; } = ""; 10 | 11 | public bool EnableRaisingEvents { get; set; } 12 | 13 | public event FileSystemEventHandler? Created; 14 | 15 | public event RenamedEventHandler? Renamed; 16 | 17 | public event FileSystemEventHandler? Deleted; 18 | 19 | public void RaiseCreated(FileSystemEventArgs e) => OnCreated(e); 20 | 21 | public void RaiseRenamed(RenamedEventArgs e) => OnRenamed(e); 22 | 23 | public void RaiseDeleted(FileSystemEventArgs e) => OnDeleted(e); 24 | 25 | protected virtual void OnCreated(FileSystemEventArgs e) => Created?.Invoke(this, e); 26 | 27 | protected virtual void OnRenamed(RenamedEventArgs e) => Renamed?.Invoke(this, e); 28 | 29 | protected virtual void OnDeleted(FileSystemEventArgs e) => Deleted?.Invoke(this, e); 30 | } 31 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Controls/SuperToolTip.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace Waf.MusicManager.Presentation.Controls; 5 | 6 | public class SuperToolTip : Control 7 | { 8 | static SuperToolTip() 9 | { 10 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SuperToolTip), new FrameworkPropertyMetadata(typeof(SuperToolTip))); 11 | } 12 | 13 | public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(SuperToolTip), new FrameworkPropertyMetadata("")); 14 | 15 | public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(SuperToolTip), new FrameworkPropertyMetadata("")); 16 | 17 | public string Title 18 | { 19 | get => (string)GetValue(TitleProperty); 20 | set => SetValue(TitleProperty, value); 21 | } 22 | 23 | public string Description 24 | { 25 | get => (string)GetValue(DescriptionProperty); 26 | set => SetValue(DescriptionProperty, value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SamplePlayerViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.ViewModels; 2 | using Waf.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.Playlists; 5 | 6 | namespace Waf.MusicManager.Presentation.DesignData; 7 | 8 | public class SamplePlayerViewModel : PlayerViewModel 9 | { 10 | public SamplePlayerViewModel() : base(new MockPlayerView(), null!, null!) 11 | { 12 | PlaylistManager = new PlaylistManager 13 | { 14 | CurrentItem = new PlaylistItem(new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 3, 45), 320) 15 | { 16 | Artists = [ @"Culture Beat" ], 17 | Title = @"Serenity (Epilog)", 18 | Genre = [ "Electronic", "Dance" ] 19 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity")) 20 | }; 21 | } 22 | 23 | 24 | private class MockPlayerView : MockView, IPlayerView 25 | { 26 | public TimeSpan GetPosition() => new(0, 3, 33); 27 | 28 | public void SetPosition(TimeSpan position) { } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/MusicPropertiesEnabledConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Test.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.MusicFiles; 5 | using Waf.MusicManager.Presentation.Converters; 6 | 7 | namespace Test.MusicManager.Presentation.Converters; 8 | 9 | [TestClass] 10 | public class MusicPropertiesEnabledConverterTest 11 | { 12 | [TestMethod] 13 | public void ConvertTest() 14 | { 15 | var musicFile = new MockMusicFile(new MusicMetadata(TimeSpan.FromSeconds(20), 0), ""); 16 | var unsupportedFile = new MockMusicFile(MusicMetadata.CreateUnsupported(TimeSpan.FromSeconds(20), 0), ""); 17 | 18 | var converter = MusicPropertiesEnabledConverter.Default; 19 | Assert.AreEqual(true, converter.Convert([ musicFile, true ], null, null, null)); 20 | Assert.AreEqual(false, converter.Convert([ unsupportedFile, true ], null, null, null)); 21 | 22 | AssertHelper.ExpectedException(() => converter.ConvertBack(null, null, null, null)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: MusicManager 2 | on: 3 | push: 4 | paths: 5 | - 'src/**' 6 | - '.github/workflows/**' 7 | 8 | jobs: 9 | build: 10 | name: 🛠️ Build and test 11 | runs-on: windows-2022 12 | 13 | steps: 14 | - name: 🔖 Check-out 15 | uses: actions/checkout@v4 16 | 17 | - name: ⚙️ Setup .NET 18 | uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: '9' 21 | 22 | - name: ⚙️ Setup MSBuild 23 | uses: microsoft/setup-msbuild@v2 24 | 25 | - name: 🛠️ Build 26 | run: msbuild ./src/MusicManager/MusicManager.sln /restore /p:Configuration=Release /p:GenerateAppxPackageOnBuild=false 27 | 28 | - name: 🕵️ Test Domain 29 | run: dotnet test ./src/MusicManager/MusicManager.Domain.Test/MusicManager.Domain.Test.csproj -c Release --no-build 30 | - name: 🕵️ Test Applications 31 | run: dotnet test ./src/MusicManager/MusicManager.Applications.Test/MusicManager.Applications.Test.csproj -c Release --no-build 32 | - name: 🕵️ Test Presentation 33 | run: dotnet test ./src/MusicManager/MusicManager.Presentation.Test/MusicManager.Presentation.Test.csproj -c Release --no-build 34 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Services/SequenceEqualityComparerTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Services; 3 | 4 | namespace Test.MusicManager.Presentation.Services; 5 | 6 | [TestClass] 7 | public class SequenceEqualityComparerTest 8 | { 9 | [TestMethod] 10 | public void ComparerTest() 11 | { 12 | var list1 = new[] { "Luke", "Han", "Lea" }; 13 | var list2 = new[] { "Luke", "Han", "Lea" }; 14 | var list3 = new[] { "Luke", "Han", "Leo" }; 15 | 16 | var comparer = SequenceEqualityComparer.Default; 17 | Assert.IsTrue(comparer.Equals(list1, list2)); 18 | Assert.IsFalse(comparer.Equals(list1, list3)); 19 | Assert.IsTrue(comparer.Equals(null, null)); 20 | Assert.IsFalse(comparer.Equals(list1, null)); 21 | Assert.IsFalse(comparer.Equals(null, list1)); 22 | 23 | Assert.AreEqual(list1![0].GetHashCode() ^ list1[1].GetHashCode() ^ list1[2].GetHashCode(), comparer.GetHashCode(list1)); 24 | Assert.AreEqual("Han".GetHashCode(), comparer.GetHashCode([ "Han" ])); 25 | Assert.AreEqual(0, comparer.GetHashCode([])); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Themes/Generic/SuperToolTip.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Resources/BrushResources.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/MusicManager/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1031: Do not catch general exception types 4 | dotnet_diagnostic.CA1031.severity = none 5 | 6 | # CA1062: Validate arguments of public methods 7 | dotnet_diagnostic.CA1062.severity = none 8 | 9 | # CA1515: Consider making public types internal 10 | dotnet_diagnostic.CA1515.severity = none 11 | 12 | # CA1860: Avoid using 'Enumerable.Any()' extension method 13 | dotnet_diagnostic.CA1860.severity = none 14 | 15 | # CA2007: Consider calling ConfigureAwait on the awaited task 16 | dotnet_diagnostic.CA2007.severity = none 17 | 18 | 19 | # IDE0008: Use explicit type 20 | csharp_style_var_when_type_is_apparent = true 21 | 22 | # IDE0008: Use explicit type 23 | csharp_style_var_for_built_in_types = true 24 | 25 | # IDE0008: Use explicit type 26 | csharp_style_var_elsewhere = true 27 | 28 | # IDE0011: Add braces 29 | csharp_prefer_braces = when_multiline 30 | 31 | # IDE0022: Use block body for methods 32 | csharp_style_expression_bodied_methods = when_on_single_line 33 | 34 | # IDE0061: Use block body for local functions 35 | csharp_style_expression_bodied_local_functions = when_on_single_line 36 | 37 | # IDE0160: Convert to block scoped namespace 38 | csharp_style_namespace_declarations = file_scoped -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/RatingToStarsConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Waf.MusicManager.Presentation.Converters; 5 | 6 | public class RatingToStarsConverter : IValueConverter 7 | { 8 | public static RatingToStarsConverter Default { get; } = new(); 9 | 10 | public object Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 11 | { 12 | int rating = System.Convert.ToInt32(value, CultureInfo.InvariantCulture); 13 | return rating switch 14 | { 15 | >= 99 => 5, 16 | >= 75 => 4, 17 | >= 50 => 3, 18 | >= 25 => 2, 19 | >= 1 => 1, 20 | _ => 0, 21 | }; 22 | } 23 | 24 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) 25 | { 26 | int stars = System.Convert.ToInt32(value, CultureInfo.InvariantCulture); 27 | return stars switch 28 | { 29 | 5 => 99, 30 | 4 => 75, 31 | 3 => 50, 32 | 2 => 25, 33 | 1 => 1, 34 | _ => 0, 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Playlists/PlayedItemsStack.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Domain.Playlists; 2 | 3 | internal class PlayedItemsStack 4 | { 5 | private readonly int capacity; 6 | private readonly LinkedList playlistItems; 7 | 8 | public PlayedItemsStack(int capacity) 9 | { 10 | this.capacity = capacity; 11 | playlistItems = []; 12 | } 13 | 14 | public int Count => playlistItems.Count; 15 | 16 | public T Pop() 17 | { 18 | var result = (playlistItems.Last ?? throw new InvalidOperationException("PlayedItemsStack is empty.")).Value; 19 | playlistItems.RemoveLast(); 20 | return result; 21 | } 22 | 23 | public bool Contains(T item) => playlistItems.Contains(item); 24 | 25 | public void Add(T item) 26 | { 27 | if (playlistItems.Count >= capacity) playlistItems.RemoveFirst(); 28 | playlistItems.AddLast(item); 29 | } 30 | 31 | public void RemoveAll(T item) 32 | { 33 | bool removed; 34 | do 35 | { 36 | removed = playlistItems.Remove(item); 37 | } 38 | while (removed); 39 | } 40 | 41 | public void Clear() => playlistItems.Clear(); 42 | } 43 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/TranscodeStatusToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Domain.Transcoding; 4 | using Waf.MusicManager.Presentation.Properties; 5 | 6 | namespace Waf.MusicManager.Presentation.Converters; 7 | 8 | public class TranscodeStatusToStringConverter : IValueConverter 9 | { 10 | public static TranscodeStatusToStringConverter Default { get; } = new(); 11 | 12 | public object? Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 13 | { 14 | if (value == null) return null; 15 | var transcodeStatus = (TranscodeStatus)value; 16 | return transcodeStatus switch 17 | { 18 | TranscodeStatus.InProgress => Resources.InProgress, 19 | TranscodeStatus.Pending => Resources.Pending, 20 | TranscodeStatus.Error => Resources.Error, 21 | TranscodeStatus.Completed => Resources.Completed, 22 | _ => throw new InvalidOperationException("Enum value is unknown."), 23 | }; 24 | } 25 | 26 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 27 | } 28 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/PresentationTest.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NLog; 4 | using Test.MusicManager.Applications; 5 | using Waf.MusicManager.Applications; 6 | using Waf.MusicManager.Applications.Services; 7 | using Waf.MusicManager.Presentation.Services; 8 | 9 | namespace Test.MusicManager.Presentation 10 | { 11 | /// Integration tests (uses real service implementations instead of mock implementations) 12 | [TestClass] 13 | public class PresentationTest : ApplicationsTest 14 | { 15 | protected override void ConfigureContainer(ContainerBuilder builder) 16 | { 17 | builder.RegisterModule(new ApplicationsModule()); 18 | builder.RegisterModule(new MockPresentationModule(useMock: false)); 19 | 20 | builder.RegisterType().As().AsSelf().SingleInstance(); 21 | builder.RegisterType().As().AsSelf().SingleInstance(); 22 | builder.RegisterType().As().AsSelf().SingleInstance(); 23 | builder.RegisterType().As().AsSelf().SingleInstance(); 24 | 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/ViewModels/TranscodingListViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | using Waf.MusicManager.Applications.Services; 3 | using Waf.MusicManager.Applications.Views; 4 | using Waf.MusicManager.Domain.MusicFiles; 5 | using Waf.MusicManager.Domain.Transcoding; 6 | 7 | namespace Waf.MusicManager.Applications.ViewModels; 8 | 9 | public class TranscodingListViewModel : ViewModel 10 | { 11 | private TranscodingManager transcodingManager = null!; 12 | 13 | public TranscodingListViewModel(ITranscodingListView view, ITranscodingService transcodingService) : base(view) 14 | { 15 | TranscodingService = transcodingService; 16 | SelectedTranscodeItems = []; 17 | } 18 | 19 | public ITranscodingService TranscodingService { get; } 20 | 21 | public ObservableList SelectedTranscodeItems { get; } 22 | 23 | public TranscodingManager TranscodingManager 24 | { 25 | get => transcodingManager; 26 | set => SetProperty(ref transcodingManager, value); 27 | } 28 | 29 | public Action> InsertFilesAction { get; set; } = null!; 30 | 31 | public Action> InsertMusicFilesAction { get; set; } = null!; 32 | } 33 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Transcoding/TranscodingManagerTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Test.MusicManager.Domain.MusicFiles; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.Transcoding; 5 | using System.Waf.UnitTesting; 6 | 7 | namespace Test.MusicManager.Domain.Transcoding; 8 | 9 | [TestClass] 10 | public class TranscodingManagerTest : DomainTest 11 | { 12 | [TestMethod] 13 | public void AddRemoveTranscodeItemsTest() 14 | { 15 | var musicFile1 = new MockMusicFile(new(TimeSpan.FromSeconds(33), 320), "TestFile1.wma"); 16 | var musicFile2 = new MockMusicFile(new(TimeSpan.FromSeconds(33), 320), "TestFile2.wma"); 17 | var item1 = new TranscodeItem(musicFile1, "TestFile1.mp3"); 18 | var item2 = new TranscodeItem(musicFile2, "TestFile2.mp3"); 19 | 20 | var manager = new TranscodingManager(); 21 | Assert.IsFalse(manager.TranscodeItems.Any()); 22 | 23 | manager.AddTranscodeItem(item1); 24 | manager.AddTranscodeItem(item2); 25 | 26 | AssertHelper.SequenceEqual([ item1, item2 ], manager.TranscodeItems); 27 | 28 | manager.RemoveTranscodeItem(item1); 29 | 30 | Assert.AreEqual(item2, manager.TranscodeItems.Single()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockMusicFileContext.cs: -------------------------------------------------------------------------------- 1 | using Test.MusicManager.Domain.MusicFiles; 2 | using Waf.MusicManager.Applications.Services; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | 5 | namespace Test.MusicManager.Applications.Services; 6 | 7 | public class MockMusicFileContext : IMusicFileContext 8 | { 9 | private readonly Dictionary musicFilesCache = new(); 10 | 11 | public Action? ApplyChangesAction { get; set; } 12 | 13 | public Func? SaveChangesAsyncAction { get; set; } 14 | 15 | public MusicFile Create(string fileName) 16 | { 17 | if (!musicFilesCache.TryGetValue(fileName, out var musicFile)) 18 | { 19 | musicFile = MockMusicFile.CreateEmpty(fileName); 20 | musicFilesCache.Add(fileName, musicFile); 21 | } 22 | return musicFile; 23 | } 24 | 25 | public MusicFile CreateFromMultiple(IReadOnlyList musicFiles) 26 | { 27 | return new MockMusicFile(new MusicMetadata(new TimeSpan(0, 3, 33), 320000), null) { SharedMusicFiles = musicFiles.ToArray() }; 28 | } 29 | 30 | public void ApplyChanges(MusicFile musicFile) => ApplyChangesAction?.Invoke(musicFile); 31 | 32 | public Task SaveChangesAsync(MusicFile musicFile) => SaveChangesAsyncAction?.Invoke(musicFile) ?? Task.CompletedTask; 33 | } 34 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Converters/FilterOperatorToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using Waf.MusicManager.Applications.DataModels; 4 | using Waf.MusicManager.Presentation.Properties; 5 | 6 | namespace Waf.MusicManager.Presentation.Converters; 7 | 8 | public class FilterOperatorToStringConverter : IValueConverter 9 | { 10 | public static FilterOperatorToStringConverter Default { get; } = new(); 11 | 12 | public object? Convert(object? value, Type? targetType, object? parameter, CultureInfo? culture) 13 | { 14 | if (value == null) return null; 15 | var isDescription = ConverterHelper.IsParameterSet("description", parameter); 16 | var filterOperator = (FilterOperator)value; 17 | return filterOperator switch 18 | { 19 | FilterOperator.Ignore => isDescription ? Resources.IgnoreValue : "", 20 | FilterOperator.LessThanOrEqual => isDescription ? Resources.LessThanOrEqual : "<=", 21 | FilterOperator.GreaterThanOrEqual => isDescription ? Resources.GreaterThanOrEqual : ">=", 22 | _ => throw new InvalidOperationException("Enum value is unknown."), 23 | }; 24 | } 25 | 26 | public object ConvertBack(object? value, Type? targetType, object? parameter, CultureInfo? culture) => throw new NotSupportedException(); 27 | } 28 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Views/MockShellView.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.UnitTesting.Mocks; 2 | using Waf.MusicManager.Applications.Views; 3 | 4 | namespace Test.MusicManager.Applications.Views; 5 | 6 | public class MockShellView : MockView, IShellView 7 | { 8 | public double VirtualScreenWidth { get; set; } 9 | 10 | public double VirtualScreenHeight { get; set; } 11 | 12 | public double Left { get; set; } 13 | 14 | public double Top { get; set; } 15 | 16 | public double Width { get; set; } 17 | 18 | public double Height { get; set; } 19 | 20 | public bool IsMaximized { get; set; } 21 | 22 | public bool IsVisible { get; private set; } 23 | 24 | public event CancelEventHandler? Closing; 25 | 26 | public event EventHandler? Closed; 27 | 28 | public void Show() => IsVisible = true; 29 | 30 | public void Close() 31 | { 32 | OnClosed(EventArgs.Empty); 33 | IsVisible = false; 34 | } 35 | 36 | public void SetNAForLocationAndSize() 37 | { 38 | Top = double.NaN; 39 | Left = double.NaN; 40 | Width = double.NaN; 41 | Height = double.NaN; 42 | } 43 | 44 | public void RaiseClosing(CancelEventArgs e) => Closing?.Invoke(this, e); 45 | 46 | protected void OnClosed(EventArgs e) => Closed?.Invoke(this, e); 47 | } 48 | -------------------------------------------------------------------------------- /src/MusicManager/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Waf Music Manager 4 | Waf 5 | Copyright © 2016-2025 jbe2277 6 | 3.4.1.100 7 | en-US 8 | enable 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | false 16 | $(MSBuildThisFileDirectory)CodeCoverage.runsettings 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | AllEnabledByDefault 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Controls/SearchableTextBlockTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Presentation.Controls; 4 | 5 | namespace Test.MusicManager.Presentation.Controls; 6 | 7 | [TestClass] 8 | public class SearchableTextBlockTest 9 | { 10 | [TestMethod] 11 | public void SplitTextTest() 12 | { 13 | AssertHelper.SequenceEqual([""], SearchableTextBlock.SplitText("", "", false)); 14 | AssertHelper.SequenceEqual([""], SearchableTextBlock.SplitText("", "", true)); 15 | 16 | AssertHelper.SequenceEqual(["Hello"], SearchableTextBlock.SplitText("Hello", "", false)); 17 | AssertHelper.SequenceEqual(["", "Hello", ""], SearchableTextBlock.SplitText("Hello", "Hello", false)); 18 | AssertHelper.SequenceEqual(["", "Hello", " World"], SearchableTextBlock.SplitText("Hello World", "Hello", false)); 19 | AssertHelper.SequenceEqual(["", "Hello", "", "Hello", "-", "Hello", "."], SearchableTextBlock.SplitText("HelloHello-Hello.", "Hello", false)); 20 | 21 | AssertHelper.SequenceEqual(["", "Hello", ""], SearchableTextBlock.SplitText("Hello", "Hello", true)); 22 | AssertHelper.SequenceEqual(["Hello"], SearchableTextBlock.SplitText("Hello", "HELLO", true)); 23 | AssertHelper.SequenceEqual(["Hello", "HELLO", ""], SearchableTextBlock.SplitText("HelloHELLO", "HELLO", true)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/ViewModels/InfoViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Waf.Applications; 4 | using System.Windows.Input; 5 | using Waf.MusicManager.Applications.Views; 6 | 7 | namespace Waf.MusicManager.Applications.ViewModels; 8 | 9 | public class InfoViewModel : ViewModel 10 | { 11 | public InfoViewModel(IInfoView view) : base(view) 12 | { 13 | ShowWebsiteCommand = new DelegateCommand(ShowWebsite); 14 | } 15 | 16 | public ICommand ShowWebsiteCommand { get; } 17 | 18 | public string ProductName => ApplicationInfo.ProductName; 19 | 20 | public string Version => ApplicationInfo.Version; 21 | 22 | public string OSVersion => Environment.OSVersion.ToString(); 23 | 24 | public string NetVersion => Environment.Version.ToString(); 25 | 26 | public Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture; 27 | 28 | public void ShowDialog(object owner) => ViewCore.ShowDialog(owner); 29 | 30 | private void ShowWebsite(object? parameter) 31 | { 32 | var url = (string)parameter!; 33 | try 34 | { 35 | Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); 36 | } 37 | catch (Exception e) 38 | { 39 | Log.Default.Error(e, "An exception occured when trying to show the url '{0}'.", url); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/MusicTitleConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Presentation.Converters; 4 | 5 | namespace Test.MusicManager.Presentation.Converters; 6 | 7 | [TestClass] 8 | public class MusicTitleConverterTest 9 | { 10 | [TestMethod] 11 | public void ConvertTest() 12 | { 13 | var converter = MusicTitleConverter.Default; 14 | var fileName = @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.mp3"; 15 | 16 | Assert.AreEqual("Culture Beat - Serenity", ConvertCore(converter, fileName, [], null)); 17 | Assert.AreEqual("Culture Beat - Serenity", ConvertCore(converter, fileName, [], "")); 18 | 19 | Assert.AreEqual("", ConvertCore(converter, fileName, [ "Culture Beat" ], null)); 20 | Assert.AreEqual("", ConvertCore(converter, fileName, [ "Culture Beat" ], "")); 21 | 22 | Assert.AreEqual("Serenity (Epilog)", ConvertCore(converter, fileName, [ "Culture Beat" ], "Serenity (Epilog)")); 23 | 24 | AssertHelper.ExpectedException(() => converter.ConvertBack(null, null, null, null)); 25 | } 26 | 27 | private static string ConvertCore(MusicTitleConverter converter, string fileName, IEnumerable artists, string? title) 28 | { 29 | return (string)converter.Convert([ fileName, artists, title ], null, null, null); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/DataModels/MusicFileDataModelTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Globalization; 3 | using System.Waf.UnitTesting; 4 | using Test.MusicManager.Domain.MusicFiles; 5 | using Waf.MusicManager.Applications.DataModels; 6 | using Waf.MusicManager.Domain.MusicFiles; 7 | 8 | namespace Test.MusicManager.Applications.DataModels; 9 | 10 | [TestClass] 11 | public class MusicFileDataModelTest : ApplicationsTest 12 | { 13 | [TestMethod] 14 | public void RaisePropertyChangedTest() 15 | { 16 | var ls = CultureInfo.CurrentCulture.TextInfo.ListSeparator; 17 | 18 | var musicFile = new MockMusicFile(new MusicMetadata(new TimeSpan(0, 3, 33), 320) 19 | { 20 | Artists = [], 21 | Title = "" 22 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.mp3"); 23 | var dataModel = new MusicFileDataModel(musicFile); 24 | 25 | Assert.AreEqual("", dataModel.ArtistsString); 26 | 27 | AssertHelper.PropertyChangedEvent(dataModel, x => x.ArtistsString, () => musicFile.Metadata!.Artists = [ "Culture Beat" ]); 28 | Assert.AreEqual("Culture Beat", dataModel.ArtistsString); 29 | 30 | AssertHelper.PropertyChangedEvent(dataModel, x => x.ArtistsString, () => musicFile.Metadata!.Artists = [ "Culture Beat", "Second artist" ]); 31 | Assert.AreEqual("Culture Beat" + ls + " Second artist", dataModel.ArtistsString); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/EnvironmentService.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Waf.Applications; 3 | using Waf.MusicManager.Applications.Services; 4 | 5 | namespace Waf.MusicManager.Presentation.Services; 6 | 7 | internal class EnvironmentService : IEnvironmentService 8 | { 9 | private static readonly Lazy appDataPath = new(() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ApplicationInfo.ProductName)); 10 | private static readonly Lazy profilePath = new(() => Path.Combine(AppDataPath, "ProfileOptimization")); 11 | private static readonly Lazy logPath = new(() => Path.Combine(AppDataPath, "Log")); 12 | private readonly Lazy> musicFilesToLoad; 13 | 14 | public EnvironmentService() 15 | { 16 | musicFilesToLoad = new(() => Environment.GetCommandLineArgs().Skip(1).ToArray()); 17 | MusicPath = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic); 18 | PublicMusicPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonMusic); 19 | } 20 | 21 | public static string AppDataPath => appDataPath.Value; 22 | 23 | public static string ProfilePath => profilePath.Value; 24 | 25 | public static string LogPath => logPath.Value; 26 | 27 | public IReadOnlyList MusicFilesToLoad => musicFilesToLoad.Value; 28 | 29 | public string MusicPath { get; } 30 | 31 | public string PublicMusicPath { get; } 32 | } 33 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Playlists/PlayedItemsStackTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Domain.Playlists; 3 | 4 | namespace Test.MusicManager.Domain.Playlists; 5 | 6 | [TestClass] 7 | public class PlayedItemsStackTest : DomainTest 8 | { 9 | [TestMethod] 10 | public void StackTest() 11 | { 12 | var items = new[] { new object(), new object(), new object(), new object() }; 13 | 14 | var stack = new PlayedItemsStack(3); 15 | Assert.AreEqual(0, stack.Count); 16 | stack.Add(items[0]); 17 | Assert.AreEqual(items[0], stack.Pop()); 18 | Assert.AreEqual(0, stack.Count); 19 | 20 | stack.Add(items[0]); 21 | stack.Add(items[1]); 22 | stack.Add(items[2]); 23 | Assert.AreEqual(3, stack.Count); 24 | Assert.IsTrue(stack.Contains(items[0])); 25 | Assert.IsFalse(stack.Contains(items[3])); 26 | 27 | stack.Add(items[3]); 28 | Assert.AreEqual(3, stack.Count); 29 | Assert.IsFalse(stack.Contains(items[0])); 30 | Assert.IsTrue(stack.Contains(items[3])); 31 | 32 | Assert.AreEqual(items[3], stack.Pop()); 33 | Assert.AreEqual(2, stack.Count); 34 | 35 | stack.Add(items[2]); 36 | Assert.AreEqual(3, stack.Count); 37 | stack.RemoveAll(items[2]); 38 | Assert.AreEqual(1, stack.Count); 39 | 40 | stack.Clear(); 41 | Assert.AreEqual(0, stack.Count); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/MusicManager.Presentation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net9.0-windows10.0.19041 5 | true 6 | Waf.MusicManager.Presentation 7 | MusicManager 8 | Waf Music Manager 9 | Resources\Images\MusicManager.ico 10 | AnyCPU;x64;ARM64 11 | true 12 | 13 | 14 | 15 | x64 16 | 17 | 18 | ARM64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/MockShellService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Properties; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Presentation.DesignData; 5 | 6 | public class MockShellService : Model, IShellService 7 | { 8 | public MockShellService() 9 | { 10 | Settings = new AppSettings(); 11 | } 12 | 13 | public AppSettings Settings { get; set; } 14 | 15 | public object ShellView { get; set; } = null!; 16 | 17 | public object? ContentView { get; set; } 18 | 19 | public object? MusicPropertiesView { get; set; } 20 | 21 | public object? PlaylistView { get; set; } 22 | 23 | public Lazy? TranscodingListView { get; set; } 24 | 25 | public object? PlayerView { get; set; } 26 | 27 | public IReadOnlyCollection TasksToCompleteBeforeShutdown { get; set; } = null!; 28 | 29 | public bool IsApplicationBusy { get; set; } 30 | 31 | public event CancelEventHandler? Closing; 32 | 33 | public void ShowError(Exception exception, string displayMessage) { } 34 | 35 | public void ShowMusicPropertiesView() { } 36 | 37 | public void ShowPlaylistView() { } 38 | 39 | public void ShowTranscodingListView() { } 40 | 41 | public void AddTaskToCompleteBeforeShutdown(Task task) { } 42 | 43 | public IDisposable SetApplicationBusy() => null!; 44 | 45 | protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); 46 | } 47 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/Services/MockFileService.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.Services; 2 | 3 | namespace Test.MusicManager.Applications.Services; 4 | 5 | public class MockFileService : IFileService 6 | { 7 | public bool DirectoryExists(string path) => true; 8 | 9 | public Task DeleteFile(string fileName) => throw new NotImplementedException(); 10 | 11 | public Task GetDisplayPath(string path) => Task.FromResult(path); 12 | 13 | public Func>>? GetFilesStub { get; set; } 14 | public Task> GetFiles(string directory, bool deep, string userSearchFilter, string applicationSearchFilter, CancellationToken cancellation) 15 | => GetFilesStub?.Invoke(directory, deep, userSearchFilter, applicationSearchFilter, cancellation) ?? Task.FromResult>([]); 16 | 17 | public Task GetFolderFromPath(string path) => Task.FromResult(new FolderItem(path, path)); 18 | 19 | public Task GetParentPath(string path) => throw new NotImplementedException(); 20 | 21 | public Task> GetSubFoldersFromPath(string path) => Task.FromResult>([]); 22 | 23 | public bool IsFileSupported(string fileName) => true; 24 | 25 | public Task> ReadPlaylist(string playlistFileName) => throw new NotImplementedException(); 26 | 27 | public Task SavePlaylist(string playlistFileName, IReadOnlyList fileNames) => throw new NotImplementedException(); 28 | } 29 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/Transcoding/TranscodeItem.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Domain.MusicFiles; 2 | 3 | namespace Waf.MusicManager.Domain.Transcoding; 4 | 5 | public class TranscodeItem : Model 6 | { 7 | private TranscodeStatus transcodeStatus; 8 | private double progress; 9 | private Exception? error; 10 | 11 | public TranscodeItem(MusicFile source, string destinationFileName) 12 | { 13 | Source = source; 14 | DestinationFileName = destinationFileName; 15 | UpdateStatus(); 16 | } 17 | 18 | public MusicFile Source { get; } 19 | 20 | public string DestinationFileName { get; } 21 | 22 | public TranscodeStatus TranscodeStatus 23 | { 24 | get => transcodeStatus; 25 | private set => SetProperty(ref transcodeStatus, value); 26 | } 27 | 28 | public double Progress 29 | { 30 | get => progress; 31 | set 32 | { 33 | if (!SetProperty(ref progress, value)) return; 34 | UpdateStatus(); 35 | } 36 | } 37 | 38 | public Exception? Error 39 | { 40 | get => error; 41 | set 42 | { 43 | if (!SetProperty(ref error, value)) return; 44 | UpdateStatus(); 45 | } 46 | } 47 | 48 | private void UpdateStatus() 49 | { 50 | TranscodeStatus = this switch 51 | { 52 | { Error: not null } => TranscodeStatus.Error, 53 | { Progress: 0 } => TranscodeStatus.Pending, 54 | { Progress: < 1} => TranscodeStatus.InProgress, 55 | _ => TranscodeStatus.Completed 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/DataModels/MusicFileDataModel.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Waf.MusicManager.Domain.MusicFiles; 3 | 4 | namespace Waf.MusicManager.Applications.DataModels; 5 | 6 | public class MusicFileDataModel : Model 7 | { 8 | private IWeakEventProxy? musicFilePropertyChangedProxy; 9 | 10 | public MusicFileDataModel(MusicFile musicFile) 11 | { 12 | MusicFile = musicFile; 13 | if (musicFile.IsMetadataLoaded) 14 | { 15 | MetadataLoaded(); 16 | } 17 | else 18 | { 19 | musicFilePropertyChangedProxy = WeakEvent.PropertyChanged.Add(musicFile, MusicFilePropertyChanged); 20 | } 21 | } 22 | 23 | public MusicFile MusicFile { get; } 24 | 25 | public string ArtistsString => string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", MusicFile.IsMetadataLoaded ? MusicFile.Metadata.Artists : []); 26 | 27 | private void MetadataLoaded() => PropertyChangedEventManager.AddHandler(MusicFile.Metadata, MetadataPropertyChanged, ""); 28 | 29 | private void MusicFilePropertyChanged(object? sender, PropertyChangedEventArgs e) 30 | { 31 | if (e.PropertyName == nameof(MusicFile.IsMetadataLoaded)) 32 | { 33 | WeakEvent.TryRemove(ref musicFilePropertyChangedProxy); 34 | MetadataLoaded(); 35 | RaisePropertyChanged(nameof(ArtistsString)); 36 | } 37 | } 38 | 39 | private void MetadataPropertyChanged(object? sender, PropertyChangedEventArgs e) 40 | { 41 | if (e.PropertyName == nameof(MusicMetadata.Artists)) RaisePropertyChanged(nameof(ArtistsString)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleManagerViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.DataModels; 2 | using Waf.MusicManager.Applications.Services; 3 | using Waf.MusicManager.Applications.ViewModels; 4 | using Waf.MusicManager.Applications.Views; 5 | using Waf.MusicManager.Domain.MusicFiles; 6 | 7 | namespace Waf.MusicManager.Presentation.DesignData; 8 | 9 | public class SampleManagerViewModel : ManagerViewModel 10 | { 11 | public SampleManagerViewModel() : base(new MockManagerView(), new Lazy(() => new MockSelectionService()), null!, null!, null!) 12 | { 13 | var musicFiles = new[] 14 | { 15 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 3, 45), 320) 16 | { 17 | Artists = [ @"Culture Beat" ], 18 | Title = @"Serenity (Epilog)", 19 | 20 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.mp3"), 21 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 2, 2), 320) 22 | { 23 | Artists = [ "First artist", "Second artist" ], 24 | Title = "", 25 | }, ""), 26 | new SampleMusicFile(new MusicMetadata(new TimeSpan(1, 33, 0), 320) 27 | { 28 | Artists = [], 29 | Title = "", 30 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Mr. Vain.mp3") 31 | }; 32 | ((MockSelectionService)SelectionService).SetMusicFiles(musicFiles.Select(x => new MusicFileDataModel(x)).ToArray()); 33 | } 34 | 35 | 36 | private class MockManagerView : MockView, IManagerView 37 | { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/TranscodingService.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | using System.Windows.Input; 3 | 4 | namespace Waf.MusicManager.Applications.Services; 5 | 6 | internal class TranscodingService : Model, ITranscodingService 7 | { 8 | private ICommand convertToMp3AllCommand = DelegateCommand.DisabledCommand; 9 | private ICommand convertToMp3SelectedCommand = DelegateCommand.DisabledCommand; 10 | private ICommand cancelAllCommand = DelegateCommand.DisabledCommand; 11 | private ICommand cancelSelectedCommand = DelegateCommand.DisabledCommand; 12 | 13 | public ICommand ConvertToMp3AllCommand 14 | { 15 | get => convertToMp3AllCommand; 16 | set => SetProperty(ref convertToMp3AllCommand, value); 17 | } 18 | 19 | public ICommand ConvertToMp3SelectedCommand 20 | { 21 | get => convertToMp3SelectedCommand; 22 | set => SetProperty(ref convertToMp3SelectedCommand, value); 23 | } 24 | 25 | public ICommand CancelAllCommand 26 | { 27 | get => cancelAllCommand; 28 | set => SetProperty(ref cancelAllCommand, value); 29 | } 30 | 31 | public ICommand CancelSelectedCommand 32 | { 33 | get => cancelSelectedCommand; 34 | set => SetProperty(ref cancelSelectedCommand, value); 35 | } 36 | 37 | public event EventHandler? TranscodingTaskCreated; 38 | 39 | public void RaiseTranscodingTaskCreated(string fileName, Task transcodingTask) => OnTranscodingTaskCreated(new(fileName, transcodingTask)); 40 | 41 | protected virtual void OnTranscodingTaskCreated(TranscodingTaskEventArgs e) => TranscodingTaskCreated?.Invoke(this, e); 42 | } 43 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleShellViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using Waf.MusicManager.Applications.ViewModels; 3 | using Waf.MusicManager.Applications.Views; 4 | 5 | namespace Waf.MusicManager.Presentation.DesignData; 6 | 7 | public class SampleShellViewModel : ShellViewModel 8 | { 9 | public SampleShellViewModel() : base(new MockShellView(), new MockShellService(), null!) 10 | { 11 | ShellService.PlaylistView = new Control(); 12 | IsPlaylistViewVisible = true; 13 | ShowError(new InvalidOperationException(), "A very long error message that does not fit in a default size window. Thus, the text block should trim the text and show ellipsis." 14 | + " And more text so that it is really too long."); 15 | } 16 | 17 | private class MockShellView : MockView, IShellView 18 | { 19 | public double VirtualScreenWidth => 0; 20 | 21 | public double VirtualScreenHeight => 0; 22 | 23 | public double Left { get; set; } 24 | 25 | public double Top { get; set; } 26 | 27 | public double Width { get; set; } 28 | 29 | public double Height { get; set; } 30 | 31 | public bool IsMaximized { get; set; } 32 | 33 | public event CancelEventHandler? Closing; 34 | 35 | public event EventHandler? Closed; 36 | 37 | public void Show() { } 38 | 39 | public void Close() { } 40 | 41 | protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); 42 | 43 | protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain/MusicFiles/Entity.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Waf.MusicManager.Domain.MusicFiles; 4 | 5 | public abstract class Entity : Model 6 | { 7 | private readonly Lazy changeTrackerService; 8 | private readonly HashSet changes; 9 | private bool entityLoaded; 10 | private bool hasChanges; 11 | 12 | protected Entity() 13 | { 14 | changeTrackerService = new(ServiceLocator.Get); 15 | changes = []; 16 | } 17 | 18 | public bool HasChanges 19 | { 20 | get => hasChanges; 21 | private set => SetProperty(ref hasChanges, value); 22 | } 23 | 24 | public IReadOnlySet Changes => changes; 25 | 26 | public void EntityLoadCompleted() => entityLoaded = true; 27 | 28 | public void ClearChanges() 29 | { 30 | changes.Clear(); 31 | HasChanges = false; 32 | } 33 | 34 | protected bool SetPropertyAndTrackChanges([NotNullIfNotNull(parameterName: "value"), MaybeNull] ref T field, [AllowNull] T value, [CallerMemberName] string propertyName = null!) 35 | { 36 | if (!SetProperty(ref field, value, propertyName)) return false; 37 | if (entityLoaded) 38 | { 39 | changes.Add(propertyName); 40 | HasChanges = true; 41 | } 42 | return true; 43 | } 44 | 45 | protected override void OnPropertyChanged(PropertyChangedEventArgs e) 46 | { 47 | base.OnPropertyChanged(e); 48 | if (entityLoaded && e.PropertyName == nameof(HasChanges) && HasChanges) 49 | { 50 | changeTrackerService.Value.EntityHasChanges(this); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/DomainTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Collections; 3 | using System.Globalization; 4 | using System.Reflection; 5 | using System.Waf.UnitTesting; 6 | 7 | namespace Test.MusicManager.Domain; 8 | 9 | [TestClass] 10 | public abstract class DomainTest 11 | { 12 | [TestInitialize] 13 | public void Initialize() 14 | { 15 | CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); 16 | CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US"); 17 | OnInitialize(); 18 | } 19 | 20 | [TestCleanup] 21 | public void Cleanup() => OnCleanup(); 22 | 23 | protected virtual void OnInitialize() { } 24 | 25 | protected virtual void OnCleanup() { } 26 | 27 | public static void AssertHaveEqualPropertyValues(T expected, T actual, Func? predicate = null) 28 | { 29 | var objectType = typeof(T); 30 | var properties = objectType.GetProperties(); 31 | predicate ??= (p => true); 32 | 33 | foreach (var property in properties.Where(predicate)) 34 | { 35 | if (property.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) 36 | { 37 | var expectedCollection = ((IEnumerable)property.GetValue(expected)!).Cast(); 38 | var actualCollection = ((IEnumerable)property.GetValue(actual)!).Cast(); 39 | AssertHelper.SequenceEqual(expectedCollection, actualCollection); 40 | } 41 | else 42 | { 43 | Assert.AreEqual(property.GetValue(expected), property.GetValue(actual), "Property name: " + property.Name); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicFiles/MusicMetadataTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Domain.MusicFiles; 3 | 4 | namespace Test.MusicManager.Domain.MusicFiles; 5 | 6 | [TestClass] 7 | public class MusicMetadataTest : DomainTest 8 | { 9 | [TestMethod] 10 | public void ApplyValuesFromTest() 11 | { 12 | var metadata1 = new MusicMetadata(TimeSpan.FromSeconds(1), 64000); 13 | var metadata2 = new MusicMetadata(TimeSpan.FromSeconds(1), 64000); 14 | Assert.IsTrue(metadata1.IsSupported); 15 | Assert.IsTrue(metadata2.IsSupported); 16 | 17 | metadata2.Artists = new[] { "Artist1", "Artist2" }; 18 | metadata2.Title = "Title"; 19 | metadata2.Rating = 80; 20 | metadata2.Album = "Album"; 21 | metadata2.TrackNumber = 3; 22 | metadata2.Year = 2000; 23 | metadata2.Genre = new[] { "Genre1", "Genre2" }; 24 | metadata2.AlbumArtist = "AlbumArtist"; 25 | metadata2.Publisher = "Publisher"; 26 | metadata2.Subtitle = "Subtitle"; 27 | metadata2.Composers = new[] { "Composer1", "Composer2" }; 28 | metadata2.Conductors = new[] { "Conductor1", "Conductor2" }; 29 | 30 | metadata1.ApplyValuesFrom(metadata2); 31 | 32 | AssertHaveEqualPropertyValues(metadata2, metadata1, p => p.Name != nameof(MusicMetadata.Parent)); 33 | } 34 | 35 | [TestMethod] 36 | public void UnsupportedMetadataTest() 37 | { 38 | var metadata = MusicMetadata.CreateUnsupported(TimeSpan.FromMinutes(3), 1024); 39 | Assert.IsFalse(metadata.IsSupported); 40 | Assert.AreEqual(TimeSpan.FromMinutes(3), metadata.Duration); 41 | Assert.AreEqual(1024, metadata.Bitrate); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/ApplicationsTest.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Waf.UnitTesting; 4 | using Test.MusicManager.Domain; 5 | using Waf.MusicManager.Applications; 6 | using Waf.MusicManager.Applications.Properties; 7 | using Waf.MusicManager.Applications.Services; 8 | using Waf.MusicManager.Domain.MusicFiles; 9 | using IContainer = Autofac.IContainer; 10 | 11 | namespace Test.MusicManager.Applications; 12 | 13 | [TestClass] 14 | public abstract class ApplicationsTest : DomainTest 15 | { 16 | protected UnitTestSynchronizationContext Context { get; private set; } = null!; 17 | 18 | protected IContainer Container { get; private set; } = null!; 19 | 20 | public T Get() where T : notnull => Container.Resolve(); 21 | 22 | public Lazy GetLazy() where T : notnull => new(Get); 23 | 24 | protected override void OnInitialize() 25 | { 26 | base.OnInitialize(); 27 | 28 | Context = UnitTestSynchronizationContext.Create(); 29 | 30 | var builder = new ContainerBuilder(); 31 | ConfigureContainer(builder); 32 | Container = builder.Build(); 33 | 34 | var shellService = Container.Resolve(); 35 | shellService.Settings = new AppSettings(); 36 | 37 | ServiceLocator.RegisterInstance(new ChangeTrackerService()); 38 | } 39 | 40 | protected override void OnCleanup() 41 | { 42 | Container.Dispose(); 43 | Context.Dispose(); 44 | base.OnCleanup(); 45 | } 46 | 47 | protected virtual void ConfigureContainer(ContainerBuilder builder) 48 | { 49 | builder.RegisterModule(new ApplicationsModule()); 50 | builder.RegisterModule(new MockPresentationModule()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Packaging/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 15 | Waf Music Manager 16 | Juergen Berchtel 17 | Images\StoreLogo.png 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/Transcoder.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Waf.MusicManager.Applications.Services; 3 | using Windows.Media.MediaProperties; 4 | using Windows.Media.Transcoding; 5 | using Windows.Storage; 6 | 7 | namespace Waf.MusicManager.Presentation.Services; 8 | 9 | internal class Transcoder : ITranscoder 10 | { 11 | public async Task TranscodeAsync(string sourceFileName, string destinationFileName, uint bitrate, CancellationToken cancellationToken, IProgress progress) 12 | { 13 | var transcoder = new MediaTranscoder(); 14 | var sourceFile = await StorageFile.GetFileFromPathAsync(sourceFileName); 15 | var destinationFolder = await StorageFolder.GetFolderFromPathAsync(Path.GetDirectoryName(destinationFileName)); 16 | var destinationFile = await destinationFolder.CreateFileAsync(Path.GetFileName(destinationFileName)); 17 | 18 | var profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); 19 | profile.Audio.Bitrate = bitrate; 20 | 21 | var preparedTranscodeResult = await transcoder.PrepareFileTranscodeAsync(sourceFile, destinationFile, profile); 22 | 23 | Exception? error = null; 24 | try 25 | { 26 | cancellationToken.ThrowIfCancellationRequested(); 27 | if (preparedTranscodeResult.CanTranscode) 28 | { 29 | await preparedTranscodeResult.TranscodeAsync().AsTask(cancellationToken, progress); 30 | } 31 | else 32 | { 33 | throw new InvalidOperationException("Reason: " + preparedTranscodeResult.FailureReason); 34 | } 35 | } 36 | catch (Exception ex) 37 | { 38 | error = ex; 39 | } 40 | 41 | if (error != null) 42 | { 43 | await destinationFile.DeleteAsync(StorageDeleteOption.PermanentDelete); 44 | throw error; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/TaskUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Waf.MusicManager.Applications; 2 | 3 | public static class TaskUtility 4 | { 5 | public static TResult GetResult(this Task task) 6 | { 7 | // This ensures the same exception behavior as it would be with 'await'. No AggregateException - just the first one that occurred. 8 | return task.GetAwaiter().GetResult(); 9 | } 10 | 11 | // Similar as Task.WhenAll but the task completes after the first one throws an exception (does not wait for all other tasks to complete). 12 | public static Task WhenAllFast(ReadOnlySpan tasks) 13 | { 14 | if (tasks.IsEmpty) return Task.CompletedTask; 15 | 16 | var taskCompletionSource = new TaskCompletionSource(); 17 | int count = tasks.Length; 18 | 19 | foreach (var task in tasks) 20 | { 21 | task.ContinueWith(t => 22 | { 23 | if (taskCompletionSource.Task.IsCompleted) 24 | { 25 | ObserveException(t.Exception); 26 | return; 27 | } 28 | 29 | if (t.IsCanceled) 30 | { 31 | taskCompletionSource.TrySetCanceled(); 32 | } 33 | else if (t.IsFaulted) 34 | { 35 | taskCompletionSource.TrySetException(t.Exception!.Flatten().InnerExceptions); 36 | } 37 | else 38 | { 39 | // Decrement the count and continue if this was the last task. 40 | if (Interlocked.Decrement(ref count) == 0) taskCompletionSource.SetResult(null); 41 | } 42 | 43 | }, TaskContinuationOptions.ExecuteSynchronously); 44 | } 45 | return taskCompletionSource.Task; 46 | 47 | static void ObserveException(Exception? ex) { /* Nothing to do. */ } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Converters/RatingToStarsConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Waf.MusicManager.Presentation.Converters; 3 | 4 | namespace Test.MusicManager.Presentation.Converters; 5 | 6 | [TestClass] 7 | public class RatingToStarsConverterTest 8 | { 9 | [TestMethod] 10 | public void ConvertTest() 11 | { 12 | var converter = RatingToStarsConverter.Default; 13 | Assert.AreEqual(5, converter.Convert(100, null, null, null)); 14 | Assert.AreEqual(5, converter.Convert(99, null, null, null)); 15 | 16 | Assert.AreEqual(4, converter.Convert(98, null, null, null)); 17 | Assert.AreEqual(4, converter.Convert(75, null, null, null)); 18 | 19 | Assert.AreEqual(3, converter.Convert(74, null, null, null)); 20 | Assert.AreEqual(3, converter.Convert(50, null, null, null)); 21 | 22 | Assert.AreEqual(2, converter.Convert(49, null, null, null)); 23 | Assert.AreEqual(2, converter.Convert(25, null, null, null)); 24 | 25 | Assert.AreEqual(1, converter.Convert(24, null, null, null)); 26 | Assert.AreEqual(1, converter.Convert(1, null, null, null)); 27 | 28 | Assert.AreEqual(0, converter.Convert(0, null, null, null)); 29 | Assert.AreEqual(0, converter.Convert(-1, null, null, null)); 30 | } 31 | 32 | [TestMethod] 33 | public void ConvertBackTest() 34 | { 35 | var converter = RatingToStarsConverter.Default; 36 | Assert.AreEqual(99, converter.ConvertBack(5, null, null, null)); 37 | Assert.AreEqual(75, converter.ConvertBack(4, null, null, null)); 38 | Assert.AreEqual(50, converter.ConvertBack(3, null, null, null)); 39 | Assert.AreEqual(25, converter.ConvertBack(2, null, null, null)); 40 | Assert.AreEqual(1, converter.ConvertBack(1, null, null, null)); 41 | Assert.AreEqual(0, converter.ConvertBack(0, null, null, null)); 42 | Assert.AreEqual(0, converter.ConvertBack(-1, null, null, null)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/ApplicationsModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using System.Waf.Applications; 3 | using Waf.MusicManager.Applications.Controllers; 4 | using Waf.MusicManager.Applications.Services; 5 | using Waf.MusicManager.Applications.ViewModels; 6 | 7 | namespace Waf.MusicManager.Applications; 8 | 9 | public class ApplicationsModule : Module 10 | { 11 | protected override void Load(ContainerBuilder builder) 12 | { 13 | builder.RegisterType().AsSelf().SingleInstance(); 14 | builder.RegisterType().As().AsSelf().SingleInstance(); 15 | builder.RegisterType().As().AsSelf().SingleInstance(); 16 | builder.RegisterType().AsSelf().SingleInstance(); 17 | builder.RegisterType().As().AsSelf().SingleInstance(); 18 | builder.RegisterType().AsSelf().SingleInstance(); 19 | 20 | builder.RegisterType().As().AsSelf().SingleInstance(); 21 | builder.RegisterType().As().AsSelf().SingleInstance(); 22 | builder.RegisterType().As().AsSelf().SingleInstance(); 23 | builder.RegisterType().As().AsSelf().SingleInstance(); 24 | builder.RegisterType().As().AsSelf().SingleInstance(); 25 | 26 | builder.RegisterType().AsSelf(); 27 | builder.RegisterType().AsSelf().SingleInstance(); 28 | builder.RegisterType().AsSelf().SingleInstance(); 29 | builder.RegisterType().AsSelf().SingleInstance(); 30 | builder.RegisterType().AsSelf().SingleInstance(); 31 | builder.RegisterType().AsSelf().SingleInstance(); 32 | builder.RegisterType().AsSelf().SingleInstance(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SamplePlaylistViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.ViewModels; 2 | using Waf.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.Playlists; 5 | 6 | namespace Waf.MusicManager.Presentation.DesignData; 7 | 8 | public class SamplePlaylistViewModel : PlaylistViewModel 9 | { 10 | public SamplePlaylistViewModel() : base(new MockPlaylistView()) 11 | { 12 | var musicFiles = new[] 13 | { 14 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 3, 45), 320000) 15 | { 16 | Artists = [ @"Culture Beat" ], 17 | Title = @"Serenity (Epilog)", 18 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity"), 19 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 2, 2), 320000) 20 | { 21 | Artists = [ "First artist", "Second artist" ], 22 | Title = "This track has a very long title. Let's see how the UI handles this.", 23 | }, ""), 24 | new SampleMusicFile(new MusicMetadata(new TimeSpan(1, 33, 0), 320000) 25 | { 26 | Artists = [], 27 | Title = "", 28 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity"), 29 | new MusicFile(x => { throw new InvalidOperationException("Sample exception."); }, @"C:\corruptfile.mp3") 30 | }; 31 | var playlistManager = new PlaylistManager(); 32 | playlistManager.AddItems(musicFiles.Select(x => new PlaylistItem(x))); 33 | PlaylistManager = playlistManager; 34 | playlistManager.CurrentItem = playlistManager.Items[0]; 35 | } 36 | 37 | 38 | private class MockPlaylistView : MockView, IPlaylistView 39 | { 40 | public void FocusSearchBox() { } 41 | 42 | public void FocusSelectedItem() { } 43 | 44 | public void ScrollIntoView(PlaylistItem item) { } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/MusicManager/CodeCoverage.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | .*\\Test\..* 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ^System\.Diagnostics\.DebuggerHiddenAttribute$ 22 | ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ 23 | ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ 24 | ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | True 39 | True 40 | True 41 | False 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/PresentationModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using System.Waf.Applications.Services; 3 | using System.Waf.Presentation.Services; 4 | using Waf.MusicManager.Applications.Services; 5 | using Waf.MusicManager.Applications.Views; 6 | using Waf.MusicManager.Presentation.Services; 7 | using Waf.MusicManager.Presentation.Views; 8 | 9 | namespace Waf.MusicManager.Presentation; 10 | 11 | public class PresentationModule : Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.RegisterType().As().AsSelf().SingleInstance(); 16 | builder.RegisterType().As().SingleInstance(); 17 | builder.RegisterType().As().SingleInstance(); 18 | 19 | builder.RegisterType().As().AsSelf().SingleInstance(); 20 | builder.RegisterType().As().AsSelf().SingleInstance(); 21 | builder.RegisterType().As().AsSelf().SingleInstance(); 22 | builder.RegisterType().As().AsSelf().SingleInstance(); 23 | builder.RegisterType().As().AsSelf().SingleInstance(); 24 | builder.RegisterType().As().AsSelf().SingleInstance(); 25 | 26 | builder.RegisterType().As(); 27 | builder.RegisterType().As().AsSelf().SingleInstance(); 28 | builder.RegisterType().As().AsSelf().SingleInstance(); 29 | builder.RegisterType().As().AsSelf().SingleInstance(); 30 | builder.RegisterType().As().AsSelf().SingleInstance(); 31 | builder.RegisterType().As().AsSelf().SingleInstance(); 32 | builder.RegisterType().As().AsSelf().SingleInstance(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/DesignData/SampleTranscodingListViewModel.cs: -------------------------------------------------------------------------------- 1 | using Waf.MusicManager.Applications.ViewModels; 2 | using Waf.MusicManager.Applications.Views; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.Transcoding; 5 | 6 | namespace Waf.MusicManager.Presentation.DesignData; 7 | 8 | public class SampleTranscodingListViewModel : TranscodingListViewModel 9 | { 10 | public SampleTranscodingListViewModel() : base(new MockTranscodingListView(), null!) 11 | { 12 | var musicFiles = new[] 13 | { 14 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 3, 45), 320000) 15 | { 16 | Artists = [ @"Culture Beat" ], 17 | Title = @"Serenity (Epilog)", 18 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.waf"), 19 | new SampleMusicFile(new MusicMetadata(new TimeSpan(0, 2, 2), 320000) 20 | { 21 | Artists = [ "First artist", "Second artist" ], 22 | Title = "This track has a very long title. Let's see how the UI handles this.", 23 | }, @"C:\Users\Public\Music\test.m4a"), 24 | new SampleMusicFile(new MusicMetadata(new TimeSpan(1, 33, 0), 320000) 25 | { 26 | Artists = [], 27 | Title = "", 28 | }, @"C:\Users\Public\Music\Dancefloor\Culture Beat - Serenity.mp4"), 29 | }; 30 | var transcodingManager = new TranscodingManager(); 31 | musicFiles.Select(x => new TranscodeItem(x, x.FileName + ".mp3")).ToList().ForEach(transcodingManager.AddTranscodeItem); 32 | transcodingManager.TranscodeItems[0].Progress = 1; 33 | transcodingManager.TranscodeItems[1].Progress = 0.27; 34 | transcodingManager.TranscodeItems[2].Error = new InvalidOperationException("Test"); 35 | TranscodingManager = transcodingManager; 36 | 37 | SelectedTranscodeItems.Add(transcodingManager.TranscodeItems[^1]); 38 | } 39 | 40 | 41 | private class MockTranscodingListView : MockView, ITranscodingListView 42 | { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Controls/InsertMarkerAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Documents; 3 | using System.Windows.Media; 4 | 5 | namespace Waf.MusicManager.Presentation.Controls; 6 | 7 | public class InsertMarkerAdorner : Adorner 8 | { 9 | private readonly FrameworkElement control; 10 | private FrameworkElement? item; 11 | private bool showMarkerAfterItem; 12 | private Rect adornerViewRect; 13 | 14 | public InsertMarkerAdorner(FrameworkElement control) : base(control) 15 | { 16 | this.control = control; 17 | } 18 | 19 | public void ShowMarker(FrameworkElement item, bool showMarkerAfterItem) 20 | { 21 | this.item = item; 22 | this.showMarkerAfterItem = showMarkerAfterItem; 23 | InvalidateVisual(); 24 | } 25 | 26 | public void ResetMarker() 27 | { 28 | item = null; 29 | InvalidateVisual(); 30 | } 31 | 32 | protected override Size ArrangeOverride(Size finalSize) 33 | { 34 | adornerViewRect = new Rect(finalSize); 35 | return base.ArrangeOverride(finalSize); 36 | } 37 | 38 | protected override void OnRender(DrawingContext drawingContext) 39 | { 40 | base.OnRender(drawingContext); 41 | if (item != null) 42 | { 43 | var itemRect = new Rect(item.RenderSize); 44 | Point startPoint; 45 | Point endPoint; 46 | if (!showMarkerAfterItem) 47 | { 48 | startPoint = itemRect.TopLeft; 49 | endPoint = itemRect.TopRight; 50 | } 51 | else 52 | { 53 | startPoint = itemRect.BottomLeft; 54 | endPoint = itemRect.BottomRight; 55 | } 56 | 57 | startPoint = item.TranslatePoint(startPoint, control); 58 | endPoint = item.TranslatePoint(endPoint, control); 59 | 60 | drawingContext.PushClip(new RectangleGeometry(adornerViewRect)); 61 | drawingContext.DrawLine(new Pen(Brushes.Green, 2), startPoint, endPoint); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/Transcoding/TranscodeItemTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Test.MusicManager.Domain.MusicFiles; 4 | using Waf.MusicManager.Domain.MusicFiles; 5 | using Waf.MusicManager.Domain.Transcoding; 6 | 7 | namespace Test.MusicManager.Domain.Transcoding; 8 | 9 | [TestClass] 10 | public class TranscodeItemTest : DomainTest 11 | { 12 | [TestMethod] 13 | public void PropertiesTest() 14 | { 15 | var musicFile = new MockMusicFile(new(TimeSpan.FromSeconds(33), 320), "TestFile.wma"); 16 | var item = new TranscodeItem(musicFile, "TestFile.mp3"); 17 | 18 | Assert.AreEqual(musicFile, item.Source); 19 | Assert.AreEqual("TestFile.mp3", item.DestinationFileName); 20 | 21 | Assert.AreEqual(0, item.Progress); 22 | AssertHelper.PropertyChangedEvent(item, x => x.Progress, () => item.Progress = 0.5); 23 | Assert.AreEqual(0.5, item.Progress); 24 | 25 | Assert.IsNull(item.Error); 26 | var exception = new InvalidOperationException("Test"); 27 | AssertHelper.PropertyChangedEvent(item, x => x.Error, () => item.Error = exception); 28 | Assert.AreEqual(exception, item.Error); 29 | } 30 | 31 | [TestMethod] 32 | public void TranscodeStatusTest() 33 | { 34 | var musicFile = new MockMusicFile(new(TimeSpan.FromSeconds(33), 320), "TestFile.wma"); 35 | var item = new TranscodeItem(musicFile, "TestFile.mp3"); 36 | 37 | Assert.AreEqual(TranscodeStatus.Pending, item.TranscodeStatus); 38 | 39 | AssertHelper.PropertyChangedEvent(item, x => x.TranscodeStatus, () => item.Progress = 0.01); 40 | Assert.AreEqual(TranscodeStatus.InProgress, item.TranscodeStatus); 41 | 42 | AssertHelper.PropertyChangedEvent(item, x => x.TranscodeStatus, () => item.Progress = 1); 43 | Assert.AreEqual(TranscodeStatus.Completed, item.TranscodeStatus); 44 | 45 | AssertHelper.PropertyChangedEvent(item, x => x.TranscodeStatus, () => item.Error = new InvalidOperationException()); 46 | Assert.AreEqual(TranscodeStatus.Error, item.TranscodeStatus); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Controls/SearchBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace Waf.MusicManager.Presentation.Controls; 5 | 6 | /// Represents a control that can be used to enter a search or filter text. 7 | [TemplatePart(Name = searchTextBoxPartName, Type = typeof(TextBox))] 8 | public class SearchBox : Control 9 | { 10 | private const string searchTextBoxPartName = "PART_SearchTextBox"; 11 | 12 | /// Identifies the Text dependency property. 13 | public static readonly DependencyProperty TextProperty = 14 | DependencyProperty.Register(nameof(Text), typeof(string), typeof(SearchBox), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); 15 | 16 | /// Identifies the HintText dependency property. 17 | public static readonly DependencyProperty HintTextProperty = 18 | DependencyProperty.Register(nameof(HintText), typeof(string), typeof(SearchBox), new FrameworkPropertyMetadata("")); 19 | 20 | private TextBox searchTextBox = null!; 21 | 22 | static SearchBox() 23 | { 24 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchBox), new FrameworkPropertyMetadata(typeof(SearchBox))); 25 | } 26 | 27 | /// Gets or sets the text. 28 | public string Text 29 | { 30 | get => (string)GetValue(TextProperty); 31 | set => SetValue(TextProperty, value); 32 | } 33 | 34 | /// Gets or sets the hint text. This text is shown in the background if no search text is entered. 35 | public string HintText 36 | { 37 | get => (string)GetValue(HintTextProperty); 38 | set => SetValue(HintTextProperty, value); 39 | } 40 | 41 | public override void OnApplyTemplate() 42 | { 43 | base.OnApplyTemplate(); 44 | searchTextBox = (TextBox)GetTemplateChild(searchTextBoxPartName); 45 | if (searchTextBox == null) throw new InvalidOperationException("The part could not be found: " + searchTextBoxPartName); 46 | } 47 | 48 | public new void Focus() => searchTextBox.Focus(); 49 | } 50 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Properties/PublishProfiles/ClickOnceProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 100 8 | 3.1.0.100 9 | True 10 | Release 11 | True 12 | False 13 | True 14 | True 15 | Web 16 | https://jbe2277.github.io/musicmanager/clickonce/ 17 | False 18 | True 19 | True 20 | False 21 | Any CPU 22 | Waf Music Manager 23 | bin\publish\ 24 | bin\publish\ 25 | Waf 26 | ClickOnce 27 | False 28 | False 29 | False 30 | (none) 31 | False 32 | Waf Music Manager 33 | https://jbe2277.github.io/musicmanager 34 | net6.0-windows10.0.19041 35 | True 36 | Foreground 37 | False 38 | Publish.html 39 | 40 | 41 | 42 | True 43 | .NET Desktop Runtime 6.0.7 (x64) 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications.Test/MockPresentationModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using System.Waf.Applications.Services; 3 | using System.Waf.UnitTesting.Mocks; 4 | using Test.MusicManager.Applications.Services; 5 | using Test.MusicManager.Applications.Views; 6 | using Waf.MusicManager.Applications.Services; 7 | using Waf.MusicManager.Applications.Views; 8 | 9 | namespace Test.MusicManager.Applications; 10 | 11 | public class MockPresentationModule(bool useMock = true) : Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.RegisterType().As().AsSelf().SingleInstance(); 16 | builder.RegisterType().As().AsSelf().SingleInstance(); 17 | builder.RegisterType().As().AsSelf().SingleInstance(); 18 | 19 | builder.RegisterType().As().AsSelf().SingleInstance(); 20 | builder.RegisterType().As().AsSelf().SingleInstance(); 21 | 22 | if (useMock) 23 | { 24 | builder.RegisterType().As().AsSelf().SingleInstance(); 25 | builder.RegisterType().As().AsSelf().SingleInstance(); 26 | builder.RegisterType().As().AsSelf().SingleInstance(); 27 | builder.RegisterType().As().AsSelf().SingleInstance(); 28 | } 29 | 30 | builder.RegisterType().As(); 31 | builder.RegisterType().As().AsSelf().SingleInstance(); 32 | builder.RegisterType().As().AsSelf().SingleInstance(); 33 | builder.RegisterType().As().AsSelf().SingleInstance(); 34 | builder.RegisterType().As().AsSelf().SingleInstance(); 35 | builder.RegisterType().As().AsSelf().SingleInstance(); 36 | builder.RegisterType().As().AsSelf().SingleInstance(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Services/StringListConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Globalization; 3 | using System.Waf.UnitTesting; 4 | using Waf.MusicManager.Presentation.Services; 5 | 6 | namespace Test.MusicManager.Presentation.Services; 7 | 8 | [TestClass] 9 | public class StringListConverterTest 10 | { 11 | [TestMethod] 12 | public void ToStringTest() 13 | { 14 | var nl = Environment.NewLine; 15 | var ls = CultureInfo.CurrentCulture.TextInfo.ListSeparator; 16 | 17 | Assert.AreEqual("", StringListConverter.ToString(null)); 18 | Assert.AreEqual("", StringListConverter.ToString([])); 19 | Assert.AreEqual("", StringListConverter.ToString([ "" ])); 20 | 21 | Assert.AreEqual("Pop", StringListConverter.ToString([ "Pop" ])); 22 | Assert.AreEqual("Pop" + ls + " Rock", StringListConverter.ToString([ "Pop", "Rock" ])); 23 | Assert.AreEqual("Pop" + nl + "Rock", StringListConverter.ToString([ "Pop", "Rock" ], nl)); 24 | } 25 | 26 | [TestMethod] 27 | public void ConvertBackTest() 28 | { 29 | var nl = Environment.NewLine; 30 | var ls = CultureInfo.CurrentCulture.TextInfo.ListSeparator; 31 | 32 | AssertHelper.SequenceEqual([], StringListConverter.FromString("")); 33 | 34 | AssertHelper.SequenceEqual([ "Pop" ], StringListConverter.FromString("Pop")); 35 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString("Pop" + nl + "Rock", nl)); 36 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString(" Pop" + nl + " Rock", nl)); 37 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString("Pop " + nl + "Rock ", nl)); 38 | 39 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString("Pop" + ls + "Rock")); 40 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString(" Pop" + ls + " Rock")); 41 | AssertHelper.SequenceEqual([ "Pop", "Rock" ], StringListConverter.FromString("Pop " + ls + "Rock ")); 42 | 43 | AssertHelper.SequenceEqual([ "Pop" ], StringListConverter.FromString("Pop" + ls)); 44 | AssertHelper.SequenceEqual([ "Pop" ], StringListConverter.FromString("Pop" + ls + " ")); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Domain.Test/MusicFiles/EntityTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.UnitTesting; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | 5 | namespace Test.MusicManager.Domain.MusicFiles; 6 | 7 | [TestClass] 8 | public class EntityTest : DomainTest 9 | { 10 | private MockChangeTrackerService changeTrackerService = null!; 11 | 12 | protected override void OnInitialize() 13 | { 14 | base.OnInitialize(); 15 | changeTrackerService = new MockChangeTrackerService(); 16 | ServiceLocator.RegisterInstance(changeTrackerService); 17 | } 18 | 19 | [TestMethod] 20 | public void ChangeTrackerTest() 21 | { 22 | var entity = new MockEntity(); 23 | entity.EntityLoadCompleted(); 24 | Assert.IsFalse(entity.HasChanges); 25 | Assert.IsFalse(entity.Changes.Any()); 26 | 27 | AssertHelper.PropertyChangedEvent(entity, x => x.HasChanges, () => entity.Name = "Bill"); 28 | Assert.IsTrue(entity.HasChanges); 29 | Assert.AreEqual("Name", entity.Changes.Single()); 30 | 31 | AssertHelper.PropertyChangedEvent(entity, x => x.HasChanges, () => entity.ClearChanges()); 32 | Assert.IsFalse(entity.HasChanges); 33 | Assert.IsFalse(entity.Changes.Any()); 34 | 35 | entity.Name = "Bill"; 36 | Assert.IsFalse(entity.HasChanges); 37 | } 38 | 39 | [TestMethod] 40 | public void EntityHasChangesTest() 41 | { 42 | int entityHasChangesCallCount = 0; 43 | changeTrackerService.EntityHasChangesAction = e => entityHasChangesCallCount++; 44 | 45 | // No change tracking during loading state 46 | var entity = new MockEntity { Name = "Bill" }; 47 | Assert.AreEqual(0, entityHasChangesCallCount); 48 | Assert.IsFalse(entity.HasChanges); 49 | 50 | // Start change tracking when load completed 51 | entity.EntityLoadCompleted(); 52 | entity.Name = "Steve"; 53 | Assert.AreEqual(1, entityHasChangesCallCount); 54 | Assert.IsTrue(entity.HasChanges); 55 | } 56 | 57 | 58 | private class MockEntity : Entity 59 | { 60 | private string? name; 61 | 62 | public string? Name 63 | { 64 | get => name; 65 | set => SetPropertyAndTrackChanges(ref name, value); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/Services/PlayerService.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | using System.Windows.Input; 3 | using Waf.MusicManager.Domain.MusicFiles; 4 | 5 | namespace Waf.MusicManager.Applications.Services; 6 | 7 | public class PlayerService : Model, IPlayerService 8 | { 9 | private ICommand playAllCommand = DelegateCommand.DisabledCommand; 10 | private ICommand playSelectedCommand = DelegateCommand.DisabledCommand; 11 | private ICommand enqueueAllCommand = DelegateCommand.DisabledCommand; 12 | private ICommand enqueueSelectedCommand = DelegateCommand.DisabledCommand; 13 | private ICommand previousCommand = DelegateCommand.DisabledCommand; 14 | private ICommand playPauseCommand = DelegateCommand.DisabledCommand; 15 | private ICommand nextCommand = DelegateCommand.DisabledCommand; 16 | private bool isPlayCommand = true; 17 | private MusicFile? playingMusicFile; 18 | 19 | public ICommand PlayAllCommand 20 | { 21 | get => playAllCommand; 22 | set => SetProperty(ref playAllCommand, value); 23 | } 24 | 25 | public ICommand PlaySelectedCommand 26 | { 27 | get => playSelectedCommand; 28 | set => SetProperty(ref playSelectedCommand, value); 29 | } 30 | 31 | public ICommand EnqueueAllCommand 32 | { 33 | get => enqueueAllCommand; 34 | set => SetProperty(ref enqueueAllCommand, value); 35 | } 36 | 37 | public ICommand EnqueueSelectedCommand 38 | { 39 | get => enqueueSelectedCommand; 40 | set => SetProperty(ref enqueueSelectedCommand, value); 41 | } 42 | 43 | public ICommand PreviousCommand 44 | { 45 | get => previousCommand; 46 | set => SetProperty(ref previousCommand, value); 47 | } 48 | 49 | public ICommand PlayPauseCommand 50 | { 51 | get => playPauseCommand; 52 | set => SetProperty(ref playPauseCommand, value); 53 | } 54 | 55 | public ICommand NextCommand 56 | { 57 | get => nextCommand; 58 | set => SetProperty(ref nextCommand, value); 59 | } 60 | 61 | public bool IsPlayCommand 62 | { 63 | get => isPlayCommand; 64 | set => SetProperty(ref isPlayCommand, value); 65 | } 66 | 67 | public MusicFile? PlayingMusicFile 68 | { 69 | get => playingMusicFile; 70 | set => SetProperty(ref playingMusicFile, value); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation/Services/FileSystemWatcherService.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Waf.MusicManager.Applications.Services; 3 | 4 | namespace Waf.MusicManager.Presentation.Services; 5 | 6 | internal class FileSystemWatcherService : Disposable, IFileSystemWatcherService 7 | { 8 | private readonly TaskScheduler taskScheduler; 9 | private readonly FileSystemWatcher watcher; 10 | 11 | public FileSystemWatcherService() 12 | { 13 | taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 14 | watcher = new FileSystemWatcher(); 15 | watcher.Created += WatcherCreated; 16 | watcher.Renamed += WatcherRenamed; 17 | watcher.Deleted += WatcherDeleted; 18 | } 19 | 20 | public NotifyFilters NotifyFilter 21 | { 22 | get => watcher.NotifyFilter; 23 | set => watcher.NotifyFilter = value; 24 | } 25 | 26 | public string Path 27 | { 28 | get => watcher.Path; 29 | set => watcher.Path = value; 30 | } 31 | 32 | public bool EnableRaisingEvents 33 | { 34 | get => watcher.EnableRaisingEvents; 35 | set => watcher.EnableRaisingEvents = value; 36 | } 37 | 38 | public event FileSystemEventHandler? Created; 39 | 40 | public event RenamedEventHandler? Renamed; 41 | 42 | public event FileSystemEventHandler? Deleted; 43 | 44 | protected virtual void OnCreated(FileSystemEventArgs e) => Created?.Invoke(this, e); 45 | 46 | protected virtual void OnRenamed(RenamedEventArgs e) => Renamed?.Invoke(this, e); 47 | 48 | protected virtual void OnDeleted(FileSystemEventArgs e) => Deleted?.Invoke(this, e); 49 | 50 | private void WatcherCreated(object? sender, FileSystemEventArgs e) 51 | { 52 | Log.Default.Trace(nameof(WatcherCreated)); 53 | TaskHelper.Run(() => OnCreated(e), taskScheduler); 54 | } 55 | 56 | private void WatcherRenamed(object? sender, RenamedEventArgs e) 57 | { 58 | Log.Default.Trace(nameof(WatcherRenamed)); 59 | TaskHelper.Run(() => OnRenamed(e), taskScheduler); 60 | } 61 | 62 | private void WatcherDeleted(object? sender, FileSystemEventArgs e) 63 | { 64 | Log.Default.Trace(nameof(WatcherDeleted)); 65 | TaskHelper.Run(() => OnDeleted(e), taskScheduler); 66 | } 67 | 68 | protected override void Dispose(bool isDisposing) 69 | { 70 | if (isDisposing) watcher.Dispose(); 71 | base.Dispose(isDisposing); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Presentation.Test/Controllers/PlaylistControllerIntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Waf.Applications; 3 | using System.Waf.Applications.Services; 4 | using System.Waf.UnitTesting.Mocks; 5 | using Waf.MusicManager.Applications.Controllers; 6 | using Waf.MusicManager.Applications.Services; 7 | using Waf.MusicManager.Applications.ViewModels; 8 | 9 | namespace Test.MusicManager.Presentation.Controllers; 10 | 11 | [TestClass] 12 | public class PlaylistControllerIntegrationTest : PresentationTest 13 | { 14 | private ModuleController moduleController = null!; 15 | 16 | protected override void OnInitialize() 17 | { 18 | base.OnInitialize(); 19 | 20 | moduleController = Get(); 21 | moduleController.Initialize(); 22 | moduleController.Run(); 23 | } 24 | 25 | protected override void OnCleanup() 26 | { 27 | moduleController.Shutdown(); 28 | base.OnCleanup(); 29 | } 30 | 31 | [TestMethod] 32 | public void SaveAndLoadPlaylist() 33 | { 34 | string musicFileName1 = Environment.CurrentDirectory + @"\Files\TestMP3.mp3"; 35 | string musicFileName2 = Environment.CurrentDirectory + @"\Files\TestWMA.wma"; 36 | string playlistFileName = Environment.CurrentDirectory + @"\TestPlaylist.m3u"; 37 | 38 | var shellService = Get(); 39 | var view = shellService.PlaylistView!; 40 | var viewModel = ViewHelper.GetViewModel((IView)view)!; 41 | viewModel.InsertFilesAction(0, 42 | [ 43 | musicFileName1, 44 | musicFileName2 45 | ]); 46 | Assert.AreEqual(2, viewModel.PlaylistManager.Items.Count); 47 | 48 | var fileDialogService = Get(); 49 | fileDialogService.Result = new(playlistFileName, new FileType("test", ".m3u")); 50 | viewModel.SaveListCommand.Execute(null); 51 | 52 | Assert.IsTrue(File.Exists(playlistFileName)); 53 | 54 | viewModel.ClearListCommand.Execute(null); 55 | Assert.AreEqual(0, viewModel.PlaylistManager.Items.Count); 56 | 57 | viewModel.OpenListCommand.Execute(null); 58 | Assert.AreEqual(2, viewModel.PlaylistManager.Items.Count); 59 | Assert.AreEqual(musicFileName1, viewModel.PlaylistManager.Items[0].MusicFile.FileName, ignoreCase: true); 60 | Assert.AreEqual(musicFileName2, viewModel.PlaylistManager.Items[1].MusicFile.FileName, ignoreCase: true); 61 | 62 | File.Delete(playlistFileName); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/MusicManager/MusicManager.Applications/ViewModels/PlayerViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Waf.Applications; 2 | using System.Windows.Input; 3 | using Waf.MusicManager.Applications.Services; 4 | using Waf.MusicManager.Applications.Views; 5 | using Waf.MusicManager.Domain.Playlists; 6 | 7 | namespace Waf.MusicManager.Applications.ViewModels; 8 | 9 | public class PlayerViewModel : ViewModel 10 | { 11 | private PlaylistManager playlistManager = null!; 12 | private ICommand previousTrackCommand = DelegateCommand.DisabledCommand; 13 | private ICommand nextTrackCommand = DelegateCommand.DisabledCommand; 14 | private ICommand infoCommand = DelegateCommand.DisabledCommand; 15 | private ICommand showMusicPropertiesCommand = DelegateCommand.DisabledCommand; 16 | private ICommand showPlaylistCommand = DelegateCommand.DisabledCommand; 17 | private double volume; 18 | 19 | public PlayerViewModel(IPlayerView view, IShellService shellService, IPlayerService playerService) : base(view) 20 | { 21 | ShellService = shellService; 22 | PlayerService = playerService; 23 | } 24 | 25 | public IShellService ShellService { get; } 26 | 27 | public IPlayerService PlayerService { get; } 28 | 29 | public PlaylistManager PlaylistManager 30 | { 31 | get => playlistManager; 32 | set => SetProperty(ref playlistManager, value); 33 | } 34 | 35 | public ICommand PreviousTrackCommand 36 | { 37 | get => previousTrackCommand; 38 | set => SetProperty(ref previousTrackCommand, value); 39 | } 40 | 41 | public ICommand NextTrackCommand 42 | { 43 | get => nextTrackCommand; 44 | set => SetProperty(ref nextTrackCommand, value); 45 | } 46 | 47 | public ICommand InfoCommand 48 | { 49 | get => infoCommand; 50 | set => SetProperty(ref infoCommand, value); 51 | } 52 | 53 | public ICommand ShowMusicPropertiesCommand 54 | { 55 | get => showMusicPropertiesCommand; 56 | set => SetProperty(ref showMusicPropertiesCommand, value); 57 | } 58 | 59 | public ICommand ShowPlaylistCommand 60 | { 61 | get => showPlaylistCommand; 62 | set => SetProperty(ref showPlaylistCommand, value); 63 | } 64 | 65 | public double Volume 66 | { 67 | get => volume; 68 | set => SetProperty(ref volume, value); 69 | } 70 | 71 | public TimeSpan GetPosition() => ViewCore.GetPosition(); 72 | 73 | public void SetPosition(TimeSpan position) => ViewCore.SetPosition(position); 74 | } 75 | --------------------------------------------------------------------------------