├── .gitignore ├── RedditSkylines ├── CitizenInfo.cs ├── ModInfo.cs ├── Properties │ └── AssemblyInfo.cs ├── RedditClient.csproj ├── TinyWeb.cs ├── Message.cs ├── Configuration.cs ├── RedditUpdater.cs └── SimpleJSON.cs ├── README.md ├── LICENSE ├── Compatibility ├── Message.cs ├── v2 │ ├── Properties │ │ └── AssemblyInfo.cs │ └── compat.v2.csproj ├── v3 │ ├── Properties │ │ └── AssemblyInfo.cs │ └── compat.v3.csproj ├── v4 │ ├── Properties │ │ └── AssemblyInfo.cs │ └── compat.v4.csproj └── v5 │ ├── Properties │ └── AssemblyInfo.cs │ └── compat.v5.csproj └── RedditClient.sln /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | [Rr]elease/ 3 | [Dd]ebug/ 4 | -------------------------------------------------------------------------------- /RedditSkylines/CitizenInfo.cs: -------------------------------------------------------------------------------- 1 | namespace RedditClient 2 | { 3 | /// 4 | /// I would've used tuples but the mod would no longer load. 5 | /// 6 | internal class CitizenInfo 7 | { 8 | public string Name; 9 | public uint ID; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RedditSkylines/ModInfo.cs: -------------------------------------------------------------------------------- 1 | using ICities; 2 | 3 | namespace RedditClient 4 | { 5 | public class ModInfo : IUserMod 6 | { 7 | public string Description 8 | { 9 | get { return "Show what's new on Reddit"; } 10 | } 11 | 12 | public string Name 13 | { 14 | get { return "Reddit for Chirpy"; } 15 | } 16 | 17 | public static int Version 18 | { 19 | get { return 9; } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reddit for Chirpy (a Cities: Skylines mod) 2 | ### Info 3 | 4 | * [Configuration explained](https://github.com/mabako/reddit-for-city-skylines/wiki/Configuration) 5 | 6 | # Links 7 | 8 | * [on Steam Workshop](http://steamcommunity.com/sharedfiles/filedetails/?id=408705348) 9 | * [on /r/CitiesSkylines](https://www.reddit.com/r/CitiesSkylines/comments/2z87if/reddit_for_chirpy_view_whats_new_on_reddit_ingame/) 10 | * [on /r/CitiesSkylinesModding](https://www.reddit.com/r/CitiesSkylinesModding/comments/2z68bw/reddit_for_chirpy_wip_showerthoughts_on_your/) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marcus Bauer 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 | -------------------------------------------------------------------------------- /Compatibility/Message.cs: -------------------------------------------------------------------------------- 1 | namespace RedditClient 2 | { 3 | public class Message : MessageBase 4 | { 5 | private string m_author; 6 | private string m_subreddit; 7 | private string m_text; 8 | 9 | public override uint GetSenderID() 10 | { 11 | return 0; 12 | } 13 | 14 | public override string GetSenderName() 15 | { 16 | return m_author; 17 | } 18 | 19 | public override string GetText() 20 | { 21 | return string.Format("{0} #{1}", m_text, m_subreddit); 22 | } 23 | 24 | /// 25 | /// We basically want to ensure the same messages aren't shown twice. 26 | /// 27 | /// 28 | /// 29 | public override bool IsSimilarMessage(MessageBase other) 30 | { 31 | return false; 32 | } 33 | 34 | public override void Serialize(ColossalFramework.IO.DataSerializer s) 35 | { 36 | s.WriteSharedString(m_author); 37 | s.WriteSharedString(m_subreddit); 38 | s.WriteSharedString(m_text); 39 | } 40 | 41 | public override void Deserialize(ColossalFramework.IO.DataSerializer s) 42 | { 43 | m_author = s.ReadSharedString(); 44 | m_subreddit = s.ReadSharedString(); 45 | m_text = s.ReadSharedString(); 46 | } 47 | 48 | public override void AfterDeserialize(ColossalFramework.IO.DataSerializer s) 49 | { 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RedditSkylines/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die mit einer Assembly verknüpft sind. 7 | [assembly: AssemblyTitle("Reddit for Chirpy")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Reddit for Chirpy")] 12 | [assembly: AssemblyCopyright("Copyright © 2015 Marcus Bauer")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("e678dd7d-2a04-4085-bf21-d5dc9d4fbbf2")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("7.0.0.0")] 35 | [assembly: AssemblyFileVersion("7.0.0.0")] 36 | -------------------------------------------------------------------------------- /Compatibility/v2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die mit einer Assembly verknüpft sind. 7 | [assembly: AssemblyTitle("Reddit for Chirpy")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Reddit for Chirpy")] 12 | [assembly: AssemblyCopyright("Copyright © 2015 Marcus Bauer")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("e678dd7d-2a04-4085-bf21-d5dc9d4fbbf2")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("2.0.0.0")] 35 | [assembly: AssemblyFileVersion("2.0.0.0")] 36 | -------------------------------------------------------------------------------- /Compatibility/v3/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die mit einer Assembly verknüpft sind. 7 | [assembly: AssemblyTitle("Reddit for Chirpy")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Reddit for Chirpy")] 12 | [assembly: AssemblyCopyright("Copyright © 2015 Marcus Bauer")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("e678dd7d-2a04-4085-bf21-d5dc9d4fbbf2")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("3.0.0.0")] 35 | [assembly: AssemblyFileVersion("3.0.0.0")] 36 | -------------------------------------------------------------------------------- /Compatibility/v4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die mit einer Assembly verknüpft sind. 7 | [assembly: AssemblyTitle("Reddit for Chirpy")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Reddit for Chirpy")] 12 | [assembly: AssemblyCopyright("Copyright © 2015 Marcus Bauer")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("e678dd7d-2a04-4085-bf21-d5dc9d4fbbf2")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("4.0.0.0")] 35 | [assembly: AssemblyFileVersion("4.0.0.0")] 36 | -------------------------------------------------------------------------------- /Compatibility/v5/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die mit einer Assembly verknüpft sind. 7 | [assembly: AssemblyTitle("Reddit for Chirpy")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Reddit for Chirpy")] 12 | [assembly: AssemblyCopyright("Copyright © 2015 Marcus Bauer")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("e678dd7d-2a04-4085-bf21-d5dc9d4fbbf2")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("5.0.0.0")] 35 | [assembly: AssemblyFileVersion("5.0.0.0")] 36 | -------------------------------------------------------------------------------- /Compatibility/v2/compat.v2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7} 8 | Library 9 | Properties 10 | RedditClient 11 | reddit 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 36 | 37 | 38 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 39 | 40 | 41 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | set CS_MOD_FOLDER=%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\ 56 | set PROJECT_MOD_FOLDER=%25CS_MOD_FOLDER%25\$(SolutionName)\ 57 | if not exist "%25PROJECT_MOD_FOLDER%25" mkdir "%25PROJECT_MOD_FOLDER%25" 58 | copy /y "$(TargetPath)" "%25PROJECT_MOD_FOLDER%25\$(ProjectName).dll" 59 | 60 | 67 | -------------------------------------------------------------------------------- /Compatibility/v3/compat.v3.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B} 8 | Library 9 | Properties 10 | RedditClient 11 | reddit 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 36 | 37 | 38 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 39 | 40 | 41 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | set CS_MOD_FOLDER=%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\ 56 | set PROJECT_MOD_FOLDER=%25CS_MOD_FOLDER%25\$(SolutionName)\ 57 | if not exist "%25PROJECT_MOD_FOLDER%25" mkdir "%25PROJECT_MOD_FOLDER%25" 58 | copy /y "$(TargetPath)" "%25PROJECT_MOD_FOLDER%25\$(ProjectName).dll" 59 | 60 | 67 | -------------------------------------------------------------------------------- /Compatibility/v4/compat.v4.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2811E498-8EC2-4B04-971A-96AEFBA34C37} 8 | Library 9 | Properties 10 | RedditClient 11 | reddit 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 36 | 37 | 38 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 39 | 40 | 41 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | set CS_MOD_FOLDER=%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\ 56 | set PROJECT_MOD_FOLDER=%25CS_MOD_FOLDER%25\$(SolutionName)\ 57 | if not exist "%25PROJECT_MOD_FOLDER%25" mkdir "%25PROJECT_MOD_FOLDER%25" 58 | copy /y "$(TargetPath)" "%25PROJECT_MOD_FOLDER%25\$(ProjectName).dll" 59 | 60 | 67 | -------------------------------------------------------------------------------- /Compatibility/v5/compat.v5.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2811E498-8FC2-4B04-971A-96AEFBA34C37} 8 | Library 9 | Properties 10 | RedditClient 11 | reddit 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 36 | 37 | 38 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 39 | 40 | 41 | C:\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | set CS_MOD_FOLDER=%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\ 56 | set PROJECT_MOD_FOLDER=%25CS_MOD_FOLDER%25\$(SolutionName)\ 57 | if not exist "%25PROJECT_MOD_FOLDER%25" mkdir "%25PROJECT_MOD_FOLDER%25" 58 | copy /y "$(TargetPath)" "%25PROJECT_MOD_FOLDER%25\$(ProjectName).dll" 59 | 60 | 67 | -------------------------------------------------------------------------------- /RedditClient.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedditClient", "RedditSkylines\RedditClient.csproj", "{A28394F0-1ED9-4153-B6EB-274D290B5E38}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compatibility", "Compatibility", "{3F498AAF-364D-42E0-8786-347A263FF904}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "compat.v2", "Compatibility\v2\compat.v2.csproj", "{2811E498-8EC2-4B04-971A-96AEFBA34CF7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "compat.v3", "Compatibility\v3\compat.v3.csproj", "{2811E498-8EC2-4B04-971A-96AEFBA34C3B}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "compat.v4", "Compatibility\v4\compat.v4.csproj", "{2811E498-8EC2-4B04-971A-96AEFBA34C37}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "compat.v5", "Compatibility\v5\compat.v5.csproj", "{2811E498-8FC2-4B04-971A-96AEFBA34C37}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {A28394F0-1ED9-4153-B6EB-274D290B5E38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A28394F0-1ED9-4153-B6EB-274D290B5E38}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A28394F0-1ED9-4153-B6EB-274D290B5E38}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A28394F0-1ED9-4153-B6EB-274D290B5E38}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {2811E498-8EC2-4B04-971A-96AEFBA34C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {2811E498-8EC2-4B04-971A-96AEFBA34C37}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {2811E498-8EC2-4B04-971A-96AEFBA34C37}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {2811E498-8EC2-4B04-971A-96AEFBA34C37}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {2811E498-8FC2-4B04-971A-96AEFBA34C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {2811E498-8FC2-4B04-971A-96AEFBA34C37}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {2811E498-8FC2-4B04-971A-96AEFBA34C37}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {2811E498-8FC2-4B04-971A-96AEFBA34C37}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {2811E498-8EC2-4B04-971A-96AEFBA34CF7} = {3F498AAF-364D-42E0-8786-347A263FF904} 50 | {2811E498-8EC2-4B04-971A-96AEFBA34C3B} = {3F498AAF-364D-42E0-8786-347A263FF904} 51 | {2811E498-8EC2-4B04-971A-96AEFBA34C37} = {3F498AAF-364D-42E0-8786-347A263FF904} 52 | {2811E498-8FC2-4B04-971A-96AEFBA34C37} = {3F498AAF-364D-42E0-8786-347A263FF904} 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /RedditSkylines/RedditClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A28394F0-1ED9-4153-B6EB-274D290B5E38} 8 | Library 9 | Properties 10 | RedditClient 11 | reddit 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | ..\..\..\..\..\..\..\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 39 | 40 | 41 | ..\..\..\..\..\..\..\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 42 | 43 | 44 | ..\..\..\..\..\..\..\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 45 | 46 | 47 | 48 | 49 | ..\..\..\..\..\..\..\Program Files (x86)\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.dll 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | set CS_MOD_FOLDER=%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\ 65 | set PROJECT_MOD_FOLDER=%25CS_MOD_FOLDER%25\$(SolutionName)\ 66 | if not exist "%25PROJECT_MOD_FOLDER%25" mkdir "%25PROJECT_MOD_FOLDER%25" 67 | copy /y "$(TargetPath)" "%25PROJECT_MOD_FOLDER%25" 68 | copy /y "$(SolutionDir)\LICENSE" "%25PROJECT_MOD_FOLDER%25" 69 | 70 | 77 | -------------------------------------------------------------------------------- /RedditSkylines/TinyWeb.cs: -------------------------------------------------------------------------------- 1 | using SimpleJson; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | 7 | namespace RedditClient 8 | { 9 | internal class TinyWeb 10 | { 11 | private const string BASE_URL = "http://www.reddit.com{0}.json?limit={1}"; 12 | 13 | private const string ANNOUNCEMENT_URL = "http://mabako.net/reddit-for-city-skylines/v{0}.txt"; 14 | 15 | public static IEnumerable FindLastPosts(string subreddit) 16 | { 17 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format(BASE_URL, subreddit, RedditUpdater.MAX_REDDIT_POSTS_PER_SUBREDDIT)); 18 | request.Method = WebRequestMethods.Http.Get; 19 | request.Accept = "text/json"; 20 | 21 | using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 22 | { 23 | if (response.StatusCode != HttpStatusCode.OK) 24 | return null; 25 | 26 | using (var sr = new StreamReader(response.GetResponseStream())) 27 | { 28 | string str = sr.ReadToEnd(); 29 | 30 | JsonObject root = (JsonObject)SimpleJson.SimpleJson.DeserializeObject(str); 31 | JsonObject rootData = (JsonObject)root["data"]; 32 | JsonArray rootChildren = (JsonArray)rootData["children"]; 33 | 34 | var list = new List(); 35 | foreach (object obj in rootChildren) 36 | { 37 | JsonObject child = (JsonObject)obj; 38 | JsonObject data = (JsonObject)child["data"]; 39 | 40 | var post = createPost(data); 41 | if (post != null) 42 | list.Add(post); 43 | } 44 | return list; 45 | } 46 | } 47 | } 48 | 49 | public static string GetAnnouncement() 50 | { 51 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format(ANNOUNCEMENT_URL, ModInfo.Version)); 52 | request.Method = WebRequestMethods.Http.Get; 53 | 54 | using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 55 | { 56 | if (response.StatusCode != HttpStatusCode.OK) 57 | return null; 58 | 59 | using (var sr = new StreamReader(response.GetResponseStream())) 60 | { 61 | return sr.ReadLine(); 62 | } 63 | } 64 | } 65 | 66 | private static RedditPost createPost(JsonObject data) 67 | { 68 | // Any karma at all? 69 | var karma = data["score"]; 70 | if (karma is Int32) 71 | if ((Int32)karma <= 0) 72 | return null; 73 | 74 | // Sticky post? 75 | var sticky = data["stickied"]; 76 | if (sticky is Boolean) 77 | if ((Boolean)sticky == true) 78 | return null; 79 | 80 | // create post object 81 | var post = new RedditPost { id = data["id"].ToString(), title = data["title"].ToString(), author = data["author"].ToString(), subreddit = data["subreddit"].ToString() }; 82 | 83 | // does it have a flair? 84 | var flair = data["link_flair_text"]; 85 | if (flair != null) 86 | { 87 | var flairStr = flair.ToString(); 88 | if (flairStr.Equals("meta", StringComparison.InvariantCultureIgnoreCase)) 89 | return null; 90 | 91 | post.title += " #" + flairStr.Replace(" ", ""); 92 | } 93 | 94 | return post; 95 | } 96 | } 97 | 98 | internal class RedditPost 99 | { 100 | internal string title; 101 | internal string author; 102 | internal string id; 103 | internal string subreddit; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /RedditSkylines/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RedditClient 4 | { 5 | public class Message : MessageBase 6 | { 7 | private string m_author; 8 | private uint m_citizenId; 9 | private string m_subreddit; 10 | private string m_text; 11 | 12 | [NonSerialized] 13 | private string m_postId; 14 | 15 | public Message(string author, string subreddit, string text, uint citizenId, string postId) 16 | { 17 | m_author = author; 18 | m_subreddit = subreddit; 19 | m_text = text; 20 | m_citizenId = citizenId; 21 | m_postId = postId; 22 | 23 | if (Configuration.Hashtags > 0) 24 | HashtagThis(); 25 | } 26 | 27 | private void HashtagThis() 28 | { 29 | var split = m_text.Split(' '); 30 | 31 | int desiredHashtags = split.Length / 4; 32 | int hashtags = m_text.Length - m_text.Replace("#", "").Length; 33 | if (hashtags >= desiredHashtags) 34 | return; 35 | 36 | // Get the longest, non-hashtagged word 37 | string longestWord = ""; 38 | foreach (string str in split) 39 | { 40 | if (!str.StartsWith("#")) 41 | { 42 | int length = str.Length; 43 | if (length == 0) 44 | continue; 45 | 46 | if (!Char.IsLetter(str[0])) 47 | continue; 48 | 49 | // UPPERCASE WORDS ARE MORE IMPORTANT 50 | if (Char.IsUpper(str[0])) 51 | length += 2; 52 | 53 | // random bonus factor 54 | length += new Random().Next(2); 55 | 56 | if (length > longestWord.Length) 57 | { 58 | longestWord = str; 59 | } 60 | } 61 | } 62 | 63 | if (longestWord == "") 64 | return; 65 | 66 | for (int i = 0; i < split.Length; ++i) 67 | { 68 | if (longestWord.Equals(split[i], StringComparison.InvariantCultureIgnoreCase)) 69 | { 70 | split[i] = "#" + split[i]; 71 | } 72 | } 73 | 74 | m_text = string.Join(" ", split); 75 | HashtagThis(); 76 | } 77 | 78 | public override uint GetSenderID() 79 | { 80 | return m_citizenId; 81 | } 82 | 83 | public override string GetSenderName() 84 | { 85 | return m_author; 86 | } 87 | 88 | public override string GetText() 89 | { 90 | return string.Format("{0} #{1}", m_text, m_subreddit); 91 | } 92 | 93 | /// 94 | /// We basically want to ensure the same messages aren't shown twice. 95 | /// 96 | /// 97 | /// 98 | public override bool IsSimilarMessage(MessageBase other) 99 | { 100 | var m = other as Message; 101 | return m != null && ((m.m_author == m_author && m.m_subreddit == m_subreddit) || m.m_text.Replace("#", "") == m_text.Replace("#", "")); 102 | } 103 | 104 | public override void Serialize(ColossalFramework.IO.DataSerializer s) 105 | { 106 | s.WriteSharedString(m_author); 107 | s.WriteSharedString(m_subreddit); 108 | s.WriteSharedString(m_text); 109 | s.WriteUInt32(m_citizenId); 110 | } 111 | 112 | public override void Deserialize(ColossalFramework.IO.DataSerializer s) 113 | { 114 | m_author = s.ReadSharedString(); 115 | m_subreddit = s.ReadSharedString(); 116 | m_text = s.ReadSharedString(); 117 | m_citizenId = s.ReadUInt32(); 118 | } 119 | 120 | public override void AfterDeserialize(ColossalFramework.IO.DataSerializer s) 121 | { 122 | } 123 | 124 | public string GetPostID() 125 | { 126 | return m_postId; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /RedditSkylines/Configuration.cs: -------------------------------------------------------------------------------- 1 | using ColossalFramework.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace RedditClient 8 | { 9 | internal class Configuration 10 | { 11 | private const string TIMER_KEY = "updateFrequency"; 12 | private const string FILTER_MESSAGES_KEY = "filterMessages"; 13 | private const string LAST_ANNOUNCEMENT = "lastAnnouncementId"; 14 | private const string ASSOCIATION_MODE = "associationMode"; 15 | private const string HASHTAG_MODE = "hashtags"; 16 | private const string CLICK_BEHAVIOUR = "clickBehaviour"; 17 | 18 | public static List Subreddits; 19 | public static int TimerInSeconds = 300; 20 | public static int AssociationMode = 0; 21 | public static int Hashtags = 1; 22 | public static int ClickBehaviour = 0; 23 | 24 | public static int FilterMessages = 0; 25 | public static int LastAnnouncement = 0; 26 | 27 | private static string ConfigPath 28 | { 29 | get 30 | { 31 | // base it on the path Cities: Skylines uses 32 | string path = string.Format("{0}/{1}/", DataLocation.localApplicationData, "ModConfig"); 33 | if (!Directory.Exists(path)) 34 | Directory.CreateDirectory(path); 35 | 36 | path += "reddit-for-chirpy.txt"; 37 | 38 | return path; 39 | } 40 | } 41 | 42 | /// 43 | /// Attempts to load the configuration. If it fails, it'll load the defaults 44 | /// 45 | internal static void Load() 46 | { 47 | try 48 | { 49 | string[] configLines = File.ReadAllLines(ConfigPath); 50 | Regex r = new Regex("^[a-zA-Z0-9/]*$"); 51 | 52 | Subreddits = new List(); 53 | bool requestSave = false; 54 | 55 | for (int i = 0; i < configLines.Length; ++i) 56 | { 57 | // Remove unnecessary spaces 58 | var line = configLines[i].Trim(); 59 | 60 | // Comment lines 61 | if (line.StartsWith("#") || line.StartsWith(";")) 62 | continue; 63 | 64 | // Config options 65 | if (line.StartsWith(TIMER_KEY + "=")) 66 | { 67 | var time = line.Substring(TIMER_KEY.Length + 1); 68 | 69 | int newTimer = -1; 70 | if (Int32.TryParse(time, out newTimer) && newTimer >= 10) 71 | { 72 | TimerInSeconds = newTimer; 73 | } 74 | } 75 | else if (line.StartsWith(ASSOCIATION_MODE + "=")) 76 | { 77 | var sound = line.Substring(ASSOCIATION_MODE.Length + 1); 78 | 79 | int mode = -1; 80 | if (Int32.TryParse(sound, out mode) && (mode >= 0 || mode <= 2)) 81 | { 82 | AssociationMode = mode; 83 | } 84 | } 85 | else if (line.StartsWith(HASHTAG_MODE + "=")) 86 | { 87 | var sound = line.Substring(HASHTAG_MODE.Length + 1); 88 | 89 | int mode = -1; 90 | if (Int32.TryParse(sound, out mode) && (mode >= 0 || mode <= 1)) 91 | { 92 | Hashtags = mode; 93 | } 94 | } 95 | else if (line.StartsWith(FILTER_MESSAGES_KEY + "=")) 96 | { 97 | var filter = line.Substring(FILTER_MESSAGES_KEY.Length + 1); 98 | 99 | int newVal = -1; 100 | if (Int32.TryParse(filter, out newVal) && (newVal >= 0 || newVal <= 2)) 101 | { 102 | FilterMessages = newVal; 103 | } 104 | } 105 | else if (line.StartsWith(LAST_ANNOUNCEMENT + "=")) 106 | { 107 | var announcement = line.Substring(LAST_ANNOUNCEMENT.Length + 1); 108 | 109 | int newVal = 0; 110 | if (Int32.TryParse(announcement, out newVal)) 111 | { 112 | LastAnnouncement = newVal; 113 | } 114 | } 115 | else if (line.StartsWith(CLICK_BEHAVIOUR + "=")) 116 | { 117 | var clickb = line.Substring(CLICK_BEHAVIOUR.Length + 1); 118 | 119 | int newVal = 0; 120 | if (Int32.TryParse(clickb, out newVal) && (newVal >= 0 || newVal <= 3)) 121 | { 122 | ClickBehaviour = newVal; 123 | } 124 | } 125 | 126 | // Just reddit names, presumably 127 | else if (line.Length > 1 && r.IsMatch(line)) 128 | { 129 | if (line.IndexOf('/') == -1) 130 | { 131 | line = string.Format("/r/{0}/new", line); 132 | configLines[i] = line; 133 | 134 | requestSave = true; 135 | } 136 | Subreddits.Add(line); 137 | } 138 | } 139 | 140 | if (requestSave) 141 | { 142 | using (StreamWriter sw = new StreamWriter(ConfigPath)) 143 | { 144 | foreach (string line in configLines) 145 | sw.WriteLine(line); 146 | } 147 | } 148 | } 149 | catch (Exception e) 150 | { 151 | DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, string.Format("Reddit: Config regenerated {0}: {1}", e.GetType().ToString(), e.Message)); 152 | 153 | TimerInSeconds = 300; 154 | AssociationMode = 0; 155 | FilterMessages = 0; 156 | Subreddits = DefaultSubreddits; 157 | LastAnnouncement = 0; 158 | Hashtags = 1; 159 | 160 | SaveConfig(true); 161 | } 162 | } 163 | 164 | internal static void SaveConfig(bool defaultConfig) 165 | { 166 | using (StreamWriter sw = new StreamWriter(ConfigPath)) 167 | { 168 | sw.WriteLine("# Reddit for Chirpy"); 169 | sw.WriteLine("# https://github.com/mabako/reddit-for-city-skylines/wiki/Configuration"); 170 | sw.WriteLine(); 171 | 172 | sw.WriteLine("# How often should new messages be displayed?"); 173 | sw.WriteLine("# Default: 300 (which is 5 minutes)"); 174 | sw.WriteLine("{0}={1}", TIMER_KEY, TimerInSeconds); 175 | sw.WriteLine(); 176 | 177 | sw.WriteLine("# How should names be handled?"); 178 | sw.WriteLine("# Default: 0 (disabled)"); 179 | sw.WriteLine("# Set this to '1' to use CIM names instead of reddit usernames."); 180 | sw.WriteLine("# Set this to '2' to permanently rename CIMs to reddit users."); 181 | sw.WriteLine("{0}={1}", ASSOCIATION_MODE, AssociationMode); 182 | 183 | sw.WriteLine(); 184 | sw.WriteLine("# One subreddit per line"); 185 | 186 | foreach (string subreddit in Subreddits) 187 | sw.WriteLine("{0}", subreddit); 188 | 189 | if (defaultConfig) 190 | { 191 | sw.WriteLine(); 192 | sw.WriteLine("# Multireddit example (remove the '#' to use)"); 193 | sw.WriteLine("# /user/ccaatt/m/chirps/new"); 194 | } 195 | 196 | sw.WriteLine(); 197 | sw.WriteLine("# Filters some or all chirps made by your citizen if enabled"); 198 | sw.WriteLine("# Default: 0 (disabled)"); 199 | sw.WriteLine("# Set this to '1' to hide useless chirps"); 200 | sw.WriteLine("# Set this to '2' to hide all chirps"); 201 | sw.WriteLine("# This may break mod compatibility."); 202 | sw.WriteLine("{0}={1}", FILTER_MESSAGES_KEY, FilterMessages); 203 | 204 | sw.WriteLine(); 205 | sw.WriteLine("# Enable automated hashtags?"); 206 | sw.WriteLine("{0}={1}", HASHTAG_MODE, Hashtags); 207 | 208 | sw.WriteLine(); 209 | sw.WriteLine("# What should happen when you click on reddit chirps?"); 210 | sw.WriteLine("# Default: 0 (open Steam Overlay)"); 211 | sw.WriteLine("# Set this to '1' to copy to clipboard"); 212 | sw.WriteLine("# Set this to '2' to open your system browser"); 213 | sw.WriteLine("# Set this to '3' for nothing to happen"); 214 | sw.WriteLine("{0}={1}", CLICK_BEHAVIOUR, ClickBehaviour); 215 | 216 | sw.WriteLine(); 217 | sw.WriteLine("# INTERNAL CONFIG"); 218 | sw.WriteLine("# Make sure to show announcements only once."); 219 | sw.WriteLine("{0}={1}", LAST_ANNOUNCEMENT, LastAnnouncement); 220 | } 221 | } 222 | 223 | private static List DefaultSubreddits 224 | { 225 | get 226 | { 227 | var s = new List(); 228 | s.Add("/r/ShowerThoughts/rising/"); 229 | s.Add("/r/CrazyIdeas/new/"); 230 | s.Add("/r/ChirpIt/new/"); 231 | return s; 232 | } 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /RedditSkylines/RedditUpdater.cs: -------------------------------------------------------------------------------- 1 | using ColossalFramework; 2 | using ColossalFramework.Steamworks; 3 | using ColossalFramework.UI; 4 | using ICities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Timers; 10 | using UnityEngine; 11 | 12 | namespace RedditClient 13 | { 14 | public class RedditUpdater : ChirperExtensionBase 15 | { 16 | public const int MAX_REDDIT_POSTS_PER_SUBREDDIT = 5; 17 | public const int MAX_CACHED_REDDIT_POSTS_PER_SUBREDDIT = 50; 18 | 19 | private System.Timers.Timer timer = new System.Timers.Timer(); 20 | private Dictionary> lastPostIds = new Dictionary>(); 21 | 22 | private AudioClip messageSound = null; 23 | private bool checkedAnnouncement = false; 24 | 25 | private CitizenMessage lastCitizenMessage = null; 26 | public Message lastRedditMessage = null; 27 | 28 | private bool IsPaused 29 | { 30 | get 31 | { 32 | return SimulationManager.instance.SimulationPaused; 33 | } 34 | } 35 | 36 | public override void OnCreated(IChirper threading) 37 | { 38 | try 39 | { 40 | messageSound = Singleton.instance.m_NotificationSound; 41 | 42 | Configuration.Load(); 43 | if (Configuration.Subreddits.Count >= 1) 44 | { 45 | DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, string.Format("Going to show a new message from one of {0} subreddits every {1} seconds (AssociationMode = {2})", Configuration.Subreddits.Count, Configuration.TimerInSeconds, Configuration.AssociationMode)); 46 | 47 | foreach (string subreddit in Configuration.Subreddits) 48 | { 49 | if (!lastPostIds.ContainsKey(subreddit)) 50 | lastPostIds.Add(subreddit, new Queue()); 51 | } 52 | 53 | timer.AutoReset = true; 54 | timer.Elapsed += new ElapsedEventHandler((sender, e) => UpdateRedditPosts()); 55 | timer.Interval = Configuration.TimerInSeconds * 1000; 56 | timer.Start(); 57 | } 58 | else 59 | { 60 | DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, "No subreddits configured."); 61 | } 62 | } 63 | catch (Exception e) 64 | { 65 | DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Error, string.Format("[Reddit] {0}: {1}", e.GetType(), e.Message)); 66 | } 67 | } 68 | 69 | public override void OnReleased() 70 | { 71 | timer.Stop(); 72 | timer.Dispose(); 73 | 74 | ChirpPanel cp = ChirpPanel.instance; 75 | if (cp != null) 76 | cp.m_NotificationSound = messageSound; 77 | } 78 | 79 | private void UpdateRedditPosts() 80 | { 81 | if (IsPaused) 82 | return; 83 | 84 | // Possibly important messages 85 | if (CheckAnnouncement()) 86 | return; 87 | 88 | // Pick a subreddit at random 89 | string subreddit = Configuration.Subreddits[new System.Random().Next(Configuration.Subreddits.Count)]; 90 | 91 | try 92 | { 93 | // Remove posts that are no longer checked against; plus some for possible deletions 94 | Queue lastPostId = lastPostIds[subreddit]; 95 | while (lastPostId.Count > MAX_CACHED_REDDIT_POSTS_PER_SUBREDDIT) 96 | lastPostId.Dequeue(); 97 | 98 | // Fetch a number of latest posts 99 | IEnumerable newestPosts = TinyWeb.FindLastPosts(subreddit); 100 | foreach (RedditPost newestPost in newestPosts) 101 | { 102 | // Find the first one we haven't shown yet 103 | if (!lastPostId.Contains(newestPost.id) && !ShouldFilterPost(newestPost)) 104 | { 105 | var data = LookupOrRenameCitizenID(newestPost.author); 106 | 107 | AddMessage(new Message(data.Name, newestPost.subreddit, newestPost.title, data.ID, newestPost.id)); 108 | lastPostIds[subreddit].Enqueue(newestPost.id); 109 | return; 110 | } 111 | } 112 | } 113 | catch (Exception) 114 | { 115 | // DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, string.Format("[Reddit {0}] {1}: {2}", subreddit, e.GetType().ToString(), e.Message)); 116 | } 117 | } 118 | 119 | private CitizenInfo LookupOrRenameCitizenID(string name) 120 | { 121 | if (!string.IsNullOrEmpty(name)) 122 | { 123 | if (Configuration.AssociationMode == 1) 124 | { 125 | // Use any citizen's name. Not necessarily consistent in and of itself, in that the same person may show up as multiple actual CIMs. 126 | uint id = MessageManager.instance.GetRandomResidentID(); 127 | if (id != 0u) 128 | return new CitizenInfo { ID = id, Name = CitizenManager.instance.GetCitizenName(id) }; 129 | } 130 | else if (Configuration.AssociationMode == 2) 131 | { 132 | // Overwrite any CIM's name by their reddit username. 133 | // To be fair: this was the more interesting part. 134 | try 135 | { 136 | // use the shared lock for this 137 | object L = GetPrivateVariable(InstanceManager.instance, "m_lock"); 138 | do { } 139 | while (!Monitor.TryEnter(L, SimulationManager.SYNCHRONIZE_TIMEOUT)); 140 | try 141 | { 142 | // do we have someone called ? 143 | var dict = GetPrivateVariable>(InstanceManager.instance, "m_names"); 144 | 145 | foreach (var entry in dict) 146 | { 147 | if (name == entry.Value) 148 | return new CitizenInfo { ID = entry.Key.Citizen, Name = name }; 149 | } 150 | } 151 | finally 152 | { 153 | Monitor.Exit(L); 154 | } 155 | 156 | for (int i = 0; i < 500; ++i) 157 | { 158 | uint id = MessageManager.instance.GetRandomResidentID(); 159 | // What probably happens when we have no residents 160 | if (id == 0u) 161 | break; 162 | 163 | // doesn't exist 164 | if (CitizenManager.instance.m_citizens.m_buffer[id].m_flags == Citizen.Flags.None) 165 | continue; 166 | 167 | // has a name 168 | if ((CitizenManager.instance.m_citizens.m_buffer[id].m_flags & Citizen.Flags.CustomName) != Citizen.Flags.None) 169 | continue; 170 | 171 | // Found a random citizen without a name 172 | CitizenManager.instance.StartCoroutine(CitizenManager.instance.SetCitizenName(id, name)); 173 | return new CitizenInfo { ID = id, Name = name }; 174 | } 175 | // either we've tried 500 CIMs which all were named (bad luck or too few people), or 176 | // we have no people at all. 177 | } 178 | catch 179 | { 180 | // not sure if this would happen often. Who knows. 181 | // DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, string.Format("[Reddit] Failed to pick random citizen name for {0}", name)); 182 | } 183 | } 184 | } 185 | 186 | // Either we have no people, or we have some people but couldn't find anyone to use for our purposes, 187 | // or we don't want people renamed. 188 | return new CitizenInfo { Name = name }; 189 | } 190 | 191 | private bool CheckAnnouncement() 192 | { 193 | if (checkedAnnouncement) 194 | return false; 195 | 196 | checkedAnnouncement = true; 197 | 198 | try 199 | { 200 | string announcement = TinyWeb.GetAnnouncement(); 201 | if (announcement != null && announcement.Length > 2) 202 | { 203 | announcement = announcement.Trim(); 204 | 205 | if (Configuration.LastAnnouncement == announcement.GetHashCode()) 206 | return false; 207 | 208 | Configuration.LastAnnouncement = announcement.GetHashCode(); 209 | Configuration.SaveConfig(false); 210 | 211 | AddMessage(new Message("Reddit for Chirpy", "Update", announcement, 0, "2z87if")); 212 | return true; 213 | } 214 | } 215 | catch 216 | { 217 | } 218 | return false; 219 | } 220 | 221 | private void AddMessage(Message m) 222 | { 223 | if (IsPaused) 224 | return; 225 | 226 | MessageManager.instance.QueueMessage(m); 227 | } 228 | 229 | private bool ShouldFilterPost(RedditPost post) 230 | { 231 | // "Meta" posts. 232 | if (post.title.ToLower().Trim().StartsWith("[meta]")) 233 | { 234 | return true; 235 | } 236 | else 237 | { 238 | return false; 239 | } 240 | } 241 | 242 | public override void OnNewMessage(IChirperMessage message) 243 | { 244 | CitizenMessage cm = message as CitizenMessage; 245 | if (cm != null) 246 | { 247 | // DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, string.Format("Reddit | CitizenMessage {0} {1}", cm.m_messageID, cm.m_keyID)); 248 | if (ShouldFilter(cm.m_messageID)) 249 | { 250 | ChirpPanel.instance.m_NotificationSound = null; 251 | lastCitizenMessage = cm; 252 | } 253 | } 254 | else if (message is Message) 255 | { 256 | lastRedditMessage = message as Message; 257 | } 258 | } 259 | 260 | private void ClickRedditChirp(UIComponent component, UIMouseEventParameter eventParam) 261 | { 262 | string postId = component.stringUserData; 263 | if (!string.IsNullOrEmpty(postId) && UIMouseButtonExtensions.IsFlagSet(eventParam.buttons, UIMouseButton.Left)) 264 | { 265 | // Other mouse flags, like .Right seem to have no effect anyway. 266 | string url = string.Format("https://reddit.com/{0}", postId); 267 | 268 | switch (Configuration.ClickBehaviour) 269 | { 270 | case 0: 271 | // Open Steam Overlay 272 | Steam.ActivateGameOverlayToWebPage(url); 273 | break; 274 | 275 | case 1: 276 | // Copy to Clipboard 277 | Clipboard.text = url; 278 | break; 279 | 280 | case 2: 281 | // Open system browser 282 | Application.OpenURL(url); 283 | break; 284 | 285 | case 3: 286 | // Nothing 287 | break; 288 | } 289 | } 290 | } 291 | 292 | private bool ShouldFilter(string p) 293 | { 294 | if (Configuration.FilterMessages >= 2) 295 | return true; 296 | if (Configuration.FilterMessages == 0) 297 | return false; 298 | 299 | switch (p) 300 | { 301 | case LocaleID.CHIRP_ASSISTIVE_TECHNOLOGIES: 302 | case LocaleID.CHIRP_ATTRACTIVE_CITY: 303 | case LocaleID.CHIRP_CHEAP_FLOWERS: 304 | case LocaleID.CHIRP_DAYCARE_SERVICE: 305 | case LocaleID.CHIRP_HAPPY_PEOPLE: 306 | case LocaleID.CHIRP_HIGH_TECH_LEVEL: 307 | case LocaleID.CHIRP_LOW_CRIME: 308 | case LocaleID.CHIRP_NEW_FIRE_STATION: 309 | case LocaleID.CHIRP_NEW_HOSPITAL: 310 | case LocaleID.CHIRP_NEW_MAP_TILE: 311 | case LocaleID.CHIRP_NEW_MONUMENT: 312 | case LocaleID.CHIRP_NEW_PARK: 313 | case LocaleID.CHIRP_NEW_PLAZA: 314 | case LocaleID.CHIRP_NEW_POLICE_HQ: 315 | case LocaleID.CHIRP_NEW_TILE_PLACED: 316 | case LocaleID.CHIRP_NEW_UNIVERSITY: 317 | case LocaleID.CHIRP_NEW_WIND_OR_SOLAR_PLANT: 318 | case LocaleID.CHIRP_ORGANIC_FARMING: 319 | case LocaleID.CHIRP_POLICY: 320 | case LocaleID.CHIRP_PUBLIC_TRANSPORT_EFFICIENCY: 321 | case LocaleID.CHIRP_RANDOM: 322 | case LocaleID.CHIRP_STUDENT_LODGING: 323 | return true; 324 | 325 | default: 326 | return false; 327 | } 328 | } 329 | 330 | public override void OnUpdate() 331 | { 332 | if (lastCitizenMessage == null && lastRedditMessage == null) 333 | return; 334 | 335 | // This code is roughly based on the work by Juuso "Zuppi" Hietala. 336 | var container = ChirpPanel.instance.transform.FindChild("Chirps").FindChild("Clipper").FindChild("Container").gameObject.transform; 337 | for (int i = 0; i < container.childCount; ++i) 338 | { 339 | var elem = container.GetChild(i); 340 | var label = elem.GetComponentInChildren(); 341 | if (lastRedditMessage != null) 342 | { 343 | if (label.text.Equals(lastRedditMessage.GetText()) && string.IsNullOrEmpty(label.stringUserData)) 344 | { 345 | label.stringUserData = lastRedditMessage.GetPostID(); 346 | label.eventClick += ClickRedditChirp; 347 | 348 | lastRedditMessage = null; 349 | } 350 | } 351 | 352 | if (lastCitizenMessage != null) 353 | { 354 | if (label.text.Equals(lastCitizenMessage.GetText())) 355 | { 356 | ChirpPanel.instance.m_NotificationSound = messageSound; 357 | 358 | UITemplateManager.RemoveInstance("ChirpTemplate", elem.GetComponent()); 359 | MessageManager.instance.DeleteMessage(lastCitizenMessage); 360 | lastCitizenMessage = null; 361 | 362 | ChirpPanel.instance.Collapse(); 363 | } 364 | } 365 | } 366 | } 367 | 368 | /// 369 | /// Resolve private assembly fields 370 | /// 371 | /// 372 | /// 373 | /// 374 | /// 375 | private T GetPrivateVariable(object obj, string fieldName) 376 | { 377 | return (T)obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj); 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /RedditSkylines/SimpleJSON.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) 2011, The Outercurve Foundation. 4 | // 5 | // Licensed under the MIT License (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // http://www.opensource.org/licenses/mit-license.php 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) 17 | // https://github.com/facebook-csharp-sdk/simple-json 18 | //----------------------------------------------------------------------- 19 | 20 | // VERSION: 21 | 22 | // NOTE: uncomment the following line to make SimpleJson class internal. 23 | #define SIMPLE_JSON_INTERNAL 24 | 25 | // NOTE: uncomment the following line to make JsonArray and JsonObject class internal. 26 | #define SIMPLE_JSON_OBJARRAYINTERNAL 27 | 28 | // NOTE: uncomment the following line to enable dynamic support. 29 | //#define SIMPLE_JSON_DYNAMIC 30 | 31 | // NOTE: uncomment the following line to enable DataContract support. 32 | //#define SIMPLE_JSON_DATACONTRACT 33 | 34 | // NOTE: uncomment the following line to enable IReadOnlyCollection and IReadOnlyList support. 35 | //#define SIMPLE_JSON_READONLY_COLLECTIONS 36 | 37 | // NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). 38 | // define if you are using .net framework <= 3.0 or < WP7.5 39 | #define SIMPLE_JSON_NO_LINQ_EXPRESSION 40 | 41 | // NOTE: uncomment the following line if you are compiling under Window Metro style application/library. 42 | // usually already defined in properties 43 | //#define NETFX_CORE; 44 | 45 | // If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; 46 | 47 | // original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html 48 | 49 | #if NETFX_CORE 50 | #define SIMPLE_JSON_TYPEINFO 51 | #endif 52 | 53 | using System; 54 | using System.CodeDom.Compiler; 55 | using System.Collections; 56 | using System.Collections.Generic; 57 | 58 | #if !SIMPLE_JSON_NO_LINQ_EXPRESSION 59 | using System.Linq.Expressions; 60 | #endif 61 | 62 | using System.ComponentModel; 63 | using System.Diagnostics.CodeAnalysis; 64 | 65 | #if SIMPLE_JSON_DYNAMIC 66 | using System.Dynamic; 67 | #endif 68 | 69 | using System.Globalization; 70 | using System.Reflection; 71 | using System.Runtime.Serialization; 72 | using System.Text; 73 | using SimpleJson.Reflection; 74 | 75 | // ReSharper disable LoopCanBeConvertedToQuery 76 | // ReSharper disable RedundantExplicitArrayCreation 77 | // ReSharper disable SuggestUseVarKeywordEvident 78 | namespace SimpleJson 79 | { 80 | /// 81 | /// Represents the json array. 82 | /// 83 | [GeneratedCode("simple-json", "1.0.0")] 84 | [EditorBrowsable(EditorBrowsableState.Never)] 85 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 86 | #if SIMPLE_JSON_OBJARRAYINTERNAL 87 | internal 88 | #else 89 | public 90 | #endif 91 | class JsonArray : List 92 | { 93 | /// 94 | /// Initializes a new instance of the class. 95 | /// 96 | public JsonArray() 97 | { 98 | } 99 | 100 | /// 101 | /// Initializes a new instance of the class. 102 | /// 103 | /// The capacity of the json array. 104 | public JsonArray(int capacity) 105 | : base(capacity) 106 | { 107 | } 108 | 109 | /// 110 | /// The json representation of the array. 111 | /// 112 | /// The json representation of the array. 113 | public override string ToString() 114 | { 115 | return SimpleJson.SerializeObject(this) ?? string.Empty; 116 | } 117 | } 118 | 119 | /// 120 | /// Represents the json object. 121 | /// 122 | [GeneratedCode("simple-json", "1.0.0")] 123 | [EditorBrowsable(EditorBrowsableState.Never)] 124 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 125 | #if SIMPLE_JSON_OBJARRAYINTERNAL 126 | internal 127 | #else 128 | public 129 | #endif 130 | class JsonObject : 131 | #if SIMPLE_JSON_DYNAMIC 132 | DynamicObject, 133 | #endif 134 | IDictionary 135 | { 136 | /// 137 | /// The internal member dictionary. 138 | /// 139 | private readonly Dictionary _members; 140 | 141 | /// 142 | /// Initializes a new instance of . 143 | /// 144 | public JsonObject() 145 | { 146 | _members = new Dictionary(); 147 | } 148 | 149 | /// 150 | /// Initializes a new instance of . 151 | /// 152 | /// The implementation to use when comparing keys, or null to use the default for the type of the key. 153 | public JsonObject(IEqualityComparer comparer) 154 | { 155 | _members = new Dictionary(comparer); 156 | } 157 | 158 | /// 159 | /// Gets the at the specified index. 160 | /// 161 | /// 162 | public object this[int index] 163 | { 164 | get { return GetAtIndex(_members, index); } 165 | } 166 | 167 | internal static object GetAtIndex(IDictionary obj, int index) 168 | { 169 | if (obj == null) 170 | throw new ArgumentNullException("obj"); 171 | if (index >= obj.Count) 172 | throw new ArgumentOutOfRangeException("index"); 173 | int i = 0; 174 | foreach (KeyValuePair o in obj) 175 | if (i++ == index) return o.Value; 176 | return null; 177 | } 178 | 179 | /// 180 | /// Adds the specified key. 181 | /// 182 | /// The key. 183 | /// The value. 184 | public void Add(string key, object value) 185 | { 186 | _members.Add(key, value); 187 | } 188 | 189 | /// 190 | /// Determines whether the specified key contains key. 191 | /// 192 | /// The key. 193 | /// 194 | /// true if the specified key contains key; otherwise, false. 195 | /// 196 | public bool ContainsKey(string key) 197 | { 198 | return _members.ContainsKey(key); 199 | } 200 | 201 | /// 202 | /// Gets the keys. 203 | /// 204 | /// The keys. 205 | public ICollection Keys 206 | { 207 | get { return _members.Keys; } 208 | } 209 | 210 | /// 211 | /// Removes the specified key. 212 | /// 213 | /// The key. 214 | /// 215 | public bool Remove(string key) 216 | { 217 | return _members.Remove(key); 218 | } 219 | 220 | /// 221 | /// Tries the get value. 222 | /// 223 | /// The key. 224 | /// The value. 225 | /// 226 | public bool TryGetValue(string key, out object value) 227 | { 228 | return _members.TryGetValue(key, out value); 229 | } 230 | 231 | /// 232 | /// Gets the values. 233 | /// 234 | /// The values. 235 | public ICollection Values 236 | { 237 | get { return _members.Values; } 238 | } 239 | 240 | /// 241 | /// Gets or sets the with the specified key. 242 | /// 243 | /// 244 | public object this[string key] 245 | { 246 | get { return _members[key]; } 247 | set { _members[key] = value; } 248 | } 249 | 250 | /// 251 | /// Adds the specified item. 252 | /// 253 | /// The item. 254 | public void Add(KeyValuePair item) 255 | { 256 | _members.Add(item.Key, item.Value); 257 | } 258 | 259 | /// 260 | /// Clears this instance. 261 | /// 262 | public void Clear() 263 | { 264 | _members.Clear(); 265 | } 266 | 267 | /// 268 | /// Determines whether [contains] [the specified item]. 269 | /// 270 | /// The item. 271 | /// 272 | /// true if [contains] [the specified item]; otherwise, false. 273 | /// 274 | public bool Contains(KeyValuePair item) 275 | { 276 | return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; 277 | } 278 | 279 | /// 280 | /// Copies to. 281 | /// 282 | /// The array. 283 | /// Index of the array. 284 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 285 | { 286 | if (array == null) throw new ArgumentNullException("array"); 287 | int num = Count; 288 | foreach (KeyValuePair kvp in this) 289 | { 290 | array[arrayIndex++] = kvp; 291 | if (--num <= 0) 292 | return; 293 | } 294 | } 295 | 296 | /// 297 | /// Gets the count. 298 | /// 299 | /// The count. 300 | public int Count 301 | { 302 | get { return _members.Count; } 303 | } 304 | 305 | /// 306 | /// Gets a value indicating whether this instance is read only. 307 | /// 308 | /// 309 | /// true if this instance is read only; otherwise, false. 310 | /// 311 | public bool IsReadOnly 312 | { 313 | get { return false; } 314 | } 315 | 316 | /// 317 | /// Removes the specified item. 318 | /// 319 | /// The item. 320 | /// 321 | public bool Remove(KeyValuePair item) 322 | { 323 | return _members.Remove(item.Key); 324 | } 325 | 326 | /// 327 | /// Gets the enumerator. 328 | /// 329 | /// 330 | public IEnumerator> GetEnumerator() 331 | { 332 | return _members.GetEnumerator(); 333 | } 334 | 335 | /// 336 | /// Returns an enumerator that iterates through a collection. 337 | /// 338 | /// 339 | /// An object that can be used to iterate through the collection. 340 | /// 341 | IEnumerator IEnumerable.GetEnumerator() 342 | { 343 | return _members.GetEnumerator(); 344 | } 345 | 346 | /// 347 | /// Returns a json that represents the current . 348 | /// 349 | /// 350 | /// A json that represents the current . 351 | /// 352 | public override string ToString() 353 | { 354 | return SimpleJson.SerializeObject(this); 355 | } 356 | 357 | #if SIMPLE_JSON_DYNAMIC 358 | /// 359 | /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. 360 | /// 361 | /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. 362 | /// The result of the type conversion operation. 363 | /// 364 | /// Alwasy returns true. 365 | /// 366 | public override bool TryConvert(ConvertBinder binder, out object result) 367 | { 368 | // 369 | if (binder == null) 370 | throw new ArgumentNullException("binder"); 371 | // 372 | Type targetType = binder.Type; 373 | 374 | if ((targetType == typeof(IEnumerable)) || 375 | (targetType == typeof(IEnumerable>)) || 376 | (targetType == typeof(IDictionary)) || 377 | (targetType == typeof(IDictionary))) 378 | { 379 | result = this; 380 | return true; 381 | } 382 | 383 | return base.TryConvert(binder, out result); 384 | } 385 | 386 | /// 387 | /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. 388 | /// 389 | /// Provides information about the deletion. 390 | /// 391 | /// Alwasy returns true. 392 | /// 393 | public override bool TryDeleteMember(DeleteMemberBinder binder) 394 | { 395 | // 396 | if (binder == null) 397 | throw new ArgumentNullException("binder"); 398 | // 399 | return _members.Remove(binder.Name); 400 | } 401 | 402 | /// 403 | /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. 404 | /// 405 | /// Provides information about the operation. 406 | /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. 407 | /// The result of the index operation. 408 | /// 409 | /// Alwasy returns true. 410 | /// 411 | public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) 412 | { 413 | if (indexes == null) throw new ArgumentNullException("indexes"); 414 | if (indexes.Length == 1) 415 | { 416 | result = ((IDictionary)this)[(string)indexes[0]]; 417 | return true; 418 | } 419 | result = null; 420 | return true; 421 | } 422 | 423 | /// 424 | /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. 425 | /// 426 | /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. 427 | /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . 428 | /// 429 | /// Alwasy returns true. 430 | /// 431 | public override bool TryGetMember(GetMemberBinder binder, out object result) 432 | { 433 | object value; 434 | if (_members.TryGetValue(binder.Name, out value)) 435 | { 436 | result = value; 437 | return true; 438 | } 439 | result = null; 440 | return true; 441 | } 442 | 443 | /// 444 | /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. 445 | /// 446 | /// Provides information about the operation. 447 | /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. 448 | /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. 449 | /// 450 | /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. 451 | /// 452 | public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) 453 | { 454 | if (indexes == null) throw new ArgumentNullException("indexes"); 455 | if (indexes.Length == 1) 456 | { 457 | ((IDictionary)this)[(string)indexes[0]] = value; 458 | return true; 459 | } 460 | return base.TrySetIndex(binder, indexes, value); 461 | } 462 | 463 | /// 464 | /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. 465 | /// 466 | /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. 467 | /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". 468 | /// 469 | /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) 470 | /// 471 | public override bool TrySetMember(SetMemberBinder binder, object value) 472 | { 473 | // 474 | if (binder == null) 475 | throw new ArgumentNullException("binder"); 476 | // 477 | _members[binder.Name] = value; 478 | return true; 479 | } 480 | 481 | /// 482 | /// Returns the enumeration of all dynamic member names. 483 | /// 484 | /// 485 | /// A sequence that contains dynamic member names. 486 | /// 487 | public override IEnumerable GetDynamicMemberNames() 488 | { 489 | foreach (var key in Keys) 490 | yield return key; 491 | } 492 | #endif 493 | } 494 | } 495 | 496 | namespace SimpleJson 497 | { 498 | /// 499 | /// This class encodes and decodes JSON strings. 500 | /// Spec. details, see http://www.json.org/ 501 | /// 502 | /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). 503 | /// All numbers are parsed to doubles. 504 | /// 505 | [GeneratedCode("simple-json", "1.0.0")] 506 | #if SIMPLE_JSON_INTERNAL 507 | internal 508 | #else 509 | public 510 | #endif 511 | static class SimpleJson 512 | { 513 | private const int TOKEN_NONE = 0; 514 | private const int TOKEN_CURLY_OPEN = 1; 515 | private const int TOKEN_CURLY_CLOSE = 2; 516 | private const int TOKEN_SQUARED_OPEN = 3; 517 | private const int TOKEN_SQUARED_CLOSE = 4; 518 | private const int TOKEN_COLON = 5; 519 | private const int TOKEN_COMMA = 6; 520 | private const int TOKEN_STRING = 7; 521 | private const int TOKEN_NUMBER = 8; 522 | private const int TOKEN_TRUE = 9; 523 | private const int TOKEN_FALSE = 10; 524 | private const int TOKEN_NULL = 11; 525 | private const int BUILDER_CAPACITY = 2000; 526 | 527 | private static readonly char[] EscapeTable; 528 | private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; 529 | private static readonly string EscapeCharactersString = new string(EscapeCharacters); 530 | 531 | static SimpleJson() 532 | { 533 | EscapeTable = new char[93]; 534 | EscapeTable['"'] = '"'; 535 | EscapeTable['\\'] = '\\'; 536 | EscapeTable['\b'] = 'b'; 537 | EscapeTable['\f'] = 'f'; 538 | EscapeTable['\n'] = 'n'; 539 | EscapeTable['\r'] = 'r'; 540 | EscapeTable['\t'] = 't'; 541 | } 542 | 543 | /// 544 | /// Parses the string json into a value 545 | /// 546 | /// A JSON string. 547 | /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false 548 | public static object DeserializeObject(string json) 549 | { 550 | object obj; 551 | if (TryDeserializeObject(json, out obj)) 552 | return obj; 553 | throw new SerializationException("Invalid JSON string"); 554 | } 555 | 556 | /// 557 | /// Try parsing the json string into a value. 558 | /// 559 | /// 560 | /// A JSON string. 561 | /// 562 | /// 563 | /// The object. 564 | /// 565 | /// 566 | /// Returns true if successfull otherwise false. 567 | /// 568 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 569 | public static bool TryDeserializeObject(string json, out object obj) 570 | { 571 | bool success = true; 572 | if (json != null) 573 | { 574 | char[] charArray = json.ToCharArray(); 575 | int index = 0; 576 | obj = ParseValue(charArray, ref index, ref success); 577 | } 578 | else 579 | obj = null; 580 | 581 | return success; 582 | } 583 | 584 | public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) 585 | { 586 | object jsonObject = DeserializeObject(json); 587 | return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) 588 | ? jsonObject 589 | : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); 590 | } 591 | 592 | public static object DeserializeObject(string json, Type type) 593 | { 594 | return DeserializeObject(json, type, null); 595 | } 596 | 597 | public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) 598 | { 599 | return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); 600 | } 601 | 602 | public static T DeserializeObject(string json) 603 | { 604 | return (T)DeserializeObject(json, typeof(T), null); 605 | } 606 | 607 | /// 608 | /// Converts a IDictionary<string,object> / IList<object> object into a JSON string 609 | /// 610 | /// A IDictionary<string,object> / IList<object> 611 | /// Serializer strategy to use 612 | /// A JSON encoded string, or null if object 'json' is not serializable 613 | public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) 614 | { 615 | StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); 616 | bool success = SerializeValue(jsonSerializerStrategy, json, builder); 617 | return (success ? builder.ToString() : null); 618 | } 619 | 620 | public static string SerializeObject(object json) 621 | { 622 | return SerializeObject(json, CurrentJsonSerializerStrategy); 623 | } 624 | 625 | public static string EscapeToJavascriptString(string jsonString) 626 | { 627 | if (string.IsNullOrEmpty(jsonString)) 628 | return jsonString; 629 | 630 | StringBuilder sb = new StringBuilder(); 631 | char c; 632 | 633 | for (int i = 0; i < jsonString.Length; ) 634 | { 635 | c = jsonString[i++]; 636 | 637 | if (c == '\\') 638 | { 639 | int remainingLength = jsonString.Length - i; 640 | if (remainingLength >= 2) 641 | { 642 | char lookahead = jsonString[i]; 643 | if (lookahead == '\\') 644 | { 645 | sb.Append('\\'); 646 | ++i; 647 | } 648 | else if (lookahead == '"') 649 | { 650 | sb.Append("\""); 651 | ++i; 652 | } 653 | else if (lookahead == 't') 654 | { 655 | sb.Append('\t'); 656 | ++i; 657 | } 658 | else if (lookahead == 'b') 659 | { 660 | sb.Append('\b'); 661 | ++i; 662 | } 663 | else if (lookahead == 'n') 664 | { 665 | sb.Append('\n'); 666 | ++i; 667 | } 668 | else if (lookahead == 'r') 669 | { 670 | sb.Append('\r'); 671 | ++i; 672 | } 673 | } 674 | } 675 | else 676 | { 677 | sb.Append(c); 678 | } 679 | } 680 | return sb.ToString(); 681 | } 682 | 683 | private static IDictionary ParseObject(char[] json, ref int index, ref bool success) 684 | { 685 | IDictionary table = new JsonObject(); 686 | int token; 687 | 688 | // { 689 | NextToken(json, ref index); 690 | 691 | bool done = false; 692 | while (!done) 693 | { 694 | token = LookAhead(json, index); 695 | if (token == TOKEN_NONE) 696 | { 697 | success = false; 698 | return null; 699 | } 700 | else if (token == TOKEN_COMMA) 701 | NextToken(json, ref index); 702 | else if (token == TOKEN_CURLY_CLOSE) 703 | { 704 | NextToken(json, ref index); 705 | return table; 706 | } 707 | else 708 | { 709 | // name 710 | string name = ParseString(json, ref index, ref success); 711 | if (!success) 712 | { 713 | success = false; 714 | return null; 715 | } 716 | // : 717 | token = NextToken(json, ref index); 718 | if (token != TOKEN_COLON) 719 | { 720 | success = false; 721 | return null; 722 | } 723 | // value 724 | object value = ParseValue(json, ref index, ref success); 725 | if (!success) 726 | { 727 | success = false; 728 | return null; 729 | } 730 | table[name] = value; 731 | } 732 | } 733 | return table; 734 | } 735 | 736 | private static JsonArray ParseArray(char[] json, ref int index, ref bool success) 737 | { 738 | JsonArray array = new JsonArray(); 739 | 740 | // [ 741 | NextToken(json, ref index); 742 | 743 | bool done = false; 744 | while (!done) 745 | { 746 | int token = LookAhead(json, index); 747 | if (token == TOKEN_NONE) 748 | { 749 | success = false; 750 | return null; 751 | } 752 | else if (token == TOKEN_COMMA) 753 | NextToken(json, ref index); 754 | else if (token == TOKEN_SQUARED_CLOSE) 755 | { 756 | NextToken(json, ref index); 757 | break; 758 | } 759 | else 760 | { 761 | object value = ParseValue(json, ref index, ref success); 762 | if (!success) 763 | return null; 764 | array.Add(value); 765 | } 766 | } 767 | return array; 768 | } 769 | 770 | private static object ParseValue(char[] json, ref int index, ref bool success) 771 | { 772 | switch (LookAhead(json, index)) 773 | { 774 | case TOKEN_STRING: 775 | return ParseString(json, ref index, ref success); 776 | 777 | case TOKEN_NUMBER: 778 | return ParseNumber(json, ref index, ref success); 779 | 780 | case TOKEN_CURLY_OPEN: 781 | return ParseObject(json, ref index, ref success); 782 | 783 | case TOKEN_SQUARED_OPEN: 784 | return ParseArray(json, ref index, ref success); 785 | 786 | case TOKEN_TRUE: 787 | NextToken(json, ref index); 788 | return true; 789 | 790 | case TOKEN_FALSE: 791 | NextToken(json, ref index); 792 | return false; 793 | 794 | case TOKEN_NULL: 795 | NextToken(json, ref index); 796 | return null; 797 | 798 | case TOKEN_NONE: 799 | break; 800 | } 801 | success = false; 802 | return null; 803 | } 804 | 805 | private static string ParseString(char[] json, ref int index, ref bool success) 806 | { 807 | StringBuilder s = new StringBuilder(BUILDER_CAPACITY); 808 | char c; 809 | 810 | EatWhitespace(json, ref index); 811 | 812 | // " 813 | c = json[index++]; 814 | bool complete = false; 815 | while (!complete) 816 | { 817 | if (index == json.Length) 818 | break; 819 | 820 | c = json[index++]; 821 | if (c == '"') 822 | { 823 | complete = true; 824 | break; 825 | } 826 | else if (c == '\\') 827 | { 828 | if (index == json.Length) 829 | break; 830 | c = json[index++]; 831 | if (c == '"') 832 | s.Append('"'); 833 | else if (c == '\\') 834 | s.Append('\\'); 835 | else if (c == '/') 836 | s.Append('/'); 837 | else if (c == 'b') 838 | s.Append('\b'); 839 | else if (c == 'f') 840 | s.Append('\f'); 841 | else if (c == 'n') 842 | s.Append('\n'); 843 | else if (c == 'r') 844 | s.Append('\r'); 845 | else if (c == 't') 846 | s.Append('\t'); 847 | else if (c == 'u') 848 | { 849 | int remainingLength = json.Length - index; 850 | if (remainingLength >= 4) 851 | { 852 | // parse the 32 bit hex into an integer codepoint 853 | uint codePoint; 854 | if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) 855 | return ""; 856 | 857 | // convert the integer codepoint to a unicode char and add to string 858 | if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate 859 | { 860 | index += 4; // skip 4 chars 861 | remainingLength = json.Length - index; 862 | if (remainingLength >= 6) 863 | { 864 | uint lowCodePoint; 865 | if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) 866 | { 867 | if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate 868 | { 869 | s.Append((char)codePoint); 870 | s.Append((char)lowCodePoint); 871 | index += 6; // skip 6 chars 872 | continue; 873 | } 874 | } 875 | } 876 | success = false; // invalid surrogate pair 877 | return ""; 878 | } 879 | s.Append(ConvertFromUtf32((int)codePoint)); 880 | // skip 4 chars 881 | index += 4; 882 | } 883 | else 884 | break; 885 | } 886 | } 887 | else 888 | s.Append(c); 889 | } 890 | if (!complete) 891 | { 892 | success = false; 893 | return null; 894 | } 895 | return s.ToString(); 896 | } 897 | 898 | private static string ConvertFromUtf32(int utf32) 899 | { 900 | // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm 901 | if (utf32 < 0 || utf32 > 0x10FFFF) 902 | throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); 903 | if (0xD800 <= utf32 && utf32 <= 0xDFFF) 904 | throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); 905 | if (utf32 < 0x10000) 906 | return new string((char)utf32, 1); 907 | utf32 -= 0x10000; 908 | return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); 909 | } 910 | 911 | private static object ParseNumber(char[] json, ref int index, ref bool success) 912 | { 913 | EatWhitespace(json, ref index); 914 | int lastIndex = GetLastIndexOfNumber(json, index); 915 | int charLength = (lastIndex - index) + 1; 916 | object returnNumber; 917 | string str = new string(json, index, charLength); 918 | if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) 919 | { 920 | double number; 921 | success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); 922 | returnNumber = number; 923 | } 924 | else 925 | { 926 | long number; 927 | success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); 928 | returnNumber = number; 929 | } 930 | index = lastIndex + 1; 931 | return returnNumber; 932 | } 933 | 934 | private static int GetLastIndexOfNumber(char[] json, int index) 935 | { 936 | int lastIndex; 937 | for (lastIndex = index; lastIndex < json.Length; lastIndex++) 938 | if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; 939 | return lastIndex - 1; 940 | } 941 | 942 | private static void EatWhitespace(char[] json, ref int index) 943 | { 944 | for (; index < json.Length; index++) 945 | if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; 946 | } 947 | 948 | private static int LookAhead(char[] json, int index) 949 | { 950 | int saveIndex = index; 951 | return NextToken(json, ref saveIndex); 952 | } 953 | 954 | [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 955 | private static int NextToken(char[] json, ref int index) 956 | { 957 | EatWhitespace(json, ref index); 958 | if (index == json.Length) 959 | return TOKEN_NONE; 960 | char c = json[index]; 961 | index++; 962 | switch (c) 963 | { 964 | case '{': 965 | return TOKEN_CURLY_OPEN; 966 | 967 | case '}': 968 | return TOKEN_CURLY_CLOSE; 969 | 970 | case '[': 971 | return TOKEN_SQUARED_OPEN; 972 | 973 | case ']': 974 | return TOKEN_SQUARED_CLOSE; 975 | 976 | case ',': 977 | return TOKEN_COMMA; 978 | 979 | case '"': 980 | return TOKEN_STRING; 981 | 982 | case '0': 983 | case '1': 984 | case '2': 985 | case '3': 986 | case '4': 987 | case '5': 988 | case '6': 989 | case '7': 990 | case '8': 991 | case '9': 992 | case '-': 993 | return TOKEN_NUMBER; 994 | 995 | case ':': 996 | return TOKEN_COLON; 997 | } 998 | index--; 999 | int remainingLength = json.Length - index; 1000 | // false 1001 | if (remainingLength >= 5) 1002 | { 1003 | if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') 1004 | { 1005 | index += 5; 1006 | return TOKEN_FALSE; 1007 | } 1008 | } 1009 | // true 1010 | if (remainingLength >= 4) 1011 | { 1012 | if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') 1013 | { 1014 | index += 4; 1015 | return TOKEN_TRUE; 1016 | } 1017 | } 1018 | // null 1019 | if (remainingLength >= 4) 1020 | { 1021 | if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') 1022 | { 1023 | index += 4; 1024 | return TOKEN_NULL; 1025 | } 1026 | } 1027 | return TOKEN_NONE; 1028 | } 1029 | 1030 | private static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) 1031 | { 1032 | bool success = true; 1033 | string stringValue = value as string; 1034 | if (stringValue != null) 1035 | success = SerializeString(stringValue, builder); 1036 | else 1037 | { 1038 | IDictionary dict = value as IDictionary; 1039 | if (dict != null) 1040 | { 1041 | success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); 1042 | } 1043 | else 1044 | { 1045 | IDictionary stringDictionary = value as IDictionary; 1046 | if (stringDictionary != null) 1047 | { 1048 | success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); 1049 | } 1050 | else 1051 | { 1052 | IEnumerable enumerableValue = value as IEnumerable; 1053 | if (enumerableValue != null) 1054 | success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); 1055 | else if (IsNumeric(value)) 1056 | success = SerializeNumber(value, builder); 1057 | else if (value is bool) 1058 | builder.Append((bool)value ? "true" : "false"); 1059 | else if (value == null) 1060 | builder.Append("null"); 1061 | else 1062 | { 1063 | object serializedObject; 1064 | success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); 1065 | if (success) 1066 | SerializeValue(jsonSerializerStrategy, serializedObject, builder); 1067 | } 1068 | } 1069 | } 1070 | } 1071 | return success; 1072 | } 1073 | 1074 | private static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) 1075 | { 1076 | builder.Append("{"); 1077 | IEnumerator ke = keys.GetEnumerator(); 1078 | IEnumerator ve = values.GetEnumerator(); 1079 | bool first = true; 1080 | while (ke.MoveNext() && ve.MoveNext()) 1081 | { 1082 | object key = ke.Current; 1083 | object value = ve.Current; 1084 | if (!first) 1085 | builder.Append(","); 1086 | string stringKey = key as string; 1087 | if (stringKey != null) 1088 | SerializeString(stringKey, builder); 1089 | else 1090 | if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; 1091 | builder.Append(":"); 1092 | if (!SerializeValue(jsonSerializerStrategy, value, builder)) 1093 | return false; 1094 | first = false; 1095 | } 1096 | builder.Append("}"); 1097 | return true; 1098 | } 1099 | 1100 | private static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) 1101 | { 1102 | builder.Append("["); 1103 | bool first = true; 1104 | foreach (object value in anArray) 1105 | { 1106 | if (!first) 1107 | builder.Append(","); 1108 | if (!SerializeValue(jsonSerializerStrategy, value, builder)) 1109 | return false; 1110 | first = false; 1111 | } 1112 | builder.Append("]"); 1113 | return true; 1114 | } 1115 | 1116 | private static bool SerializeString(string aString, StringBuilder builder) 1117 | { 1118 | // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) 1119 | if (aString.IndexOfAny(EscapeCharacters) == -1) 1120 | { 1121 | builder.Append('"'); 1122 | builder.Append(aString); 1123 | builder.Append('"'); 1124 | 1125 | return true; 1126 | } 1127 | 1128 | builder.Append('"'); 1129 | int safeCharacterCount = 0; 1130 | char[] charArray = aString.ToCharArray(); 1131 | 1132 | for (int i = 0; i < charArray.Length; i++) 1133 | { 1134 | char c = charArray[i]; 1135 | 1136 | // Non ascii characters are fine, buffer them up and send them to the builder 1137 | // in larger chunks if possible. The escape table is a 1:1 translation table 1138 | // with \0 [default(char)] denoting a safe character. 1139 | if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) 1140 | { 1141 | safeCharacterCount++; 1142 | } 1143 | else 1144 | { 1145 | if (safeCharacterCount > 0) 1146 | { 1147 | builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); 1148 | safeCharacterCount = 0; 1149 | } 1150 | 1151 | builder.Append('\\'); 1152 | builder.Append(EscapeTable[c]); 1153 | } 1154 | } 1155 | 1156 | if (safeCharacterCount > 0) 1157 | { 1158 | builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); 1159 | } 1160 | 1161 | builder.Append('"'); 1162 | return true; 1163 | } 1164 | 1165 | private static bool SerializeNumber(object number, StringBuilder builder) 1166 | { 1167 | if (number is long) 1168 | builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); 1169 | else if (number is ulong) 1170 | builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); 1171 | else if (number is int) 1172 | builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); 1173 | else if (number is uint) 1174 | builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); 1175 | else if (number is decimal) 1176 | builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); 1177 | else if (number is float) 1178 | builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); 1179 | else 1180 | builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); 1181 | return true; 1182 | } 1183 | 1184 | /// 1185 | /// Determines if a given object is numeric in any way 1186 | /// (can be integer, double, null, etc). 1187 | /// 1188 | private static bool IsNumeric(object value) 1189 | { 1190 | if (value is sbyte) return true; 1191 | if (value is byte) return true; 1192 | if (value is short) return true; 1193 | if (value is ushort) return true; 1194 | if (value is int) return true; 1195 | if (value is uint) return true; 1196 | if (value is long) return true; 1197 | if (value is ulong) return true; 1198 | if (value is float) return true; 1199 | if (value is double) return true; 1200 | if (value is decimal) return true; 1201 | return false; 1202 | } 1203 | 1204 | private static IJsonSerializerStrategy _currentJsonSerializerStrategy; 1205 | 1206 | public static IJsonSerializerStrategy CurrentJsonSerializerStrategy 1207 | { 1208 | get 1209 | { 1210 | return _currentJsonSerializerStrategy ?? 1211 | (_currentJsonSerializerStrategy = 1212 | #if SIMPLE_JSON_DATACONTRACT 1213 | DataContractJsonSerializerStrategy 1214 | #else 1215 | PocoJsonSerializerStrategy 1216 | #endif 1217 | ); 1218 | } 1219 | set 1220 | { 1221 | _currentJsonSerializerStrategy = value; 1222 | } 1223 | } 1224 | 1225 | private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; 1226 | 1227 | [EditorBrowsable(EditorBrowsableState.Advanced)] 1228 | public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy 1229 | { 1230 | get 1231 | { 1232 | return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); 1233 | } 1234 | } 1235 | 1236 | #if SIMPLE_JSON_DATACONTRACT 1237 | 1238 | private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; 1239 | [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] 1240 | public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy 1241 | { 1242 | get 1243 | { 1244 | return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); 1245 | } 1246 | } 1247 | 1248 | #endif 1249 | } 1250 | 1251 | [GeneratedCode("simple-json", "1.0.0")] 1252 | #if SIMPLE_JSON_INTERNAL 1253 | internal 1254 | #else 1255 | public 1256 | #endif 1257 | interface IJsonSerializerStrategy 1258 | { 1259 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 1260 | bool TrySerializeNonPrimitiveObject(object input, out object output); 1261 | 1262 | object DeserializeObject(object value, Type type); 1263 | } 1264 | 1265 | [GeneratedCode("simple-json", "1.0.0")] 1266 | #if SIMPLE_JSON_INTERNAL 1267 | internal 1268 | #else 1269 | public 1270 | #endif 1271 | class PocoJsonSerializerStrategy : IJsonSerializerStrategy 1272 | { 1273 | internal IDictionary ConstructorCache; 1274 | internal IDictionary> GetCache; 1275 | internal IDictionary>> SetCache; 1276 | 1277 | internal static readonly Type[] EmptyTypes = new Type[0]; 1278 | internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; 1279 | 1280 | private static readonly string[] Iso8601Format = new string[] 1281 | { 1282 | @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", 1283 | @"yyyy-MM-dd\THH:mm:ss\Z", 1284 | @"yyyy-MM-dd\THH:mm:ssK" 1285 | }; 1286 | 1287 | public PocoJsonSerializerStrategy() 1288 | { 1289 | ConstructorCache = new ReflectionUtils.ThreadSafeDictionary(ContructorDelegateFactory); 1290 | GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); 1291 | SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); 1292 | } 1293 | 1294 | protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) 1295 | { 1296 | return clrPropertyName; 1297 | } 1298 | 1299 | internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) 1300 | { 1301 | return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); 1302 | } 1303 | 1304 | internal virtual IDictionary GetterValueFactory(Type type) 1305 | { 1306 | IDictionary result = new Dictionary(); 1307 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 1308 | { 1309 | if (propertyInfo.CanRead) 1310 | { 1311 | MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); 1312 | if (getMethod.IsStatic || !getMethod.IsPublic) 1313 | continue; 1314 | result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); 1315 | } 1316 | } 1317 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 1318 | { 1319 | if (fieldInfo.IsStatic || !fieldInfo.IsPublic) 1320 | continue; 1321 | result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); 1322 | } 1323 | return result; 1324 | } 1325 | 1326 | internal virtual IDictionary> SetterValueFactory(Type type) 1327 | { 1328 | IDictionary> result = new Dictionary>(); 1329 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 1330 | { 1331 | if (propertyInfo.CanWrite) 1332 | { 1333 | MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); 1334 | if (setMethod.IsStatic || !setMethod.IsPublic) 1335 | continue; 1336 | result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); 1337 | } 1338 | } 1339 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 1340 | { 1341 | if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) 1342 | continue; 1343 | result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); 1344 | } 1345 | return result; 1346 | } 1347 | 1348 | public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) 1349 | { 1350 | return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); 1351 | } 1352 | 1353 | [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 1354 | public virtual object DeserializeObject(object value, Type type) 1355 | { 1356 | if (type == null) throw new ArgumentNullException("type"); 1357 | string str = value as string; 1358 | 1359 | if (type == typeof(Guid) && string.IsNullOrEmpty(str)) 1360 | return default(Guid); 1361 | 1362 | if (value == null) 1363 | return null; 1364 | 1365 | object obj = null; 1366 | 1367 | if (str != null) 1368 | { 1369 | if (str.Length != 0) // We know it can't be null now. 1370 | { 1371 | if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) 1372 | return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); 1373 | if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) 1374 | return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); 1375 | if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) 1376 | return new Guid(str); 1377 | if (type == typeof(Uri)) 1378 | { 1379 | bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); 1380 | 1381 | Uri result; 1382 | if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) 1383 | return result; 1384 | 1385 | return null; 1386 | } 1387 | 1388 | if (type == typeof(string)) 1389 | return str; 1390 | 1391 | return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); 1392 | } 1393 | else 1394 | { 1395 | if (type == typeof(Guid)) 1396 | obj = default(Guid); 1397 | else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) 1398 | obj = null; 1399 | else 1400 | obj = str; 1401 | } 1402 | // Empty string case 1403 | if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) 1404 | return str; 1405 | } 1406 | else if (value is bool) 1407 | return value; 1408 | 1409 | bool valueIsLong = value is long; 1410 | bool valueIsDouble = value is double; 1411 | if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) 1412 | return value; 1413 | if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) 1414 | { 1415 | obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) 1416 | ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) 1417 | : value; 1418 | } 1419 | else 1420 | { 1421 | IDictionary objects = value as IDictionary; 1422 | if (objects != null) 1423 | { 1424 | IDictionary jsonObject = objects; 1425 | 1426 | if (ReflectionUtils.IsTypeDictionary(type)) 1427 | { 1428 | // if dictionary then 1429 | Type[] types = ReflectionUtils.GetGenericTypeArguments(type); 1430 | Type keyType = types[0]; 1431 | Type valueType = types[1]; 1432 | 1433 | Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); 1434 | 1435 | IDictionary dict = (IDictionary)ConstructorCache[genericType](); 1436 | 1437 | foreach (KeyValuePair kvp in jsonObject) 1438 | dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); 1439 | 1440 | obj = dict; 1441 | } 1442 | else 1443 | { 1444 | if (type == typeof(object)) 1445 | obj = value; 1446 | else 1447 | { 1448 | obj = ConstructorCache[type](); 1449 | foreach (KeyValuePair> setter in SetCache[type]) 1450 | { 1451 | object jsonValue; 1452 | if (jsonObject.TryGetValue(setter.Key, out jsonValue)) 1453 | { 1454 | jsonValue = DeserializeObject(jsonValue, setter.Value.Key); 1455 | setter.Value.Value(obj, jsonValue); 1456 | } 1457 | } 1458 | } 1459 | } 1460 | } 1461 | else 1462 | { 1463 | IList valueAsList = value as IList; 1464 | if (valueAsList != null) 1465 | { 1466 | IList jsonObject = valueAsList; 1467 | IList list = null; 1468 | 1469 | if (type.IsArray) 1470 | { 1471 | list = (IList)ConstructorCache[type](jsonObject.Count); 1472 | int i = 0; 1473 | foreach (object o in jsonObject) 1474 | list[i++] = DeserializeObject(o, type.GetElementType()); 1475 | } 1476 | else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) 1477 | { 1478 | Type innerType = ReflectionUtils.GetGenericListElementType(type); 1479 | list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); 1480 | foreach (object o in jsonObject) 1481 | list.Add(DeserializeObject(o, innerType)); 1482 | } 1483 | obj = list; 1484 | } 1485 | } 1486 | return obj; 1487 | } 1488 | if (ReflectionUtils.IsNullableType(type)) 1489 | return ReflectionUtils.ToNullableType(obj, type); 1490 | return obj; 1491 | } 1492 | 1493 | protected virtual object SerializeEnum(Enum p) 1494 | { 1495 | return Convert.ToDouble(p, CultureInfo.InvariantCulture); 1496 | } 1497 | 1498 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 1499 | protected virtual bool TrySerializeKnownTypes(object input, out object output) 1500 | { 1501 | bool returnValue = true; 1502 | if (input is DateTime) 1503 | output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); 1504 | else if (input is DateTimeOffset) 1505 | output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); 1506 | else if (input is Guid) 1507 | output = ((Guid)input).ToString("D"); 1508 | else if (input is Uri) 1509 | output = input.ToString(); 1510 | else 1511 | { 1512 | Enum inputEnum = input as Enum; 1513 | if (inputEnum != null) 1514 | output = SerializeEnum(inputEnum); 1515 | else 1516 | { 1517 | returnValue = false; 1518 | output = null; 1519 | } 1520 | } 1521 | return returnValue; 1522 | } 1523 | 1524 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 1525 | protected virtual bool TrySerializeUnknownTypes(object input, out object output) 1526 | { 1527 | if (input == null) throw new ArgumentNullException("input"); 1528 | output = null; 1529 | Type type = input.GetType(); 1530 | if (type.FullName == null) 1531 | return false; 1532 | IDictionary obj = new JsonObject(); 1533 | IDictionary getters = GetCache[type]; 1534 | foreach (KeyValuePair getter in getters) 1535 | { 1536 | if (getter.Value != null) 1537 | obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); 1538 | } 1539 | output = obj; 1540 | return true; 1541 | } 1542 | } 1543 | 1544 | #if SIMPLE_JSON_DATACONTRACT 1545 | [GeneratedCode("simple-json", "1.0.0")] 1546 | #if SIMPLE_JSON_INTERNAL 1547 | internal 1548 | #else 1549 | public 1550 | #endif 1551 | class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy 1552 | { 1553 | public DataContractJsonSerializerStrategy() 1554 | { 1555 | GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); 1556 | SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); 1557 | } 1558 | 1559 | internal override IDictionary GetterValueFactory(Type type) 1560 | { 1561 | bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; 1562 | if (!hasDataContract) 1563 | return base.GetterValueFactory(type); 1564 | string jsonKey; 1565 | IDictionary result = new Dictionary(); 1566 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 1567 | { 1568 | if (propertyInfo.CanRead) 1569 | { 1570 | MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); 1571 | if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) 1572 | result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); 1573 | } 1574 | } 1575 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 1576 | { 1577 | if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) 1578 | result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); 1579 | } 1580 | return result; 1581 | } 1582 | 1583 | internal override IDictionary> SetterValueFactory(Type type) 1584 | { 1585 | bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; 1586 | if (!hasDataContract) 1587 | return base.SetterValueFactory(type); 1588 | string jsonKey; 1589 | IDictionary> result = new Dictionary>(); 1590 | foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) 1591 | { 1592 | if (propertyInfo.CanWrite) 1593 | { 1594 | MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); 1595 | if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) 1596 | result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); 1597 | } 1598 | } 1599 | foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) 1600 | { 1601 | if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) 1602 | result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); 1603 | } 1604 | // todo implement sorting for DATACONTRACT. 1605 | return result; 1606 | } 1607 | 1608 | private static bool CanAdd(MemberInfo info, out string jsonKey) 1609 | { 1610 | jsonKey = null; 1611 | if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) 1612 | return false; 1613 | DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); 1614 | if (dataMemberAttribute == null) 1615 | return false; 1616 | jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; 1617 | return true; 1618 | } 1619 | } 1620 | 1621 | #endif 1622 | 1623 | namespace Reflection 1624 | { 1625 | // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules 1626 | // that might be in place in the target project. 1627 | [GeneratedCode("reflection-utils", "1.0.0")] 1628 | #if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC 1629 | public 1630 | #else 1631 | internal 1632 | #endif 1633 | class ReflectionUtils 1634 | { 1635 | private static readonly object[] EmptyObjects = new object[] { }; 1636 | 1637 | public delegate object GetDelegate(object source); 1638 | 1639 | public delegate void SetDelegate(object source, object value); 1640 | 1641 | public delegate object ConstructorDelegate(params object[] args); 1642 | 1643 | public delegate TValue ThreadSafeDictionaryValueFactory(TKey key); 1644 | 1645 | #if SIMPLE_JSON_TYPEINFO 1646 | public static TypeInfo GetTypeInfo(Type type) 1647 | { 1648 | return type.GetTypeInfo(); 1649 | } 1650 | #else 1651 | 1652 | public static Type GetTypeInfo(Type type) 1653 | { 1654 | return type; 1655 | } 1656 | 1657 | #endif 1658 | 1659 | public static Attribute GetAttribute(MemberInfo info, Type type) 1660 | { 1661 | #if SIMPLE_JSON_TYPEINFO 1662 | if (info == null || type == null || !info.IsDefined(type)) 1663 | return null; 1664 | return info.GetCustomAttribute(type); 1665 | #else 1666 | if (info == null || type == null || !Attribute.IsDefined(info, type)) 1667 | return null; 1668 | return Attribute.GetCustomAttribute(info, type); 1669 | #endif 1670 | } 1671 | 1672 | public static Type GetGenericListElementType(Type type) 1673 | { 1674 | IEnumerable interfaces; 1675 | #if SIMPLE_JSON_TYPEINFO 1676 | interfaces = type.GetTypeInfo().ImplementedInterfaces; 1677 | #else 1678 | interfaces = type.GetInterfaces(); 1679 | #endif 1680 | foreach (Type implementedInterface in interfaces) 1681 | { 1682 | if (IsTypeGeneric(implementedInterface) && 1683 | implementedInterface.GetGenericTypeDefinition() == typeof(IList<>)) 1684 | { 1685 | return GetGenericTypeArguments(implementedInterface)[0]; 1686 | } 1687 | } 1688 | return GetGenericTypeArguments(type)[0]; 1689 | } 1690 | 1691 | public static Attribute GetAttribute(Type objectType, Type attributeType) 1692 | { 1693 | #if SIMPLE_JSON_TYPEINFO 1694 | if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) 1695 | return null; 1696 | return objectType.GetTypeInfo().GetCustomAttribute(attributeType); 1697 | #else 1698 | if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) 1699 | return null; 1700 | return Attribute.GetCustomAttribute(objectType, attributeType); 1701 | #endif 1702 | } 1703 | 1704 | public static Type[] GetGenericTypeArguments(Type type) 1705 | { 1706 | #if SIMPLE_JSON_TYPEINFO 1707 | return type.GetTypeInfo().GenericTypeArguments; 1708 | #else 1709 | return type.GetGenericArguments(); 1710 | #endif 1711 | } 1712 | 1713 | public static bool IsTypeGeneric(Type type) 1714 | { 1715 | return GetTypeInfo(type).IsGenericType; 1716 | } 1717 | 1718 | public static bool IsTypeGenericeCollectionInterface(Type type) 1719 | { 1720 | if (!IsTypeGeneric(type)) 1721 | return false; 1722 | 1723 | Type genericDefinition = type.GetGenericTypeDefinition(); 1724 | 1725 | return (genericDefinition == typeof(IList<>) 1726 | || genericDefinition == typeof(ICollection<>) 1727 | || genericDefinition == typeof(IEnumerable<>) 1728 | #if SIMPLE_JSON_READONLY_COLLECTIONS 1729 | || genericDefinition == typeof(IReadOnlyCollection<>) 1730 | || genericDefinition == typeof(IReadOnlyList<>) 1731 | #endif 1732 | ); 1733 | } 1734 | 1735 | public static bool IsAssignableFrom(Type type1, Type type2) 1736 | { 1737 | return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); 1738 | } 1739 | 1740 | public static bool IsTypeDictionary(Type type) 1741 | { 1742 | #if SIMPLE_JSON_TYPEINFO 1743 | if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) 1744 | return true; 1745 | #else 1746 | if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) 1747 | return true; 1748 | #endif 1749 | if (!GetTypeInfo(type).IsGenericType) 1750 | return false; 1751 | 1752 | Type genericDefinition = type.GetGenericTypeDefinition(); 1753 | return genericDefinition == typeof(IDictionary<,>); 1754 | } 1755 | 1756 | public static bool IsNullableType(Type type) 1757 | { 1758 | return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 1759 | } 1760 | 1761 | public static object ToNullableType(object obj, Type nullableType) 1762 | { 1763 | return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); 1764 | } 1765 | 1766 | public static bool IsValueType(Type type) 1767 | { 1768 | return GetTypeInfo(type).IsValueType; 1769 | } 1770 | 1771 | public static IEnumerable GetConstructors(Type type) 1772 | { 1773 | #if SIMPLE_JSON_TYPEINFO 1774 | return type.GetTypeInfo().DeclaredConstructors; 1775 | #else 1776 | return type.GetConstructors(); 1777 | #endif 1778 | } 1779 | 1780 | public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) 1781 | { 1782 | IEnumerable constructorInfos = GetConstructors(type); 1783 | int i; 1784 | bool matches; 1785 | foreach (ConstructorInfo constructorInfo in constructorInfos) 1786 | { 1787 | ParameterInfo[] parameters = constructorInfo.GetParameters(); 1788 | if (argsType.Length != parameters.Length) 1789 | continue; 1790 | 1791 | i = 0; 1792 | matches = true; 1793 | foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) 1794 | { 1795 | if (parameterInfo.ParameterType != argsType[i]) 1796 | { 1797 | matches = false; 1798 | break; 1799 | } 1800 | } 1801 | 1802 | if (matches) 1803 | return constructorInfo; 1804 | } 1805 | 1806 | return null; 1807 | } 1808 | 1809 | public static IEnumerable GetProperties(Type type) 1810 | { 1811 | #if SIMPLE_JSON_TYPEINFO 1812 | return type.GetRuntimeProperties(); 1813 | #else 1814 | return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); 1815 | #endif 1816 | } 1817 | 1818 | public static IEnumerable GetFields(Type type) 1819 | { 1820 | #if SIMPLE_JSON_TYPEINFO 1821 | return type.GetRuntimeFields(); 1822 | #else 1823 | return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); 1824 | #endif 1825 | } 1826 | 1827 | public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) 1828 | { 1829 | #if SIMPLE_JSON_TYPEINFO 1830 | return propertyInfo.GetMethod; 1831 | #else 1832 | return propertyInfo.GetGetMethod(true); 1833 | #endif 1834 | } 1835 | 1836 | public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) 1837 | { 1838 | #if SIMPLE_JSON_TYPEINFO 1839 | return propertyInfo.SetMethod; 1840 | #else 1841 | return propertyInfo.GetSetMethod(true); 1842 | #endif 1843 | } 1844 | 1845 | public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo) 1846 | { 1847 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1848 | return GetConstructorByReflection(constructorInfo); 1849 | #else 1850 | return GetConstructorByExpression(constructorInfo); 1851 | #endif 1852 | } 1853 | 1854 | public static ConstructorDelegate GetContructor(Type type, params Type[] argsType) 1855 | { 1856 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1857 | return GetConstructorByReflection(type, argsType); 1858 | #else 1859 | return GetConstructorByExpression(type, argsType); 1860 | #endif 1861 | } 1862 | 1863 | public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) 1864 | { 1865 | return delegate(object[] args) { return constructorInfo.Invoke(args); }; 1866 | } 1867 | 1868 | public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) 1869 | { 1870 | ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); 1871 | return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); 1872 | } 1873 | 1874 | #if !SIMPLE_JSON_NO_LINQ_EXPRESSION 1875 | 1876 | public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) 1877 | { 1878 | ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); 1879 | ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); 1880 | Expression[] argsExp = new Expression[paramsInfo.Length]; 1881 | for (int i = 0; i < paramsInfo.Length; i++) 1882 | { 1883 | Expression index = Expression.Constant(i); 1884 | Type paramType = paramsInfo[i].ParameterType; 1885 | Expression paramAccessorExp = Expression.ArrayIndex(param, index); 1886 | Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); 1887 | argsExp[i] = paramCastExp; 1888 | } 1889 | NewExpression newExp = Expression.New(constructorInfo, argsExp); 1890 | Expression> lambda = Expression.Lambda>(newExp, param); 1891 | Func compiledLambda = lambda.Compile(); 1892 | return delegate(object[] args) { return compiledLambda(args); }; 1893 | } 1894 | 1895 | public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) 1896 | { 1897 | ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); 1898 | return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); 1899 | } 1900 | 1901 | #endif 1902 | 1903 | public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) 1904 | { 1905 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1906 | return GetGetMethodByReflection(propertyInfo); 1907 | #else 1908 | return GetGetMethodByExpression(propertyInfo); 1909 | #endif 1910 | } 1911 | 1912 | public static GetDelegate GetGetMethod(FieldInfo fieldInfo) 1913 | { 1914 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1915 | return GetGetMethodByReflection(fieldInfo); 1916 | #else 1917 | return GetGetMethodByExpression(fieldInfo); 1918 | #endif 1919 | } 1920 | 1921 | public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) 1922 | { 1923 | MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); 1924 | return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; 1925 | } 1926 | 1927 | public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) 1928 | { 1929 | return delegate(object source) { return fieldInfo.GetValue(source); }; 1930 | } 1931 | 1932 | #if !SIMPLE_JSON_NO_LINQ_EXPRESSION 1933 | 1934 | public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) 1935 | { 1936 | MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); 1937 | ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); 1938 | UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); 1939 | Func compiled = Expression.Lambda>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); 1940 | return delegate(object source) { return compiled(source); }; 1941 | } 1942 | 1943 | public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) 1944 | { 1945 | ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); 1946 | MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); 1947 | GetDelegate compiled = Expression.Lambda(Expression.Convert(member, typeof(object)), instance).Compile(); 1948 | return delegate(object source) { return compiled(source); }; 1949 | } 1950 | 1951 | #endif 1952 | 1953 | public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) 1954 | { 1955 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1956 | return GetSetMethodByReflection(propertyInfo); 1957 | #else 1958 | return GetSetMethodByExpression(propertyInfo); 1959 | #endif 1960 | } 1961 | 1962 | public static SetDelegate GetSetMethod(FieldInfo fieldInfo) 1963 | { 1964 | #if SIMPLE_JSON_NO_LINQ_EXPRESSION 1965 | return GetSetMethodByReflection(fieldInfo); 1966 | #else 1967 | return GetSetMethodByExpression(fieldInfo); 1968 | #endif 1969 | } 1970 | 1971 | public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) 1972 | { 1973 | MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); 1974 | return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; 1975 | } 1976 | 1977 | public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) 1978 | { 1979 | return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; 1980 | } 1981 | 1982 | #if !SIMPLE_JSON_NO_LINQ_EXPRESSION 1983 | 1984 | public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) 1985 | { 1986 | MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); 1987 | ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); 1988 | ParameterExpression value = Expression.Parameter(typeof(object), "value"); 1989 | UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); 1990 | UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); 1991 | Action compiled = Expression.Lambda>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); 1992 | return delegate(object source, object val) { compiled(source, val); }; 1993 | } 1994 | 1995 | public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) 1996 | { 1997 | ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); 1998 | ParameterExpression value = Expression.Parameter(typeof(object), "value"); 1999 | Action compiled = Expression.Lambda>( 2000 | Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); 2001 | return delegate(object source, object val) { compiled(source, val); }; 2002 | } 2003 | 2004 | public static BinaryExpression Assign(Expression left, Expression right) 2005 | { 2006 | #if SIMPLE_JSON_TYPEINFO 2007 | return Expression.Assign(left, right); 2008 | #else 2009 | MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); 2010 | BinaryExpression assignExpr = Expression.Add(left, right, assign); 2011 | return assignExpr; 2012 | #endif 2013 | } 2014 | 2015 | private static class Assigner 2016 | { 2017 | public static T Assign(ref T left, T right) 2018 | { 2019 | return (left = right); 2020 | } 2021 | } 2022 | 2023 | #endif 2024 | 2025 | public sealed class ThreadSafeDictionary : IDictionary 2026 | { 2027 | private readonly object _lock = new object(); 2028 | private readonly ThreadSafeDictionaryValueFactory _valueFactory; 2029 | private Dictionary _dictionary; 2030 | 2031 | public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory valueFactory) 2032 | { 2033 | _valueFactory = valueFactory; 2034 | } 2035 | 2036 | private TValue Get(TKey key) 2037 | { 2038 | if (_dictionary == null) 2039 | return AddValue(key); 2040 | TValue value; 2041 | if (!_dictionary.TryGetValue(key, out value)) 2042 | return AddValue(key); 2043 | return value; 2044 | } 2045 | 2046 | private TValue AddValue(TKey key) 2047 | { 2048 | TValue value = _valueFactory(key); 2049 | lock (_lock) 2050 | { 2051 | if (_dictionary == null) 2052 | { 2053 | _dictionary = new Dictionary(); 2054 | _dictionary[key] = value; 2055 | } 2056 | else 2057 | { 2058 | TValue val; 2059 | if (_dictionary.TryGetValue(key, out val)) 2060 | return val; 2061 | Dictionary dict = new Dictionary(_dictionary); 2062 | dict[key] = value; 2063 | _dictionary = dict; 2064 | } 2065 | } 2066 | return value; 2067 | } 2068 | 2069 | public void Add(TKey key, TValue value) 2070 | { 2071 | throw new NotImplementedException(); 2072 | } 2073 | 2074 | public bool ContainsKey(TKey key) 2075 | { 2076 | return _dictionary.ContainsKey(key); 2077 | } 2078 | 2079 | public ICollection Keys 2080 | { 2081 | get { return _dictionary.Keys; } 2082 | } 2083 | 2084 | public bool Remove(TKey key) 2085 | { 2086 | throw new NotImplementedException(); 2087 | } 2088 | 2089 | public bool TryGetValue(TKey key, out TValue value) 2090 | { 2091 | value = this[key]; 2092 | return true; 2093 | } 2094 | 2095 | public ICollection Values 2096 | { 2097 | get { return _dictionary.Values; } 2098 | } 2099 | 2100 | public TValue this[TKey key] 2101 | { 2102 | get { return Get(key); } 2103 | set { throw new NotImplementedException(); } 2104 | } 2105 | 2106 | public void Add(KeyValuePair item) 2107 | { 2108 | throw new NotImplementedException(); 2109 | } 2110 | 2111 | public void Clear() 2112 | { 2113 | throw new NotImplementedException(); 2114 | } 2115 | 2116 | public bool Contains(KeyValuePair item) 2117 | { 2118 | throw new NotImplementedException(); 2119 | } 2120 | 2121 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 2122 | { 2123 | throw new NotImplementedException(); 2124 | } 2125 | 2126 | public int Count 2127 | { 2128 | get { return _dictionary.Count; } 2129 | } 2130 | 2131 | public bool IsReadOnly 2132 | { 2133 | get { throw new NotImplementedException(); } 2134 | } 2135 | 2136 | public bool Remove(KeyValuePair item) 2137 | { 2138 | throw new NotImplementedException(); 2139 | } 2140 | 2141 | public IEnumerator> GetEnumerator() 2142 | { 2143 | return _dictionary.GetEnumerator(); 2144 | } 2145 | 2146 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 2147 | { 2148 | return _dictionary.GetEnumerator(); 2149 | } 2150 | } 2151 | } 2152 | } 2153 | } 2154 | 2155 | // ReSharper restore LoopCanBeConvertedToQuery 2156 | // ReSharper restore RedundantExplicitArrayCreation 2157 | // ReSharper restore SuggestUseVarKeywordEvident 2158 | --------------------------------------------------------------------------------