diff --git a/windows/StreamtaggerSync/.gitignore b/windows/StreamtaggerSync/.gitignore new file mode 100644 index 0000000..a57fdbb --- /dev/null +++ b/windows/StreamtaggerSync/.gitignore @@ -0,0 +1,353 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync.sln b/windows/StreamtaggerSync/StreamtaggerSync.sln new file mode 100644 index 0000000..2633683 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1000 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamtaggerSync", "StreamtaggerSync\StreamtaggerSync.csproj", "{29E66EA1-567E-4260-8297-8FAE62F84E6C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29E66EA1-567E-4260-8297-8FAE62F84E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29E66EA1-567E-4260-8297-8FAE62F84E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29E66EA1-567E-4260-8297-8FAE62F84E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29E66EA1-567E-4260-8297-8FAE62F84E6C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D3F1B622-E895-4BBD-BD44-F203E5C93195} + EndGlobalSection +EndGlobal diff --git a/windows/StreamtaggerSync/StreamtaggerSync/App.config b/windows/StreamtaggerSync/StreamtaggerSync/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Assets/folder-xxl.png b/windows/StreamtaggerSync/StreamtaggerSync/Assets/folder-xxl.png new file mode 100644 index 0000000..a022324 Binary files /dev/null and b/windows/StreamtaggerSync/StreamtaggerSync/Assets/folder-xxl.png differ diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.Designer.cs new file mode 100644 index 0000000..15f5d22 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.Designer.cs @@ -0,0 +1,112 @@ +namespace StreamtaggerSync +{ + partial class PlaylistControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.txtName = new System.Windows.Forms.TextBox(); + this.txtQuery = new System.Windows.Forms.TextBox(); + this.cmdDelete = new System.Windows.Forms.Button(); + this.llQuery = new System.Windows.Forms.LinkLabel(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 6); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(38, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Name:"; + // + // txtName + // + this.txtName.Location = new System.Drawing.Point(47, 3); + this.txtName.Name = "txtName"; + this.txtName.Size = new System.Drawing.Size(111, 20); + this.txtName.TabIndex = 1; + this.txtName.Leave += new System.EventHandler(this.txtName_Leave); + // + // txtQuery + // + this.txtQuery.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtQuery.Location = new System.Drawing.Point(208, 3); + this.txtQuery.Name = "txtQuery"; + this.txtQuery.Size = new System.Drawing.Size(209, 20); + this.txtQuery.TabIndex = 3; + this.txtQuery.Leave += new System.EventHandler(this.txtQuery_Leave); + // + // cmdDelete + // + this.cmdDelete.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdDelete.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(225)))), ((int)(((byte)(192)))), ((int)(((byte)(192))))); + this.cmdDelete.Location = new System.Drawing.Point(423, 1); + this.cmdDelete.Name = "cmdDelete"; + this.cmdDelete.Size = new System.Drawing.Size(23, 23); + this.cmdDelete.TabIndex = 4; + this.cmdDelete.Text = "-"; + this.cmdDelete.UseVisualStyleBackColor = false; + this.cmdDelete.Click += new System.EventHandler(this.cmdDelete_Click); + // + // llQuery + // + this.llQuery.AutoSize = true; + this.llQuery.Location = new System.Drawing.Point(164, 6); + this.llQuery.Name = "llQuery"; + this.llQuery.Size = new System.Drawing.Size(38, 13); + this.llQuery.TabIndex = 5; + this.llQuery.TabStop = true; + this.llQuery.Text = "Query:"; + this.llQuery.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.llQuery_LinkClicked); + // + // PlaylistControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.llQuery); + this.Controls.Add(this.cmdDelete); + this.Controls.Add(this.txtQuery); + this.Controls.Add(this.txtName); + this.Controls.Add(this.label1); + this.Name = "PlaylistControl"; + this.Size = new System.Drawing.Size(450, 26); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtName; + private System.Windows.Forms.TextBox txtQuery; + private System.Windows.Forms.Button cmdDelete; + private System.Windows.Forms.LinkLabel llQuery; + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.cs b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.cs new file mode 100644 index 0000000..ef7e834 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + public partial class PlaylistControl : UserControl + { + public class InformationRequestedEventArgs : EventArgs + { + public Uri StreamtaggerUri; + } + + private Playlist _Playlist; + + public event EventHandler DeleteRequested; + public event EventHandler PlaylistChanged; + public event EventHandler InformationRequested; + + public Playlist Playlist + { + get + { + _Playlist.Name = txtName.Text; + _Playlist.Query = txtQuery.Text; + return _Playlist; + } + set + { + _Playlist = value; + txtName.Text = value.Name; + txtQuery.Text = value.Query; + } + } + + public PlaylistControl() + { + InitializeComponent(); + _Playlist = new Playlist(txtName.Text, txtQuery.Text); + } + + private void cmdDelete_Click(object sender, EventArgs e) + { + DeleteRequested?.Invoke(this, EventArgs.Empty); + } + + private void txtName_Leave(object sender, EventArgs e) + { + if (txtName.Text != _Playlist.Name) + { + _Playlist.Name = txtName.Text; + PlaylistChanged?.Invoke(this, EventArgs.Empty); + } + } + + private void txtQuery_Leave(object sender, EventArgs e) + { + if (txtQuery.Text != _Playlist.Query) + { + _Playlist.Query = txtQuery.Text; + PlaylistChanged?.Invoke(this, EventArgs.Empty); + } + } + + private void llQuery_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + var infoArgs = new InformationRequestedEventArgs(); + InformationRequested?.Invoke(this, infoArgs); + if (infoArgs.StreamtaggerUri != null) + { + Uri uri = Synchronizer.AddQuery(infoArgs.StreamtaggerUri, txtQuery.Text); + System.Diagnostics.Process.Start(uri.AbsoluteUri); + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.resx b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.Designer.cs new file mode 100644 index 0000000..4a97dad --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.Designer.cs @@ -0,0 +1,62 @@ +namespace StreamtaggerSync +{ + partial class PlaylistListBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.cmdAdd = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // cmdAdd + // + this.cmdAdd.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(225)))), ((int)(((byte)(192))))); + this.cmdAdd.Location = new System.Drawing.Point(3, 3); + this.cmdAdd.Name = "cmdAdd"; + this.cmdAdd.Size = new System.Drawing.Size(23, 23); + this.cmdAdd.TabIndex = 0; + this.cmdAdd.Text = "+"; + this.cmdAdd.UseVisualStyleBackColor = false; + this.cmdAdd.Click += new System.EventHandler(this.cmdAdd_Click); + // + // PlaylistListBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScroll = true; + this.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.Controls.Add(this.cmdAdd); + this.Name = "PlaylistListBox"; + this.Size = new System.Drawing.Size(227, 108); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button cmdAdd; + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.cs b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.cs new file mode 100644 index 0000000..c65ec87 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + public partial class PlaylistListBox : UserControl + { + private List PlaylistControls = new List(); + + public event EventHandler PlaylistsChanged; + public event EventHandler InformationRequested; + + public PlaylistListBox() + { + InitializeComponent(); + } + + public PlaylistSet Playlists + { + get + { + return new PlaylistSet(PlaylistControls.Select(ctrl => ctrl.Playlist).ToList()); + } + set + { + this.SuspendLayout(); + foreach (PlaylistControl playlistControl in PlaylistControls) + { + RemoveControl(playlistControl); + } + PlaylistControls.Clear(); + foreach (Playlist playlist in value.Playlists) + { + AddControl(playlist); + } + this.ResumeLayout(); + } + } + + private PlaylistControl AddControl(Playlist playlist = null) + { + var playlistControl = new PlaylistControl(); + playlistControl.Left = this.ClientRectangle.Left; + if (PlaylistControls.Count == 0) + { + playlistControl.Top = this.ClientRectangle.Top; + } + else + { + playlistControl.Top = PlaylistControls.Last().Bottom + 3; + } + playlistControl.Width = this.ClientRectangle.Width; + playlistControl.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + if (playlist != null) + { + playlistControl.Playlist = playlist; + } + playlistControl.DeleteRequested += playlist_DeleteRequested; + playlistControl.PlaylistChanged += playlist_PlaylistChanged; + playlistControl.InformationRequested += playlist_InformationRequested; + PlaylistControls.Add(playlistControl); + + cmdAdd.Top = playlistControl.Bottom + 3; + + this.Controls.Add(playlistControl); + + return playlistControl; + } + + private void RemoveControl(PlaylistControl playlistControl) + { + this.Controls.Remove(playlistControl); + playlistControl.DeleteRequested -= playlist_DeleteRequested; + playlistControl.PlaylistChanged -= playlist_PlaylistChanged; + playlistControl.InformationRequested -= playlist_InformationRequested; + } + + private void cmdAdd_Click(object sender, EventArgs e) + { + AddControl(); + } + + private void playlist_DeleteRequested(object sender, EventArgs e) + { + this.SuspendLayout(); + PlaylistControl playlistControl = sender as PlaylistControl; + RemoveControl(playlistControl); + int i = PlaylistControls.IndexOf(playlistControl); + PlaylistControls.RemoveAt(i); + for (int j = i; j < PlaylistControls.Count; j++) + { + if (j == 0) + { + PlaylistControls[j].Top = this.ClientRectangle.Top; + } + else + { + PlaylistControls[j].Top = PlaylistControls[j - 1].Bottom + 3; + } + } + if (PlaylistControls.Count == 0) + { + cmdAdd.Top = this.ClientRectangle.Top; + } + else + { + cmdAdd.Top = PlaylistControls.Last().Bottom + 3; + } + PlaylistsChanged?.Invoke(this, EventArgs.Empty); + this.ResumeLayout(); + } + + private void playlist_PlaylistChanged(object sender, EventArgs e) + { + PlaylistsChanged?.Invoke(this, EventArgs.Empty); + } + + private void playlist_InformationRequested(object sender, PlaylistControl.InformationRequestedEventArgs e) + { + InformationRequested?.Invoke(this, e); + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.resx b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Controls/PlaylistListBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.Designer.cs new file mode 100644 index 0000000..1fb4902 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.Designer.cs @@ -0,0 +1,101 @@ +namespace StreamtaggerSync +{ + partial class PasswordDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.cmdOk = new System.Windows.Forms.Button(); + this.cmdCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 21); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(172, 13); + this.label1.TabIndex = 3; + this.label1.Text = "Enter your Streamtagger password:"; + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(12, 55); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.Size = new System.Drawing.Size(250, 20); + this.txtPassword.TabIndex = 0; + this.txtPassword.UseSystemPasswordChar = true; + this.txtPassword.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtPassword_KeyPress); + // + // cmdOk + // + this.cmdOk.Location = new System.Drawing.Point(187, 93); + this.cmdOk.Name = "cmdOk"; + this.cmdOk.Size = new System.Drawing.Size(75, 23); + this.cmdOk.TabIndex = 1; + this.cmdOk.Text = "Ok"; + this.cmdOk.UseVisualStyleBackColor = true; + this.cmdOk.Click += new System.EventHandler(this.cmdOk_Click); + // + // cmdCancel + // + this.cmdCancel.Location = new System.Drawing.Point(12, 93); + this.cmdCancel.Name = "cmdCancel"; + this.cmdCancel.Size = new System.Drawing.Size(75, 23); + this.cmdCancel.TabIndex = 2; + this.cmdCancel.Text = "Cancel"; + this.cmdCancel.UseVisualStyleBackColor = true; + this.cmdCancel.Click += new System.EventHandler(this.cmdCancel_Click); + // + // PasswordDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(274, 134); + this.Controls.Add(this.cmdCancel); + this.Controls.Add(this.cmdOk); + this.Controls.Add(this.txtPassword); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "PasswordDialog"; + this.Text = "Password"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.Button cmdOk; + private System.Windows.Forms.Button cmdCancel; + } +} \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.cs b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.cs new file mode 100644 index 0000000..9a2ca19 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + public partial class PasswordDialog : Form + { + public string Password + { + get + { + return txtPassword.Text; + } + set + { + txtPassword.Text = value; + } + } + + public PasswordDialog() + { + InitializeComponent(); + + DialogResult = DialogResult.Cancel; + } + + private void cmdCancel_Click(object sender, EventArgs e) + { + Close(); + } + + private void cmdOk_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } + + private void txtPassword_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == Convert.ToChar(Keys.Return)) + { + cmdOk.PerformClick(); + e.Handled = true; + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.resx b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PasswordDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.Designer.cs new file mode 100644 index 0000000..a9ee226 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.Designer.cs @@ -0,0 +1,244 @@ +namespace StreamtaggerSync +{ + partial class PreferencesDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreferencesDialog)); + this.gbStorage = new System.Windows.Forms.GroupBox(); + this.cmdPlaylistsPath = new System.Windows.Forms.Button(); + this.lblPlaylistsPath = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.cmdMusicPath = new System.Windows.Forms.Button(); + this.lblMusicPath = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.gbCloud = new System.Windows.Forms.GroupBox(); + this.txtUsername = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.txtStreamtaggerUrl = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.cmdOk = new System.Windows.Forms.Button(); + this.fbdMusicPath = new System.Windows.Forms.FolderBrowserDialog(); + this.fbdPlaylistsPath = new System.Windows.Forms.FolderBrowserDialog(); + this.lblInstructions = new System.Windows.Forms.Label(); + this.gbStorage.SuspendLayout(); + this.gbCloud.SuspendLayout(); + this.SuspendLayout(); + // + // gbStorage + // + this.gbStorage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbStorage.Controls.Add(this.cmdPlaylistsPath); + this.gbStorage.Controls.Add(this.lblPlaylistsPath); + this.gbStorage.Controls.Add(this.label4); + this.gbStorage.Controls.Add(this.cmdMusicPath); + this.gbStorage.Controls.Add(this.lblMusicPath); + this.gbStorage.Controls.Add(this.label2); + this.gbStorage.Location = new System.Drawing.Point(12, 96); + this.gbStorage.Name = "gbStorage"; + this.gbStorage.Size = new System.Drawing.Size(580, 85); + this.gbStorage.TabIndex = 0; + this.gbStorage.TabStop = false; + this.gbStorage.Text = "Local storage"; + // + // cmdPlaylistsPath + // + this.cmdPlaylistsPath.BackgroundImage = global::StreamtaggerSync.Properties.Resources.folder_xxl; + this.cmdPlaylistsPath.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; + this.cmdPlaylistsPath.Location = new System.Drawing.Point(110, 48); + this.cmdPlaylistsPath.Name = "cmdPlaylistsPath"; + this.cmdPlaylistsPath.Size = new System.Drawing.Size(23, 23); + this.cmdPlaylistsPath.TabIndex = 5; + this.cmdPlaylistsPath.UseVisualStyleBackColor = true; + this.cmdPlaylistsPath.Click += new System.EventHandler(this.cmdPlaylistsPath_Click); + // + // lblPlaylistsPath + // + this.lblPlaylistsPath.AutoSize = true; + this.lblPlaylistsPath.Location = new System.Drawing.Point(139, 53); + this.lblPlaylistsPath.Name = "lblPlaylistsPath"; + this.lblPlaylistsPath.Size = new System.Drawing.Size(25, 13); + this.lblPlaylistsPath.TabIndex = 4; + this.lblPlaylistsPath.Text = "???"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(6, 53); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(71, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Playlists path:"; + // + // cmdMusicPath + // + this.cmdMusicPath.BackgroundImage = global::StreamtaggerSync.Properties.Resources.folder_xxl; + this.cmdMusicPath.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; + this.cmdMusicPath.Location = new System.Drawing.Point(110, 19); + this.cmdMusicPath.Name = "cmdMusicPath"; + this.cmdMusicPath.Size = new System.Drawing.Size(23, 23); + this.cmdMusicPath.TabIndex = 2; + this.cmdMusicPath.UseVisualStyleBackColor = true; + this.cmdMusicPath.Click += new System.EventHandler(this.cmdMusicPath_Click); + // + // lblMusicPath + // + this.lblMusicPath.AutoSize = true; + this.lblMusicPath.Location = new System.Drawing.Point(139, 24); + this.lblMusicPath.Name = "lblMusicPath"; + this.lblMusicPath.Size = new System.Drawing.Size(25, 13); + this.lblMusicPath.TabIndex = 1; + this.lblMusicPath.Text = "???"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(6, 24); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(62, 13); + this.label2.TabIndex = 0; + this.label2.Text = "Music path:"; + // + // gbCloud + // + this.gbCloud.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbCloud.Controls.Add(this.txtUsername); + this.gbCloud.Controls.Add(this.label3); + this.gbCloud.Controls.Add(this.txtStreamtaggerUrl); + this.gbCloud.Controls.Add(this.label1); + this.gbCloud.Location = new System.Drawing.Point(12, 12); + this.gbCloud.Name = "gbCloud"; + this.gbCloud.Size = new System.Drawing.Size(580, 78); + this.gbCloud.TabIndex = 1; + this.gbCloud.TabStop = false; + this.gbCloud.Text = "Cloud"; + // + // txtUsername + // + this.txtUsername.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtUsername.Location = new System.Drawing.Point(110, 45); + this.txtUsername.Name = "txtUsername"; + this.txtUsername.Size = new System.Drawing.Size(464, 20); + this.txtUsername.TabIndex = 3; + this.txtUsername.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txt_KeyPress); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(46, 48); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(58, 13); + this.label3.TabIndex = 2; + this.label3.Text = "Username:"; + // + // txtStreamtaggerUrl + // + this.txtStreamtaggerUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtStreamtaggerUrl.Location = new System.Drawing.Point(110, 19); + this.txtStreamtaggerUrl.Name = "txtStreamtaggerUrl"; + this.txtStreamtaggerUrl.Size = new System.Drawing.Size(464, 20); + this.txtStreamtaggerUrl.TabIndex = 1; + this.txtStreamtaggerUrl.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txt_KeyPress); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 22); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(98, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Streamtagger URL:"; + // + // cmdOk + // + this.cmdOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdOk.Location = new System.Drawing.Point(517, 194); + this.cmdOk.Name = "cmdOk"; + this.cmdOk.Size = new System.Drawing.Size(75, 23); + this.cmdOk.TabIndex = 2; + this.cmdOk.Text = "Ok"; + this.cmdOk.UseVisualStyleBackColor = true; + this.cmdOk.Click += new System.EventHandler(this.cmdOk_Click); + // + // lblInstructions + // + this.lblInstructions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblInstructions.AutoSize = true; + this.lblInstructions.ForeColor = System.Drawing.Color.Red; + this.lblInstructions.Location = new System.Drawing.Point(9, 204); + this.lblInstructions.Name = "lblInstructions"; + this.lblInstructions.Size = new System.Drawing.Size(0, 13); + this.lblInstructions.TabIndex = 3; + // + // PreferencesDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(604, 229); + this.Controls.Add(this.lblInstructions); + this.Controls.Add(this.cmdOk); + this.Controls.Add(this.gbCloud); + this.Controls.Add(this.gbStorage); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "PreferencesDialog"; + this.Text = "Preferences"; + this.gbStorage.ResumeLayout(false); + this.gbStorage.PerformLayout(); + this.gbCloud.ResumeLayout(false); + this.gbCloud.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.GroupBox gbStorage; + private System.Windows.Forms.Label lblMusicPath; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.GroupBox gbCloud; + private System.Windows.Forms.TextBox txtStreamtaggerUrl; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button cmdPlaylistsPath; + private System.Windows.Forms.Label lblPlaylistsPath; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Button cmdMusicPath; + private System.Windows.Forms.Button cmdOk; + private System.Windows.Forms.FolderBrowserDialog fbdMusicPath; + private System.Windows.Forms.FolderBrowserDialog fbdPlaylistsPath; + private System.Windows.Forms.TextBox txtUsername; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label lblInstructions; + } +} \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.cs b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.cs new file mode 100644 index 0000000..761d367 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + public partial class PreferencesDialog : Form + { + private Preferences _Preferences; + + public Preferences Preferences + { + set + { + _Preferences = value; + if (_Preferences.StreamtaggerUrl != null) + { + txtStreamtaggerUrl.Text = _Preferences.StreamtaggerUrl.AbsoluteUri; + } + txtUsername.Text = _Preferences.Username; + if (_Preferences.MusicPath != null) + { + fbdMusicPath.SelectedPath = _Preferences.MusicPath.FullName; + lblMusicPath.Text = fbdMusicPath.SelectedPath; + } + if (_Preferences.PlaylistsPath != null) + { + fbdPlaylistsPath.SelectedPath = _Preferences.PlaylistsPath.FullName; + lblPlaylistsPath.Text = fbdPlaylistsPath.SelectedPath; + } + } + } + + public string Instructions + { + set + { + lblInstructions.Text = value; + } + } + + public PreferencesDialog() + { + InitializeComponent(); + DialogResult = DialogResult.Cancel; + } + + private void cmdOk_Click(object sender, EventArgs e) + { + try + { + _Preferences.StreamtaggerUrl = new Uri(txtStreamtaggerUrl.Text); + } + catch (UriFormatException ex) + { + lblInstructions.Text = ex.Message; + return; + } + _Preferences.Username = txtUsername.Text; + _Preferences.MusicPath = new DirectoryInfo(fbdMusicPath.SelectedPath); + _Preferences.PlaylistsPath = new DirectoryInfo(fbdPlaylistsPath.SelectedPath); + DialogResult = DialogResult.OK; + Close(); + } + + private void cmdMusicPath_Click(object sender, EventArgs e) + { + string oldPath = fbdMusicPath.SelectedPath; + if (fbdMusicPath.ShowDialog(this) == DialogResult.OK) + { + lblMusicPath.Text = fbdMusicPath.SelectedPath; + } + else + { + fbdMusicPath.SelectedPath = oldPath; + } + } + + private void cmdPlaylistsPath_Click(object sender, EventArgs e) + { + string oldPath = fbdPlaylistsPath.SelectedPath; + if (fbdPlaylistsPath.ShowDialog(this) == DialogResult.OK) + { + lblPlaylistsPath.Text = fbdPlaylistsPath.SelectedPath; + } + else + { + fbdPlaylistsPath.SelectedPath = oldPath; + } + } + + private void txt_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == Convert.ToChar(Keys.Return)) + { + cmdOk.PerformClick(); + e.Handled = true; + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.resx b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.resx new file mode 100644 index 0000000..c166e31 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Dialogs/PreferencesDialog.resx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 143, 17 + + + + + AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAEgOAABIDgAAAAAAAAAA + AAAAAAAAAAAAAOpKUgDkFB4H98nMSfXCx6jqUFjk6klR++tbY/v53N/k8YyRqOlCSkjqSlIH6kpSAAAA + AAAAAAAA6kpSAOpKUgDqSlIY6UVOkvCDiO/76uz/7XN6/+pGTv/ubnb//Ozu//B4fv/qR0/u6kpSkupK + UhjqSlIA6kpSAOpKUgDqSlIY7EtTsOpJUf/sV1//+9vd//W7wP/rS1P/yWNp/66lpv/PXGL/6EhQ/+5L + U//sS1Ow60pSGOtKUgD5UVkG6kpSkqg1O//UQ0r/ojE3/9KZnP/+8vP/7nF4/2EdIf8AAQH/HwsM/1cb + Hv+WLzX/z0JJ/+dJUZLwTFQGvTxCSddES+1yJCj/y0BH/3EiJv+qWFz///P0//W5vv9bKi7/FRQU/wwG + Bv8AAAD/AgEB/yYMDf+yOD7t9U5WSWUgI6czEBL/KQ0O/44tMv9jHyL/tj5E//3Iyv/28vP/Z2Fi/7ip + rf+3S1L/iiov/00YG/8hCgz/xD5F/+5LU6fIP0bjfCcs/xgICP8oDQ7/EAUG/0ASFP+jZmn/5N3d/1BX + X/+WqNL/xXCK/+xNVv/XQ0r/aCEk/99GTv/rSlLjgCgt+0UWGP82ERP/qTU7/0kXGv9mICP/izA0/8B0 + fv8wN0j/b5LM/5WQwP/LZXz/y0dR/3YlKf/mSVH/6kpS+8lAR/tvIyf/GQgJ/yoND/8UBgf/YB4i/8pA + Rv/HPkX/Gw0R/x0qOv91cZf/xXCL/4ppif9oJi3/7EpS/+tKUvvwTFTjwD1D/3AjJ/+eMTb/MA8Q/1MZ + G/+mNDr/3EdO/zkWGP8AAAD/f3+F/+WOnP9tVG//JRAT/4YqL//bRU3j70xUp7s7Qf9vKzL/xlJh/1Uo + MP+2ZX7/s3WW/6aAqf+Sja3/bHyY/8HN3v+isd//V05o/wAAAP8VBwf/nzI4p+5LU0naRU3ts1do/79V + Zv9xLDP/z1do/9NdcP/KY3v/zW2E/7KHrP+knsj/vXmX/8lNWf97Jir/fCcs7cM+REnoSVAG60tTkutM + VP/pSVD/4UdO/+tJUP/rSVD/7UdO/8xid/+Ll87/fGmN/85CSv/hSFD/7UtT/+5LU5L/U1wG6kpSAOpK + UhjqSlKw6kpS/+tKUv/qSlL/6kpS/+tJUf/QW27/bHyr/zxEXv9tMz//vEJL/+5LU7DqSlIY6kpSAOpK + UgDqSlIA6kpSGOpKUpLqSlLu6kpS/+pKUv/qSlL/50pS/6VBTf+QOkX/hDhE7shETZL2TFQY6kpSAOpK + UgAAAAAAAAAAAOpKUgDqSlIH6kpSSOpKUqjqSlLk6kpS++pKUvvtSlLk70tTqO9LU0j8T1YH/E9WAAAA + AAAAAAAA4AcAAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB + AADAAwAA4AcAAA== + + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Playlists.cs b/windows/StreamtaggerSync/StreamtaggerSync/Playlists.cs new file mode 100644 index 0000000..5d04b6a --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Playlists.cs @@ -0,0 +1,47 @@ +using Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StreamtaggerSync +{ + public class Playlist + { + public string Name; + public string Query; + + public Playlist(string name, string query) + { + Name = name; + Query = query; + } + } + + public class PlaylistSet + { + private static JsonTranslator _Translator = new JsonTranslator(); + + public List Playlists; + + public PlaylistSet(List playlists) + { + Playlists = playlists; + } + + public static PlaylistSet FromFile(string jsonFilename) + { + return _Translator.MakeObject(JsonObject.Parse(File.ReadAllText(jsonFilename).Trim())); + } + + public void WriteToFile(string jsonFilename) + { + using (var f = new StreamWriter(jsonFilename)) + { + f.WriteLine(_Translator.MakeJson(this).ToMultilineString()); + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Preferences.cs b/windows/StreamtaggerSync/StreamtaggerSync/Preferences.cs new file mode 100644 index 0000000..6de72b5 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Preferences.cs @@ -0,0 +1,33 @@ +using Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StreamtaggerSync +{ + public class Preferences + { + static JsonTranslator _Translator = new JsonTranslator(); + + public Uri StreamtaggerUrl = new Uri("https://bjp.streamtagger.com"); + public string Username; + public DirectoryInfo MusicPath = new DirectoryInfo(Path.Combine(Syroot.Windows.IO.KnownFolders.Music.Path, "Streamtagger")); + public DirectoryInfo PlaylistsPath = new DirectoryInfo(Path.Combine(Syroot.Windows.IO.KnownFolders.Music.Path, "Streamtagger")); + + public static Preferences FromFile(string jsonFilename) + { + return _Translator.MakeObject(JsonObject.Parse(File.ReadAllText(jsonFilename).TrimEnd())); + } + + public void WriteToFile(string jsonFilename) + { + using (var f = new StreamWriter(jsonFilename)) + { + f.WriteLine(_Translator.MakeJson(this).ToMultilineString()); + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Program.cs b/windows/StreamtaggerSync/StreamtaggerSync/Program.cs new file mode 100644 index 0000000..c4e55a6 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new SyncForm()); + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Properties/AssemblyInfo.cs b/windows/StreamtaggerSync/StreamtaggerSync/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..795a54c --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StreamtaggerSync")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StreamtaggerSync")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29e66ea1-567e-4260-8297-8fae62f84e6c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.Designer.cs new file mode 100644 index 0000000..cbaf7ca --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace StreamtaggerSync.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StreamtaggerSync.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap folder_xxl { + get { + object obj = ResourceManager.GetObject("folder-xxl", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.resx b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.resx new file mode 100644 index 0000000..9191135 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Assets\folder-xxl.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.Designer.cs new file mode 100644 index 0000000..347ab46 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace StreamtaggerSync.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.settings b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/windows/StreamtaggerSync/StreamtaggerSync/StreamtaggerSync.csproj b/windows/StreamtaggerSync/StreamtaggerSync/StreamtaggerSync.csproj new file mode 100644 index 0000000..b111b37 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/StreamtaggerSync.csproj @@ -0,0 +1,182 @@ + + + + + Debug + AnyCPU + {29E66EA1-567E-4260-8297-8FAE62F84E6C} + WinExe + StreamtaggerSync + StreamtaggerSync + v4.6.1 + 512 + true + true + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + https://github.com/BenjaminPelletier/streamtagger + https://github.com/BenjaminPelletier/streamtagger/issues + Streamtagger Sync + Benjamin Pelletier + Streamtagger + false + true + 1 + 1.0.0.%2a + false + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + 951BA243733DB08C19ECC57DBE314B4FF0AB159B + + + StreamtaggerSync_TemporaryKey.pfx + + + true + + + true + + + + ..\packages\JsonSerialization.1.0.4\lib\netstandard2.0\JsonSerialization.dll + + + ..\packages\Syroot.Windows.IO.KnownFolders.1.2.1\lib\net452\Syroot.KnownFolders.dll + + + + + + + + + + + + + + + + + Form + + + PasswordDialog.cs + + + UserControl + + + PlaylistControl.cs + + + UserControl + + + PlaylistListBox.cs + + + + + Form + + + SyncForm.cs + + + Form + + + PreferencesDialog.cs + + + + + + PlaylistControl.cs + + + PlaylistListBox.cs + + + PasswordDialog.cs + + + SyncForm.cs + + + PreferencesDialog.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.Designer.cs b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.Designer.cs new file mode 100644 index 0000000..337858b --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.Designer.cs @@ -0,0 +1,228 @@ +namespace StreamtaggerSync +{ + partial class SyncForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SyncForm)); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.preferencesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.gbPlaylists = new System.Windows.Forms.GroupBox(); + this.gbSync = new System.Windows.Forms.GroupBox(); + this.txtSyncLog = new System.Windows.Forms.TextBox(); + this.cmdSync = new System.Windows.Forms.Button(); + this.cmdCopyLog = new System.Windows.Forms.Button(); + this.playlistListBox1 = new StreamtaggerSync.PlaylistListBox(); + this.menuStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.gbPlaylists.SuspendLayout(); + this.gbSync.SuspendLayout(); + this.SuspendLayout(); + // + // menuStrip1 + // + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(1046, 24); + this.menuStrip1.TabIndex = 0; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.preferencesToolStripMenuItem, + this.toolStripMenuItem1, + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "&File"; + // + // preferencesToolStripMenuItem + // + this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; + this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(135, 22); + this.preferencesToolStripMenuItem.Text = "&Preferences"; + this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); + // + // toolStripMenuItem1 + // + this.toolStripMenuItem1.Name = "toolStripMenuItem1"; + this.toolStripMenuItem1.Size = new System.Drawing.Size(132, 6); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(135, 22); + this.exitToolStripMenuItem.Text = "E&xit"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + // + // splitContainer1 + // + this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer1.Location = new System.Drawing.Point(12, 27); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.gbPlaylists); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.gbSync); + this.splitContainer1.Size = new System.Drawing.Size(1022, 532); + this.splitContainer1.SplitterDistance = 487; + this.splitContainer1.TabIndex = 2; + // + // gbPlaylists + // + this.gbPlaylists.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbPlaylists.Controls.Add(this.playlistListBox1); + this.gbPlaylists.Location = new System.Drawing.Point(3, 3); + this.gbPlaylists.Name = "gbPlaylists"; + this.gbPlaylists.Size = new System.Drawing.Size(481, 526); + this.gbPlaylists.TabIndex = 2; + this.gbPlaylists.TabStop = false; + this.gbPlaylists.Text = "Playlists"; + // + // gbSync + // + this.gbSync.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbSync.Controls.Add(this.cmdCopyLog); + this.gbSync.Controls.Add(this.cmdSync); + this.gbSync.Controls.Add(this.txtSyncLog); + this.gbSync.Location = new System.Drawing.Point(3, 3); + this.gbSync.Name = "gbSync"; + this.gbSync.Size = new System.Drawing.Size(525, 526); + this.gbSync.TabIndex = 0; + this.gbSync.TabStop = false; + this.gbSync.Text = "Sync"; + // + // txtSyncLog + // + this.txtSyncLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtSyncLog.Location = new System.Drawing.Point(6, 19); + this.txtSyncLog.Multiline = true; + this.txtSyncLog.Name = "txtSyncLog"; + this.txtSyncLog.ReadOnly = true; + this.txtSyncLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtSyncLog.Size = new System.Drawing.Size(513, 472); + this.txtSyncLog.TabIndex = 0; + // + // cmdSync + // + this.cmdSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdSync.Location = new System.Drawing.Point(413, 497); + this.cmdSync.Name = "cmdSync"; + this.cmdSync.Size = new System.Drawing.Size(106, 23); + this.cmdSync.TabIndex = 1; + this.cmdSync.Text = "Sync!"; + this.cmdSync.UseVisualStyleBackColor = true; + this.cmdSync.Click += new System.EventHandler(this.cmdSync_Click); + // + // cmdCopyLog + // + this.cmdCopyLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cmdCopyLog.Location = new System.Drawing.Point(6, 497); + this.cmdCopyLog.Name = "cmdCopyLog"; + this.cmdCopyLog.Size = new System.Drawing.Size(75, 23); + this.cmdCopyLog.TabIndex = 2; + this.cmdCopyLog.Text = "Copy log"; + this.cmdCopyLog.UseVisualStyleBackColor = true; + // + // playlistListBox1 + // + this.playlistListBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.playlistListBox1.AutoScroll = true; + this.playlistListBox1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.playlistListBox1.Location = new System.Drawing.Point(6, 19); + this.playlistListBox1.Name = "playlistListBox1"; + this.playlistListBox1.Size = new System.Drawing.Size(469, 501); + this.playlistListBox1.TabIndex = 0; + this.playlistListBox1.PlaylistsChanged += new System.EventHandler(this.playlistListBox1_PlaylistsChanged); + this.playlistListBox1.InformationRequested += new System.EventHandler(this.playlistListBox1_InformationRequested); + // + // SyncForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1046, 571); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.menuStrip1); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.menuStrip1; + this.Name = "SyncForm"; + this.Text = "Streamtagger Sync"; + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.gbPlaylists.ResumeLayout(false); + this.gbSync.ResumeLayout(false); + this.gbSync.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.GroupBox gbPlaylists; + private PlaylistListBox playlistListBox1; + private System.Windows.Forms.GroupBox gbSync; + private System.Windows.Forms.Button cmdSync; + private System.Windows.Forms.TextBox txtSyncLog; + private System.Windows.Forms.Button cmdCopyLog; + } +} + diff --git a/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.cs b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.cs new file mode 100644 index 0000000..d545c71 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace StreamtaggerSync +{ + public partial class SyncForm : Form + { + private FileInfo _PreferencesPath = new FileInfo("Preferences.json"); + private Preferences _Preferences; + + private FileInfo _PlaylistsPath = new FileInfo("Playlists.json"); + + private string _Password = null; + + public SyncForm() + { + InitializeComponent(); + if (_PreferencesPath.Exists) + { + _Preferences = Preferences.FromFile(_PreferencesPath.FullName); + } + else + { + _Preferences = new Preferences(); + } + if (_PlaylistsPath.Exists) + { + playlistListBox1.Playlists = PlaylistSet.FromFile(_PlaylistsPath.FullName); + } + } + + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + Application.Exit(); + } + + private void preferencesToolStripMenuItem_Click(object sender, EventArgs e) + { + UpdatePreferences(); + } + + private DialogResult UpdatePreferences(string instructions = null) + { + var prefs = new PreferencesDialog(); + prefs.Preferences = _Preferences; + if (instructions != null) + { + prefs.Instructions = instructions; + } + DialogResult result = prefs.ShowDialog(this); + if (result == DialogResult.OK) + { + _Preferences.WriteToFile(_PreferencesPath.FullName); + } + return result; + } + + private void playlistListBox1_PlaylistsChanged(object sender, EventArgs e) + { + playlistListBox1.Playlists.WriteToFile(_PlaylistsPath.FullName); + } + + private async void cmdSync_Click(object sender, EventArgs e) + { + if (cmdSync.Text == "Sync!") + { + cmdSync.Text = "Cancel"; + var cts = new CancellationTokenSource(); + cmdSync.Tag = cts; + await Synchronize(cts.Token); + cmdSync.Text = "Sync!"; + cmdSync.Enabled = true; + } + else if (cmdSync.Text == "Cancel") + { + (cmdSync.Tag as CancellationTokenSource).Cancel(); + cmdSync.Text = "Cancelling..."; + cmdSync.Enabled = false; + } + } + + private async Task Synchronize(CancellationToken token) + { + if (_Preferences.Username == "") + { + if (UpdatePreferences("Please specify a valid username") != DialogResult.OK) + { + return; + } + } + + var passwordDialog = new PasswordDialog(); + passwordDialog.Password = _Password; + DialogResult result = passwordDialog.ShowDialog(this); + if (result != DialogResult.OK) + { + return; + } + _Password = passwordDialog.Password; + + var synchronizer = new Synchronizer(playlistListBox1.Playlists, _Preferences, _Password); + synchronizer.LogMessage += synchronizer_LogMessage; + + await synchronizer.Synchronize(token); + } + + private void synchronizer_LogMessage(object sender, Synchronizer.LogMessageEventArgs e) + { + if (txtSyncLog.InvokeRequired) + { + txtSyncLog.Invoke(new Action(synchronizer_LogMessage)); + return; + } + + txtSyncLog.Text += "==> " + e.Message + "\r\n"; + txtSyncLog.SelectionStart = txtSyncLog.Text.Length - 1; + txtSyncLog.ScrollToCaret(); + } + + private void playlistListBox1_InformationRequested(object sender, PlaylistControl.InformationRequestedEventArgs e) + { + e.StreamtaggerUri = _Preferences.StreamtaggerUrl; + } + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.resx b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.resx new file mode 100644 index 0000000..75ae26d --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/SyncForm.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAEgOAABIDgAAAAAAAAAA + AAAAAAAAAAAAAOpKUgDkFB4H98nMSfXCx6jqUFjk6klR++tbY/v53N/k8YyRqOlCSkjqSlIH6kpSAAAA + AAAAAAAA6kpSAOpKUgDqSlIY6UVOkvCDiO/76uz/7XN6/+pGTv/ubnb//Ozu//B4fv/qR0/u6kpSkupK + UhjqSlIA6kpSAOpKUgDqSlIY7EtTsOpJUf/sV1//+9vd//W7wP/rS1P/yWNp/66lpv/PXGL/6EhQ/+5L + U//sS1Ow60pSGOtKUgD5UVkG6kpSkqg1O//UQ0r/ojE3/9KZnP/+8vP/7nF4/2EdIf8AAQH/HwsM/1cb + Hv+WLzX/z0JJ/+dJUZLwTFQGvTxCSddES+1yJCj/y0BH/3EiJv+qWFz///P0//W5vv9bKi7/FRQU/wwG + Bv8AAAD/AgEB/yYMDf+yOD7t9U5WSWUgI6czEBL/KQ0O/44tMv9jHyL/tj5E//3Iyv/28vP/Z2Fi/7ip + rf+3S1L/iiov/00YG/8hCgz/xD5F/+5LU6fIP0bjfCcs/xgICP8oDQ7/EAUG/0ASFP+jZmn/5N3d/1BX + X/+WqNL/xXCK/+xNVv/XQ0r/aCEk/99GTv/rSlLjgCgt+0UWGP82ERP/qTU7/0kXGv9mICP/izA0/8B0 + fv8wN0j/b5LM/5WQwP/LZXz/y0dR/3YlKf/mSVH/6kpS+8lAR/tvIyf/GQgJ/yoND/8UBgf/YB4i/8pA + Rv/HPkX/Gw0R/x0qOv91cZf/xXCL/4ppif9oJi3/7EpS/+tKUvvwTFTjwD1D/3AjJ/+eMTb/MA8Q/1MZ + G/+mNDr/3EdO/zkWGP8AAAD/f3+F/+WOnP9tVG//JRAT/4YqL//bRU3j70xUp7s7Qf9vKzL/xlJh/1Uo + MP+2ZX7/s3WW/6aAqf+Sja3/bHyY/8HN3v+isd//V05o/wAAAP8VBwf/nzI4p+5LU0naRU3ts1do/79V + Zv9xLDP/z1do/9NdcP/KY3v/zW2E/7KHrP+knsj/vXmX/8lNWf97Jir/fCcs7cM+REnoSVAG60tTkutM + VP/pSVD/4UdO/+tJUP/rSVD/7UdO/8xid/+Ll87/fGmN/85CSv/hSFD/7UtT/+5LU5L/U1wG6kpSAOpK + UhjqSlKw6kpS/+tKUv/qSlL/6kpS/+tJUf/QW27/bHyr/zxEXv9tMz//vEJL/+5LU7DqSlIY6kpSAOpK + UgDqSlIA6kpSGOpKUpLqSlLu6kpS/+pKUv/qSlL/50pS/6VBTf+QOkX/hDhE7shETZL2TFQY6kpSAOpK + UgAAAAAAAAAAAOpKUgDqSlIH6kpSSOpKUqjqSlLk6kpS++pKUvvtSlLk70tTqO9LU0j8T1YH/E9WAAAA + AAAAAAAA4AcAAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB + AADAAwAA4AcAAA== + + + \ No newline at end of file diff --git a/windows/StreamtaggerSync/StreamtaggerSync/Synchronizer.cs b/windows/StreamtaggerSync/StreamtaggerSync/Synchronizer.cs new file mode 100644 index 0000000..d113c47 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/Synchronizer.cs @@ -0,0 +1,243 @@ +using Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; + +namespace StreamtaggerSync +{ + class Synchronizer + { + /// + /// Salt to use when constructing the password hash + /// + /// + /// If changing here, also change in + /// + const string SALT = "K80Tgi^w1&jc"; + + public class LogMessageEventArgs : EventArgs + { + public readonly string Message; + + public LogMessageEventArgs(string message) + { + Message = message; + } + } + + private PlaylistSet _Playlists; + private Preferences _Preferences; + private string _Password; + + public event EventHandler LogMessage; + + public Synchronizer(PlaylistSet playlists, Preferences preferences, string password) + { + _Playlists = playlists; + _Preferences = preferences; + _Password = password; + } + + private void Log(string message) + { + LogMessage?.Invoke(this, new LogMessageEventArgs(message)); + } + + public async Task Synchronize(CancellationToken token) + { + try + { + Log("***STARTING SYNCHRONIZATION***"); + CookieContainer cookies = new CookieContainer(); + using (var handler = new HttpClientHandler() { CookieContainer = cookies }) + using (var client = new HttpClient(handler)) + { + // Log in to get session cookie + Uri loginUri = new Uri(_Preferences.StreamtaggerUrl, "login"); + Log("Logging " + _Preferences.Username + " into Streamtagger at " + loginUri.AbsoluteUri + "..."); + string passwordHash = Hash(SALT + _Preferences.Username + _Password + SALT + _Preferences.Username); + var loginData = new Dictionary() { { "username", _Preferences.Username }, { "password_hash", passwordHash } }; + var content = new FormUrlEncodedContent(loginData); + using (var response = await client.PostAsync(loginUri, content, token)) + { + if (!response.IsSuccessStatusCode) + { + Log("Error logging into Streamtagger: " + response.StatusCode + " " + response.ReasonPhrase + ":\r\n" + await response.Content.ReadAsStringAsync()); + return; + } + } + + // Retrieve song lists for all playlists + var playlistPathLists = new Dictionary>(); + var syncSongs = new Dictionary(); + Uri songListUri = new Uri(_Preferences.StreamtaggerUrl, "song_list"); + foreach (Playlist playlistDef in _Playlists.Playlists) + { + Uri playlistUri = AddQuery(songListUri, playlistDef.Query); + Log("Reading song list " + (playlistDef.Name == "" ? "" : ("for " + playlistDef.Name)) + " at " + playlistUri.AbsoluteUri); + using (var response = await client.GetAsync(playlistUri, token)) + { + if (!response.IsSuccessStatusCode) + { + Log("Error reading song list: " + response.StatusCode + " " + response.ReasonPhrase + ":\r\n" + await response.Content.ReadAsStringAsync()); + return; + } + string songListJson = await response.Content.ReadAsStringAsync(); + StreamtaggerSongsResponse parsedResponse = JsonTranslator.Singleton.MakeObject(JsonObject.Parse(songListJson.Trim())); + if (parsedResponse.status != "success") + { + Log("Song list query did not indicate success:\r\n" + songListJson); + return; + } + + if (playlistDef.Name != "") + { + playlistPathLists[playlistDef.Name] = parsedResponse.songs.Select(s => s.path).ToList(); + } + + int prevSongPathCount = syncSongs.Count; + foreach (var song in parsedResponse.songs) + { + if (!syncSongs.ContainsKey(song.path)) + { + syncSongs[song.path] = new SyncSong(song.path, _Preferences.StreamtaggerUrl, _Preferences.MusicPath); + } + } + Log("Added " + (syncSongs.Count - prevSongPathCount) + " to sync list"); + } + } + + // Download songs + string tempFileName = Path.Combine(_Preferences.MusicPath.FullName, "song.partialdownload"); + int nDownloaded = 0; + foreach (SyncSong syncSong in syncSongs.Values) + { + if (!syncSong.LocalFile.Directory.Exists) + { + Log("Creating " + syncSong.LocalFile.DirectoryName + " locally"); + syncSong.LocalFile.Directory.Create(); + } + + if (syncSong.LocalFile.Exists) + { + continue; + } + + Log("Downloading " + syncSong.RemotePath); + using (var response = await client.GetAsync(syncSong.RemotePath, token)) + { + if (!response.IsSuccessStatusCode) + { + Log("Error downloading: " + response.StatusCode + " " + response.ReasonPhrase); + continue; + } + + using (var w = new FileStream(tempFileName, FileMode.Create)) + { + await response.Content.CopyToAsync(w); + } + File.Move(tempFileName, syncSong.LocalFile.FullName); + nDownloaded++; + } + } + + // Write M3U playlists + int nPlaylists = 0; + foreach (string playlistName in playlistPathLists.Keys) + { + string playlistFilename = Path.Combine(_Preferences.PlaylistsPath.FullName, playlistName + ".m3u"); + using (var w = new StreamWriter(playlistFilename)) + { + w.WriteLine("#EXTM3U"); + foreach (string songPath in playlistPathLists[playlistName]) + { + w.WriteLine(syncSongs[songPath].LocalFile.FullName); + } + nPlaylists++; + } + } + + Log("Synchronization successful; downloaded " + nDownloaded + " songs and updated " + nPlaylists + " playlists."); + } + } + catch (WebException ex) + { + if (ex.Status == WebExceptionStatus.RequestCanceled) + { + Log("Synchronization was cancelled"); + } + } + catch (Exception ex) + { + if (ex is AggregateException) + { + ex = ex.InnerException; + } + Log("Fatal unexpected error " + ex.GetType().FullName + ":\r\n" + ex.ToString()); + } + } + + static string Hash(string input) + { + using (SHA1Managed sha1 = new SHA1Managed()) + { + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input)); + return string.Concat(hash.Select(b => b.ToString("x2"))); + } + } + + public static Uri AddQuery(Uri uri, string queryString) + { + var uriBuilder = new UriBuilder(uri); + var query = HttpUtility.ParseQueryString(queryString); + uriBuilder.Query = query.ToString(); + return uriBuilder.Uri; + } + + private class SyncSong + { + public Uri RemotePath; + public FileInfo LocalFile; + + public SyncSong(string relativeSongPath, Uri baseUri, DirectoryInfo mediaPath) + { + if (!relativeSongPath.Contains("media/")) + { + throw new FormatException("Relative song path provided by server does not contain 'media/': " + relativeSongPath); + } + if (baseUri.AbsolutePath.EndsWith("/") && relativeSongPath.StartsWith("/")) + { + relativeSongPath = relativeSongPath.Substring(1); + } + RemotePath = new Uri(baseUri.AbsoluteUri + relativeSongPath); + string[] relativeSongPathComponents = relativeSongPath.Substring(relativeSongPath.IndexOf("media/") + "media/".Length).Split('/'); + LocalFile = new FileInfo(Path.Combine(mediaPath.FullName, relativeSongPathComponents.Aggregate(Path.Combine))); + } + } + + #pragma warning disable 0649 + + private class StreamtaggerSongsResponse + { + public string status; + public List songs; + + public class StreamtaggerSong + { + public string title; + public string path; + } + } + + #pragma warning restore 0649 + + } +} diff --git a/windows/StreamtaggerSync/StreamtaggerSync/packages.config b/windows/StreamtaggerSync/StreamtaggerSync/packages.config new file mode 100644 index 0000000..53581d4 --- /dev/null +++ b/windows/StreamtaggerSync/StreamtaggerSync/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file