From 9a07ec2240a41997b4ee61d8bb8170cb83a434bf Mon Sep 17 00:00:00 2001 From: pvschakradhar <48746416+pvschakradhar@users.noreply.github.com> Date: Mon, 13 Apr 2020 09:28:37 +0530 Subject: [PATCH] [System.PowerUsage] Introduce Tizen.System.PowerUsage (#1512) * Addition of Tizen.System.PowerUsage Addition of Tizen.System.PowerUsage * Variables renaming and addition of DateTimeOffset Variables renaming and addition of DateTimeOffset * Change of dll to Tizen.System.PowerUsage.dll and RecordNotFound exception handling Change of dll to Tizen.System.PowerUsage.dll and RecordNotFound exception handling * Rearrangement for code readability and adition of ArgumentNullException Rearrangement for code readability and adition of ArgumentNullException * change in DateTime.SpecifyKind and correction in copyright change in DateTime.SpecifyKind and correction in copyright * removal of specify kind in DateTime removal of specify kind in DateTime --- .../Interop/Interop.Libraries.cs | 23 ++ .../Interop/Interop.PowerUsage.cs | 43 ++++ .../PowerUsage/PowerUsage.cs | 233 +++++++++++++++++++++ .../PowerUsage/PowerUsageExceptionFactory.cs | 104 +++++++++ .../Tizen.System.PowerUsage.csproj | 13 ++ .../Tizen.System.PowerUsage.sln | 69 ++++++ 6 files changed, 485 insertions(+) create mode 100644 src/Tizen.System.PowerUsage/Interop/Interop.Libraries.cs create mode 100644 src/Tizen.System.PowerUsage/Interop/Interop.PowerUsage.cs create mode 100644 src/Tizen.System.PowerUsage/PowerUsage/PowerUsage.cs create mode 100644 src/Tizen.System.PowerUsage/PowerUsage/PowerUsageExceptionFactory.cs create mode 100644 src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.csproj create mode 100644 src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.sln diff --git a/src/Tizen.System.PowerUsage/Interop/Interop.Libraries.cs b/src/Tizen.System.PowerUsage/Interop/Interop.Libraries.cs new file mode 100644 index 0000000..6271990 --- /dev/null +++ b/src/Tizen.System.PowerUsage/Interop/Interop.Libraries.cs @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved +* +* Licensed under the Apache License, Version 2.0 (the License); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an AS IS BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +internal static partial class Interop +{ + internal static partial class Libraries + { + internal const string PowerUsage = "libcapi-system-battery-monitor.so.0"; + } +} diff --git a/src/Tizen.System.PowerUsage/Interop/Interop.PowerUsage.cs b/src/Tizen.System.PowerUsage/Interop/Interop.PowerUsage.cs new file mode 100644 index 0000000..fc16a52 --- /dev/null +++ b/src/Tizen.System.PowerUsage/Interop/Interop.PowerUsage.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Runtime.InteropServices; +using Tizen.System; + +internal static partial class Interop +{ + internal static class PowerUsage + { + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_battery_usage_data_destroy")] + internal static extern int BatteryUsageDataDestroy(IntPtr dataHandle); + + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_usage_data_get_power_usage_per_resource")] + internal static extern int UsageDataGetPowerUsagePerResource(IntPtr dataHandle, PowerUsageResourceType rtype, out double batteryUsage); + + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_get_power_usage_by_app_for_all_resources")] + internal static extern int GetPowerUsageByAppForAllResources(string appID, long startTime, long endTime, out IntPtr dataHandle); + + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_get_power_usage_by_app_per_resource")] + internal static extern int GetPowerUsageByAppPerResource(string appID, PowerUsageResourceType rtype, long startTime, long endTime, out double batteryUsage); + + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_get_power_usage_by_app")] + internal static extern int GetPowerUsageByApp(string appID, long startTime, long endTime, out double batteryUsage); + + [DllImport(Libraries.PowerUsage, EntryPoint = "battery_monitor_get_power_usage_by_resource")] + internal static extern int GetPowerUsageByResource(PowerUsageResourceType rtype, long startTime, long endTime, out double batteryUsage); + } +} \ No newline at end of file diff --git a/src/Tizen.System.PowerUsage/PowerUsage/PowerUsage.cs b/src/Tizen.System.PowerUsage/PowerUsage/PowerUsage.cs new file mode 100644 index 0000000..722f78c --- /dev/null +++ b/src/Tizen.System.PowerUsage/PowerUsage/PowerUsage.cs @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; + +namespace Tizen.System +{ + /// + /// Enumeration for battery consuming features. + /// + /// 7 + public enum PowerUsageResourceType + { + /// + /// Bluetooth Low Energy. + /// + Ble = 0, + + /// + /// Wi-Fi. + /// + Wifi = 1, + + /// + /// CPU. + /// + Cpu = 2, + + /// + /// Display. + /// + Display = 3, + + /// + /// Device Network. + /// + DeviceNetwork = 4, + + /// + /// GPS Sensor. + /// + Gps = 5 + } + + /// + /// Provides information related to the power consumption by applications or by hardware resources on a battery-powered device for a given duration of time. + /// + /// http://tizen.org/feature/battery + /// http://tizen.org/privilege/systemmonitor + /// 7 + public static class PowerUsage + { + /// + /// Gets the battery consumption in mAh(milli-Ampere hour) for the resources specified by the application in custom interval. + /// + /// 7 + /// Application ID of the application for which battery usage is required. + /// list of resource type identifiers like BLE, WiFi, CPU etc. + /// Start Time for data in DateTime. + /// End Time for data in DateTime. + /// Returns the battery consumption in mAh(milli-Ampere hour) for the resources specified by the application in custom interval. + /// http://tizen.org/feature/battery + /// http://tizen.org/privilege/systemmonitor + /// When an invalid parameter value is set. + /// If the privilege is not set. + /// In case of any system error. + /// In case power usage is not supported + public static IDictionary GetPowerUsage(string appID, IList rtypes, DateTime start, DateTime end) + { + IntPtr dataHandle = IntPtr.Zero; + IDictionary batteryUsage = new Dictionary(); + try + { + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.GetPowerUsageByAppForAllResources(appID, ((DateTimeOffset)start).ToUnixTimeSeconds(), ((DateTimeOffset)end).ToUnixTimeSeconds(), out dataHandle); + if (ret != PowerUsageError.None) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error getting battery usage for all resources" + ret); + throw PowerUsageErrorFactory.ThrowPowerUsageException(ret); + } + + if (rtypes != null) + { + foreach (PowerUsageResourceType type in rtypes) + { + try + { + batteryUsage[type] = GetPowerUsage(dataHandle, type); + } + catch (InvalidOperationException) + { + Log.Error(PowerUsageErrorFactory.LogTag, $"Error getting battery usage for {type}"); + } + } + } + else + { + Log.Error(PowerUsageErrorFactory.LogTag, "Power usage resource types parameter is empty"); + throw new ArgumentNullException(nameof(rtypes)); + } + } + finally + { + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.BatteryUsageDataDestroy(dataHandle); + if (ret != PowerUsageError.None) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error in Destroy handle, " + ret); + } + } + return batteryUsage; + } + + /// + /// Gets the battery consumption in mAh(milli-Ampere hour) for the specific resource for the given application in custom interval. + /// + /// 7 + /// Application ID of the application for which battery usage is required. + /// Identifier of resource type. BLE, WiFi, CPU etc. + /// Start Time for data in DateTime. + /// End Time for data in DateTime. + /// Returns the battery consumption in mAh(milli-Ampere hour) for the specific resource for the given application in custom interval. + /// http://tizen.org/feature/battery + /// http://tizen.org/privilege/systemmonitor + /// When an invalid parameter value is set. + /// If the privilege is not set. + /// In case of any system error. + /// In case power usage is not supported + public static double GetPowerUsage(string appID, PowerUsageResourceType rtype, DateTime start, DateTime end) + { + double batteryUsage = 0; + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.GetPowerUsageByAppPerResource(appID, rtype, ((DateTimeOffset)start).ToUnixTimeSeconds(), ((DateTimeOffset)end).ToUnixTimeSeconds(), out batteryUsage); + if (ret != PowerUsageError.None) + { + if (ret == PowerUsageError.RecordNotFound) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error PowerUsageResourceType is not supported"); + throw new ArgumentException($"{rtype} is not supproted", nameof(rtype)); + } + else + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error getting battery usage by app per resrource ," + ret); + throw PowerUsageErrorFactory.ThrowPowerUsageException(ret); + } + } + return batteryUsage; + } + + /// + /// Gets the total battery usage in mAh(milli-Ampere hour) by an application for certain time interval. + /// + /// 7 + /// Application ID of the application for which battery usage is required. + /// Start Time for data in DateTime. + /// End Time for data in DateTime. + /// Returns the total battery usage in mAh(milli-Ampere hour) by an application for certain time interval. + /// http://tizen.org/feature/battery + /// http://tizen.org/privilege/systemmonitor + /// When an invalid parameter value is set. + /// If the privilege is not set. + /// In case of any system error. + /// In case power usage is not supported + public static double GetPowerUsage(string appID, DateTime start, DateTime end) + { + double batteryUsage = 0; + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.GetPowerUsageByApp(appID, ((DateTimeOffset)start).ToUnixTimeSeconds(), ((DateTimeOffset)end).ToUnixTimeSeconds(), out batteryUsage); + if (ret != PowerUsageError.None) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error getting battery usage by app," + ret); + throw PowerUsageErrorFactory.ThrowPowerUsageException(ret); + } + return batteryUsage; + } + + /// + /// Gets the battery usage in mAh(milli-Ampere hour) by a resource for certain time interval. + /// + /// 7 + /// Identifier of resource type. BLE, WiFi, CPU etc. + /// Start Time for data in DateTime. + /// End Time for data in DateTime. + /// Returns the battery usage in mAh(milli-Ampere hour) by a resource for certain time interval. + /// http://tizen.org/feature/battery + /// http://tizen.org/privilege/systemmonitor + /// When an invalid parameter value is set. + /// If the privilege is not set. + /// In case of any system error. + /// In case power usage is not supported + public static double GetPowerUsage(PowerUsageResourceType rtype, DateTime start, DateTime end) + { + double batteryUsage = 0; + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.GetPowerUsageByResource(rtype, ((DateTimeOffset)start).ToUnixTimeSeconds(), ((DateTimeOffset)end).ToUnixTimeSeconds(), out batteryUsage); + if (ret != PowerUsageError.None) + { + if (ret == PowerUsageError.RecordNotFound) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error PowerUsageResourceType is not supported"); + throw new ArgumentException($"{rtype} is not supproted", nameof(rtype)); + } + else + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error getting battery usage by resource ," + ret); + throw PowerUsageErrorFactory.ThrowPowerUsageException(ret); + } + } + return batteryUsage; + } + + private static double GetPowerUsage(IntPtr dataHandle, PowerUsageResourceType rtype) + { + double batteryUsage = 0; + PowerUsageError ret = (PowerUsageError)Interop.PowerUsage.UsageDataGetPowerUsagePerResource(dataHandle, rtype, out batteryUsage); + if (ret != PowerUsageError.None) + { + Log.Error(PowerUsageErrorFactory.LogTag, "Error getting battery usage for resource" + ret); + throw PowerUsageErrorFactory.ThrowPowerUsageException(ret); + } + return batteryUsage; + } + } +} diff --git a/src/Tizen.System.PowerUsage/PowerUsage/PowerUsageExceptionFactory.cs b/src/Tizen.System.PowerUsage/PowerUsage/PowerUsageExceptionFactory.cs new file mode 100644 index 0000000..46c48ba --- /dev/null +++ b/src/Tizen.System.PowerUsage/PowerUsage/PowerUsageExceptionFactory.cs @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using Tizen.Internals.Errors; + +namespace Tizen.System +{ + /// + /// Power Usage error codes. + /// + /// 7 + internal enum PowerUsageError + { + /// + /// Successful. + /// + None = ErrorCode.None, + + /// + /// Out of memory error. + /// + OutOfMemory = ErrorCode.OutOfMemory, + + /// + /// Invalid parameter. + /// + InvalidParameter = ErrorCode.InvalidParameter, + + /// + /// Permission denied. + /// + AcessibilityNotallowed = ErrorCode.PermissionDenied, + + /// + /// Address family not supported. + /// + NotSupported = ErrorCode.NotSupported, + + /// + /// Related record does not exist. + /// + RecordNotFound = -0x03060000 | 0x01, + + /// + /// DB operation failed . + /// + DBFailed = -0x03060000 | 0x02, + + /// + /// DB is not connected. + /// + DBNotOpened = -0x03060000 | 0x03, + + /// + /// Internal error for generic use. + /// + Internal = -0x03060000 | 0x04 + } + + internal static class PowerUsageErrorFactory + { + internal const string LogTag = "Tizen.System.PowerUsage"; + internal static Exception ThrowPowerUsageException(PowerUsageError errCode) + { + Log.Error(LogTag, "Throw PowerUsage Exception : " + errCode); + + switch (errCode) + { + case PowerUsageError.OutOfMemory: + return new InvalidOperationException("Out of memory"); + case PowerUsageError.InvalidParameter: + return new ArgumentException("Invalid Parameter passed"); + case PowerUsageError.AcessibilityNotallowed: + return new UnauthorizedAccessException("Permission denied"); + case PowerUsageError.NotSupported: + return new NotSupportedException("Not supported"); + case PowerUsageError.RecordNotFound: + return new InvalidOperationException("Related record does not exist"); + case PowerUsageError.DBFailed: + return new InvalidOperationException("DB operation failed"); + case PowerUsageError.DBNotOpened: + return new InvalidOperationException("DB is not connected"); + case PowerUsageError.Internal: + return new InvalidOperationException("Internal error for generic use"); + default: + return new InvalidOperationException("Unknown Error"); + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.csproj b/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.csproj new file mode 100644 index 0000000..2648db5 --- /dev/null +++ b/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.sln b/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.sln new file mode 100644 index 0000000..ff67f39 --- /dev/null +++ b/src/Tizen.System.PowerUsage/Tizen.System.PowerUsage.sln @@ -0,0 +1,69 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.System.PowerUsage", "Tizen.System.PowerUsage.csproj", "{C4007463-E96F-43B7-91A8-52B94A4951EF}" + ProjectSection(ProjectDependencies) = postProject + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC} = {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC} + {BB810DE4-4F52-468F-B68C-408F958CAEFC} = {BB810DE4-4F52-468F-B68C-408F958CAEFC} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen", "..\Tizen\Tizen.csproj", "{BB810DE4-4F52-468F-B68C-408F958CAEFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Log", "..\Tizen.Log\Tizen.Log.csproj", "{64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|x64.Build.0 = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Debug|x86.Build.0 = Debug|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|x64.ActiveCfg = Release|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|x64.Build.0 = Release|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|x86.ActiveCfg = Release|Any CPU + {C4007463-E96F-43B7-91A8-52B94A4951EF}.Release|x86.Build.0 = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|x64.Build.0 = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Debug|x86.Build.0 = Debug|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|Any CPU.Build.0 = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|x64.ActiveCfg = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|x64.Build.0 = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|x86.ActiveCfg = Release|Any CPU + {BB810DE4-4F52-468F-B68C-408F958CAEFC}.Release|x86.Build.0 = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|x64.ActiveCfg = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|x64.Build.0 = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|x86.ActiveCfg = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Debug|x86.Build.0 = Debug|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|Any CPU.Build.0 = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|x64.ActiveCfg = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|x64.Build.0 = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|x86.ActiveCfg = Release|Any CPU + {64F7D1C0-2E3C-4E80-B81D-D0B70A3448FC}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A33C2591-BFEA-4BCC-890D-C205A7D9B990} + EndGlobalSection +EndGlobal -- 2.7.4