diff --git a/Workshop/Module1-AdaptiveUI/Images/adaptive_tile1.png b/Workshop/Module1-AdaptiveUI/Images/adaptive_tile1.png index a8e821a..411beb8 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/adaptive_tile1.png and b/Workshop/Module1-AdaptiveUI/Images/adaptive_tile1.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/aerial3d.png b/Workshop/Module1-AdaptiveUI/Images/aerial3d.png index 2411869..5380518 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/aerial3d.png and b/Workshop/Module1-AdaptiveUI/Images/aerial3d.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/continuum.png b/Workshop/Module1-AdaptiveUI/Images/continuum.png new file mode 100644 index 0000000..2ec1dc4 Binary files /dev/null and b/Workshop/Module1-AdaptiveUI/Images/continuum.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/continuum1.png b/Workshop/Module1-AdaptiveUI/Images/continuum1.png new file mode 100644 index 0000000..e51bb70 Binary files /dev/null and b/Workshop/Module1-AdaptiveUI/Images/continuum1.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/debug_mode.png b/Workshop/Module1-AdaptiveUI/Images/debug_mode.png index eb3c3eb..86079c2 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/debug_mode.png and b/Workshop/Module1-AdaptiveUI/Images/debug_mode.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/fixed_layout.png b/Workshop/Module1-AdaptiveUI/Images/fixed_layout.png index 14de247..0a1bc97 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/fixed_layout.png and b/Workshop/Module1-AdaptiveUI/Images/fixed_layout.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/fixed_layout_cut.png b/Workshop/Module1-AdaptiveUI/Images/fixed_layout_cut.png index 37c7a59..3d4d26b 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/fixed_layout_cut.png and b/Workshop/Module1-AdaptiveUI/Images/fixed_layout_cut.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/mobile_state.png b/Workshop/Module1-AdaptiveUI/Images/mobile_state.png index 4b68c0e..a534b9f 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/mobile_state.png and b/Workshop/Module1-AdaptiveUI/Images/mobile_state.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer.png b/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer.png index 2287bbd..5c2df85 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer.png and b/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer_store2.png b/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer_store2.png index c844594..b26a67f 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer_store2.png and b/Workshop/Module1-AdaptiveUI/Images/notifications_visualizer_store2.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/startup.png b/Workshop/Module1-AdaptiveUI/Images/startup.png index 5ed1847..b6f4f3f 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/startup.png and b/Workshop/Module1-AdaptiveUI/Images/startup.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/state-trigger-editor.png b/Workshop/Module1-AdaptiveUI/Images/state-trigger-editor.png index 30d5917..e98479d 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/state-trigger-editor.png and b/Workshop/Module1-AdaptiveUI/Images/state-trigger-editor.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/states_window_initial.png b/Workshop/Module1-AdaptiveUI/Images/states_window_initial.png index fa83adf..4a49350 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/states_window_initial.png and b/Workshop/Module1-AdaptiveUI/Images/states_window_initial.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/streetside.png b/Workshop/Module1-AdaptiveUI/Images/streetside.png index 2acf79d..b3b7c27 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/streetside.png and b/Workshop/Module1-AdaptiveUI/Images/streetside.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/tablet_state.png b/Workshop/Module1-AdaptiveUI/Images/tablet_state.png index e4ccb78..ce3c2f5 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/tablet_state.png and b/Workshop/Module1-AdaptiveUI/Images/tablet_state.png differ diff --git a/Workshop/Module1-AdaptiveUI/Images/uwp_tile_extension.png b/Workshop/Module1-AdaptiveUI/Images/uwp_tile_extension.png index a85a146..0ba52b7 100644 Binary files a/Workshop/Module1-AdaptiveUI/Images/uwp_tile_extension.png and b/Workshop/Module1-AdaptiveUI/Images/uwp_tile_extension.png differ diff --git a/Workshop/Module1-AdaptiveUI/README.md b/Workshop/Module1-AdaptiveUI/README.md index e6bb6ec..d83ea6e 100644 --- a/Workshop/Module1-AdaptiveUI/README.md +++ b/Workshop/Module1-AdaptiveUI/README.md @@ -12,7 +12,7 @@ An adaptive UI differs from a responsive UI, because it can deliver an individua With adaptive UI, you can deliver a responsive design, but you also have the ability to deliver unique views to devices that have little in common with each other. For example, an Xbox view may be completely distinct from the desktop and mobile views for an app, because the device UI and interactions are so different. -In this lab, we will evolve a fixed layout into an adaptive UI and view it on Desktop, Mobile, and Continuum. We’ll also use some new tools to easily generate default tiles, build adaptive tiles from XML, and enhance the Maps experience. +In this module, we will evolve a fixed layout into an adaptive UI and view it on Desktop, Mobile, and Continuum. We’ll also use some new tools to easily generate default tiles, build adaptive tiles from XML, and enhance the Maps experience. ### Objectives ### @@ -90,7 +90,7 @@ Let’s take a look at how the app is set up and plan an adaptive layout. We’l _Content in the fixed UI is cut off for smaller window sizes_ -1. Click on the Hamburger to observe the menu behavior. The menu will collapse, and the content moves to the left. +1. Click the hamburger to observe the menu behavior. The menu will collapse, and the content moves to the left. 1. Return to Visual Studio and stop debugging. @@ -121,7 +121,7 @@ Now that we’ve explored the fixed UI in the SightsToSee starter app, we can ad > **Note**: Code snippets are blocks of reusable code that can be quickly inserted using a unique string, hotkey, or context menu. There are two kinds of snippets: expansion snippets and surround-with snippets. - > **Note**: Expansion snippets contain contiguous blocks of code that insert at the point of the cursor. Surround-with snippets can wrap around existing code. In this lab, we will use expansion snippets. For more on code snippets, visit https://msdn.microsoft.com/en-us/library/ms165392.aspx + > **Note**: Expansion snippets contain contiguous blocks of code that insert at the point of the cursor. Surround-with snippets can wrap around existing code. In this module, we will use expansion snippets. For more on code snippets, visit https://msdn.microsoft.com/en-us/library/ms165392.aspx 1. Open **AppShell.xaml**. @@ -131,6 +131,8 @@ Now that we’ve explored the fixed UI in the SightsToSee starter app, we can ad This state will control the behavior of the navigation pane for mid-size windows and tablets. It will set the SplitView pane to closed by default and keep the SplitView in **CompactInline** mode. + (Code Snippet - _M1_ShellTablet_) + ````XAML @@ -145,195 +147,131 @@ Now that we’ve explored the fixed UI in the SightsToSee starter app, we can ad ```` -1. The snippet we just added will control the Tablet state for the navigation pane, but we also need to add a tablet state for the content. We will add this state interactively using Blend. Right-click on **Views > TripDetailPage.xaml** and choose **Design in Blend**. - -1. When Blend opens, display the **States Window** to view the existing visual states. - - > **Note:** The States Window usually shares a pane with the Solution Editor in the default layout in Blend. If you can’t find the States Window, use the Quick Launch search field to find it by searching for States Window. Select the States Window search result to open it. - - ![The States Window in Blend](Images/states_window_initial.png?raw=true "The States Window in Blend") - - _The States Window in Blend_ - -1. Rename the existing visual state to **DesktopState** by clicking on the state name to enable editing. Edit its adaptive trigger by clicking on the lightning button to open the State Trigger editor. Change the **MinWindowWidth** on the existing adaptive trigger to **800**. - -1. To make changing the UI easier, select the SightsGrid in the Objects and Timelines Window. Give it a temporary background color in the Properties Window. We don’t have design time data turned on for this control, so a background color will make its position more obvious. - -1. In the Design Window, select the **8” Tablet** as the display device and put it in **Portrait** mode. - -1. Use the **Add State** button inside the existing VisualStateGroup in the **States Window** to add another visual state called **TabletState**. - -1. Next, you’ll need to give the Tablet state an adaptive trigger. Use the lightning bolt button next to the TabletState name to open the StateTriggerBase Collection Editor. When the editor is open, select **AdaptiveTrigger** from the dropdown menu and choose **Add**. Set the **MinWindowWidth** to **720** and close the State Trigger editor with the **OK** button. - - ![Adding an Adaptive Trigger in the State Trigger Editor in Blend](Images/state-trigger-editor.png?raw=true "Adding an Adaptive Trigger in the State Trigger Editor in Blend") - - _Adding an Adaptive Trigger in the State Trigger Editor in Blend_ - -1. Select the **TabletState** in the States Window. When you see a red dot next to the state name, recording is turned on. While recording in Blend, any property changes you make to properties on the XAML controls will be saved to the selected visual state. - - > **Note:** Recording in Blend is a quick and easy way to make a number of changes at once and save them as a visual state. - -1. Let’s build the Tablet visual state. Use the **Objects and Timeline** Window to select the **title** TextBlock. Once the title is selected, move over to the **Properties Window**. Right-click on the box to the right of the **Visibility** field to open its context menu. Select **Record Current Value** from the context menu. The box to the right of the field will turn solid to indicate that the property is recorded in the visual state. - - > **Note:** Even though some properties already appear to have values in the Properties Window, the new visual state will remain empty until those values are recorded. - -1. Select the **MobileHeader** Border element in the **Objects and Timeline Window**. The **Visibility** property should already be set to **Collapsed**. Record the current value. - -1. Use the **Objects and Timeline Window** to select the **MapGrid**. You may need to drill down into the Visual Tree to find it. - -1. Once the **MapGrid** is expanded, move over to the **Properties Window**. Expand the **RelativePanel** section. - -1. In the Tablet state, we will position the Map above the Sights grid to make better use of space and show content responsively on the screen. We will use the attached RelativePanel properties to position the Map and the Sights grid in relation to each other and the panel. +1. The snippet we just added will control the Tablet state for the navigation pane, but we also need to add a tablet state for the content. but we also need to add a tablet state for the content. Open **Views > TripDetailPage.xaml**. - Check the checkboxes in the MapGrid RelativePanel properties for **AlignTopWithPanel**, **AlignLeftWithPanel**, and **AlignRightWithPanel**. +1. Look for the `` section in the VisualStateGroup. Type **M1_TripTablet** below the comment and hit the Tab key to expand the snippet. +This state moves the Map control above the Sights GridViews, and it anchors the top, left, and right sides of the Map to the panel. - > **Note:** Visual states sometimes conflict with each other if certain properties aren’t cleared or overridden. With RelativePanels, it is easy to inadvertently set up a circular reference. Explicit layouts and properties directly on controls can also cause conflicts in visual states. - > - > You can override properties in a visual state by setting them to different values. To clear out a RelativePanel alignment state without setting it to a new value, set it to the empty string. However, it is recommended that you avoid explicit RelativePanel layouts on controls if you are using visual states. + (Code Snippet - _M1_TripTablet_) -1. Set the **Height** property on the **MapGrid** to **360**. - -1. Set the **Margins** on the MapGrid to **24, 0, 24, 28**. Record the MapGrid **Padding** as **0** on all sides. - - > **Note:** Margins and Padding in XAML are set in the clockwise order **left**, **top**, **right**, **bottom**. When four Margin or Padding values are written in a comma-delimited list, you can assume they follow this order. When two values are given, for instance **12, 16**, the values will be interpreted as **12, 16, 12, 16**. When one value is given, it will be applied to all four values on the element. - -1. Select the **LayoutPanel** in the Objects and Timelines Window and set its **Padding** to **0**. - -1. Select the **SightsGrid** in the Objects and Timelines Window. Using the Properties Window, set its **Width** to **Auto**. Set its **Margins** to **24, 0, 0, 0** and its **Padding** to **0**. - -1. Expand the **SightsGrid** RelativePanel properties in the Properties Window. Set the SightsGrid RelativePanel **Below** property to **MapGrid**. - -1. Check the **AlignRightWithPanel** and **AlignLeftWithPanel** checkboxes to stretch the SightsGrid to full width across the screen. - -1. View the XAML for the TabletState and check that the correct Setters have been added. View the Designer to check that the state looks appropriate visually. - - > **Note:** You may notice that Setter Targets generated by Blend use attached properties—for example, element.(UIElement.Visibility)—instead of dependency properties such as element.Visibility. The attached properties are more type correct, but the end result is the same. Either target can be used to achieve the same result. - - Your XAML for the TabletState should look similar to the code sample below. - - ````XAML - - - - - - - - - - - 24,0,24,28 - - - - - 0 - - - - - 0 - - - - - 24,0,0,0 - - - - - 0 - - - - - - - - - - - ```` - -1. Select the **SightsGrid** in the Objects and Timeline Window. Remove the background color from the SightsGrid by selecting **Reset** from the Background property context menu. + ````XAML + -1. Build and run the app. Resize the window to view the new Tablet visual state. Take a look at the menu behavior and Sight detail popup behavior. If you are using a device that has Tablet Mode, enable it in the Action Center. Although the adaptive layout may not change, because it is triggered by screen size rather than device type, the back button experience will change from the shell back button to the global back button. + + + + + + + + + + + + + + + + + + + + + + + + + + -1. Save your work, exit Blend, and return to Visual Studio. When prompted to reload **TripDetailPage.xaml** in Visual Studio, choose **Yes** to update to the version you recorded in Blend. + + + ```` + > **Note**: Visual states sometimes conflict with each other if certain properties aren’t cleared or overridden. With RelativePanels, it is easy to inadvertently set up a circular reference. Explicit layouts and properties directly on controls can also cause conflicts in visual states. +You can override properties in a visual state by setting them to different values. To clear out a RelativePanel alignment state without setting it to a new value, set it to the empty string. - > **Note:** If your Tablet visual state does not display properly, you may delete it in XAML and replace it by expanding the M1_TripTablet snippet. +1. Build and run the app. Resize the window to view the new Tablet visual state. Take a look at the menu behavior and Sight detail popup behavior. If you are using a device with Tablet mode, turn it on. Although the adaptive layout may not change, since it is triggered by screen size, the back button experience will change from the shell back button to the global back button. ![The Tablet visual state in the SightsToSee app](Images/tablet_state.png?raw=true "The Tablet visual state in the SightsToSee app") - _The Tablet visual state in the SightsToSee app_ - - You may notice that the UI is still cut off for window sizes smaller than the Tablet state we’ve defined. In the following steps, we will add the Mobile state. + _The Tablet visual state in the SightsToSee app_ -1. Stop debugging and return to Visual Studio. - -1. Open **AppShell.xaml**. Expand the **M1_ShellMobile** snippet into the `` section in the **VisualStateGroup**. + You may notice that the UI is still cut off for window sizes smaller than the Tablet state we’ve defined. In the following steps, we will add the Mobile state. + +1. Stop debugging and return to Visual Studio. - This state sets the SplitView pane to **Overlay** mode. Overlay mode means the menu is invisible when closed and lays over the content when open. Note that the hamburger button is not included in the SplitView, so it will always display. This state also sets the nav pane to closed by default, so only the hamburger button will be visible when the user arrives at the page. +1. Return to **AppShell.xaml**. - ````XAML - +1. Expand the **M1_ShellMobile** snippet into the `` section in the VisualStateGroup. + + This state sets the SplitView pane to Overlay mode. Overlay mode means the menu is invisible when closed and lays over the content when open. Note that the hamburger button is not included in the SplitView, so it will always display. This state also sets the nav pane to closed by default, so only the hamburger button will be visible when the user arrives at the page. + + (Code Snippet - _M1_ShellMobile_) - - - - - - - - - - ```` + ````XAML + -1. Open **Views > TripDetailPage.xaml**. + + + + + + + + + + ```` + +1. Open **Views > TripDetailPage.xaml**. +1. Expand the **M1_TripMobile** snippet into the `` section in the VisualStateGroup. -1. Expand the **M1_TripMobile** snippet into the `` section in the VisualStateGroup. + This state sets the map to full-bleed width with no margins. The large page title from the Tablet and Desktop states is hidden, and the Mobile header is shown instead. Some of the setters are set to the empty string to clear out conflicting RelativePanel properties from other states. - This state sets the map to full-bleed width with no margins. The large page title from the Tablet and Desktop states is hidden, and the Mobile header is shown instead. Some of the setters are set to the empty string to clear out conflicting RelativePanel properties from other states. + (Code Snippet - _M1_TripMobile_) ````XAML - - - - - - - - - - - - - - - - - - - - - - - - - ```` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + ```` + 1. Build and run the app. Resize the window to see the app adapt from Desktop to Tablet to Mobile states. ![The Mobile visual state in the SightsToSee app](Images/mobile_state.png?raw=true "The Mobile visual state in the SightsToSee app") @@ -363,7 +301,7 @@ With the new Visual States, we’ve seen how the app is adaptive on Desktop for 1. Tap the **Tap to control <device name>** bar at top of Mobile screen. - ![Tap the bar at the top of the screen to use the Mobile device as a touchpad](Images/missing.png?raw=true "Tap the bar at the top of the screen to use the Mobile device as a touchpad") + ![Tap the bar at the top of the screen to use the Mobile device as a touchpad](Images/continuum.png?raw=true "Tap the bar at the top of the screen to use the Mobile device as a touchpad") _Tap the bar at the top of the screen to use the Mobile device as a touchpad_ @@ -371,7 +309,7 @@ With the new Visual States, we’ve seen how the app is adaptive on Desktop for 1. When the touchpad opens, follow the directions on the screen and use one finger to move the mouse, a tap to select, and two fingers to scroll. - ![The Mobile device becomes a touchpad to control the external Continuum display](Images/missing.png?raw=true "The Mobile device becomes a touchpad to control the external Continuum display") + ![The Mobile device becomes a touchpad to control the external Continuum display](Images/continuum1.png?raw=true "The Mobile device becomes a touchpad to control the external Continuum display") _The Mobile device becomes a touchpad to control the external Continuum display_ @@ -413,13 +351,13 @@ In this task, we will add a logo asset to the project, and use the UWP Tile Gene First, open the **Package.appxmanifest** and browse to the **Visual Assets** tab. Select **All Image Assets** to view the current tiles and splash assets. You’ll see that the placeholder UWP app tile appears for the recommended tile sizes. -1. Right-click on the **Assets** directory in the Solution Explorer and choose **Add > Existing Item**. Add the logo asset from **C:\Labs\CodeLabs-UWP\Workshop\Module 1-AdaptiveUI\Begin\Assets\Tile_Logo.png**. +1. Right-click the **Assets** directory in the Solution Explorer and choose **Add > Existing Item**. Add the logo asset from **C:\Labs\CodeLabs-UWP\Workshop\Module 1-AdaptiveUI\Begin\Assets\Tile_Logo.png**. > **Note:** The new assets that will be generated using the extension will appear in the same folder as the original image. You may choose to add a subfolder to the Assets directory and place the new logo image in the subfolder for better organization. > > When using a generator extension, it is a good idea to use a high resolution seed image in order to generate good quality results for the largest tile and splash assets. -1. Right-click on the **Tile_Logo.png** asset in the Solution Explorer and select **Generate UWP Tiles** from the context menu. +1. Right-click the **Tile_Logo.png** asset in the Solution Explorer and select **Generate UWP Tiles** from the context menu. 1. Right-click the asset again. This time, select **Generate UWP Splash** from the context menu. You will see the new assets appear in the same folder as the original logo image. @@ -478,13 +416,13 @@ Now that we’ve examined the structure of the adaptive tile XML, let’s add ad The structure of the XML we’re going to generate with code will reflect the structure of the XML file we just previewed in the Notifications Visualizer app. -1. Return to Visual Studio and right-click on the **SightsToSee** project name in the Solution Explorer. Select **Manage NuGet packages** from the context menu. +1. Return to Visual Studio and right-click the **SightsToSee** project name in the Solution Explorer. Select **Manage NuGet packages** from the context menu. 1. On the **Browse** tab of the NuGet Package Manager, search for **Notifications Extensions**. 1. Install the **NotificationsExtensions.Win10** NuGet Package. -1. In the Solution Explorer, right-click on the **Services > TileNotificationService** folder and choose **Add > Existing Item**. Browse to the **C:\Labs\CodeLabs-UWP\Workshop\Module 1-AdaptiveUI\Begin\Assets** folder and add **TileHelper.cs**. +1. In the Solution Explorer, right-click the **Services > TileNotificationService** folder and choose **Add > Existing Item**. Browse to the **C:\Labs\CodeLabs-UWP\Workshop\Module 1-AdaptiveUI\Begin\Assets** folder and add **TileHelper.cs**. > **Note:** The TileHelper generates XML similar to the XML we previewed in the Notifications Visualizer app. It looks for the first five Sights added to a Trip, and displays a peek image from each Sight along with the Sight name and description on an adaptive tile. The TileHelper also generates the tile notification. @@ -492,6 +430,8 @@ The structure of the XML we’re going to generate with code will reflect the st 1. Create a new line after line **113** and expand the **M1_CreateTiles** snippet. + (Code Snippet - _M1_CreateTiles_) + ````C# TileHelper.SetInteractiveTilesForTrip(CurrentTrip); // Also whenever the MySights collection changes @@ -519,6 +459,8 @@ In this task, we will display the Sights as PushPins on the map, enable Aerial3D 1. Open TripDetailPage.xaml. Expand the **M1_MapItems** snippet inside the MapControl. The Map items in the MapItemsControl are bound to the list of Sights. Sights added to **My Sights** will display as larger PushPins with borders. **Suggested Sights** will display as smaller PushPins without borders. + (Code Snippet - _M1_MapItems_) + ````XAML @@ -658,7 +602,10 @@ In this task, we will display the Sights as PushPins on the map, enable Aerial3D The **Show3D()** method hides the flyout and sets the Map style to **Aerial3DWithRoads**. It also sets the scene by controlling the pitch, direction, and radius of the 3D view. - public async void Show3D(object sender, RoutedEventArgs e) + (Code Snippet - _M1_Show3D_) + + ````C# + public async void Show3D(object sender, RoutedEventArgs e) { Flyout?.Hide(); // sender is the button - and the data context is the Sight @@ -701,7 +648,7 @@ In this task, we will display the Sights as PushPins on the map, enable Aerial3D > **Note:** The M1_Show3D code snippet includes a stubbed method for ShowStreet(). We will add the contents of the ShowStreet method in a later step. -1. Build and run the app. The PushPins will animate in with Bow animation. Click on a PushPin to view its flyout. Use the building icon to open Aerial3D mode. +1. Build and run the app. The PushPins will animate in with Bow animation. Click a PushPin to view its flyout. Use the building icon to open Aerial3D mode. ![Aerial3D view](Images/aerial3d.png?raw=true "Aerial3D view") @@ -713,6 +660,8 @@ In this task, we will display the Sights as PushPins on the map, enable Aerial3D This method hides the flyout and turns on the Streetside overlay if it is available. + (Code Snippet - _M1_ShowStreet_) + ````C# public async void ShowStreet(object sender, RoutedEventArgs e) { diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Models/Restaurant.cs b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Models/Restaurant.cs index 1210b03..f8b26ee 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Models/Restaurant.cs +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Models/Restaurant.cs @@ -254,5 +254,12 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + + + } + public class EatsGroup + { + public string GroupName { get; set; } + public List ListOfEats { get; set; } } } \ No newline at end of file diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/AppShell.xaml b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/AppShell.xaml index 5879024..22546c1 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/AppShell.xaml +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/AppShell.xaml @@ -53,8 +53,13 @@ + + + + + diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Sights2SeeLogo.png b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Sights2SeeLogo.png new file mode 100644 index 0000000..a181869 Binary files /dev/null and b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Sights2SeeLogo.png differ diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Tile_Logo.png b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Tile_Logo.png new file mode 100644 index 0000000..b01c181 Binary files /dev/null and b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Assets/Tile_Logo.png differ diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/BlurredBackgroundControl.xaml b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/BlurredBackgroundControl.xaml index 4df7261..fdf9b7d 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/BlurredBackgroundControl.xaml +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/BlurredBackgroundControl.xaml @@ -11,8 +11,12 @@ d:DesignWidth="400"> + + + + { + control.BlurredBackImage.Invalidate(); + }; fadeOut.Begin(); fadeOut.Completed += (sender, o) => { @@ -141,30 +167,61 @@ private void BlurredImage_Draw(CanvasControl sender, CanvasDrawEventArgs args) var imageHeight = _backgroundBitmap.Bounds.Height; var imageWidth = _backgroundBitmap.Bounds.Width; - var scale = Math.Min(BlurredImage.ActualWidth/imageWidth, BlurredImage.ActualHeight/imageHeight); + var scale = Math.Min(BlurredImage.ActualWidth / imageWidth, BlurredImage.ActualHeight / imageHeight); double xOffset = 0, yOffset = 0; - if (Math.Abs(imageWidth*scale - BlurredImage.ActualWidth) < 1) + if (Math.Abs(imageWidth * scale - BlurredImage.ActualWidth) < 1) { // Basically the same width, we need to scale up for the height to fit - var newScale = BlurredImage.ActualHeight/(imageHeight*scale); + var newScale = BlurredImage.ActualHeight / (imageHeight * scale); scale *= newScale; } else { - var newScale = BlurredImage.ActualWidth/(imageWidth*scale); + var newScale = BlurredImage.ActualWidth / (imageWidth * scale); scale *= newScale; } - yOffset = (BlurredImage.ActualHeight - imageHeight*scale)/2.0; - xOffset = (BlurredImage.ActualWidth - imageWidth*scale)/2.0; + yOffset = (BlurredImage.ActualHeight - imageHeight * scale) / 2.0; + xOffset = (BlurredImage.ActualWidth - imageWidth * scale) / 2.0; args.DrawingSession.DrawImage(_blurEffect, - new Rect(xOffset, yOffset, imageWidth*scale, imageHeight*scale), + new Rect(xOffset, yOffset, imageWidth * scale, imageHeight * scale), _backgroundBitmap.Bounds); } } + private void AltBlurredImage_Draw(CanvasControl sender, CanvasDrawEventArgs args) + { + if (_altBackBitmap != null) + { + var imageHeight = _altBackBitmap.Bounds.Height; + var imageWidth = _altBackBitmap.Bounds.Width; + + var scale = Math.Min(BlurredImage.ActualWidth / imageWidth, BlurredImage.ActualHeight / imageHeight); + + double xOffset = 0, yOffset = 0; + if (Math.Abs(imageWidth * scale - BlurredImage.ActualWidth) < 1) + { + // Basically the same width, we need to scale up for the height to fit + var newScale = BlurredImage.ActualHeight / (imageHeight * scale); + scale *= newScale; + } + else + { + var newScale = BlurredImage.ActualWidth / (imageWidth * scale); + scale *= newScale; + } + + yOffset = (BlurredImage.ActualHeight - imageHeight * scale) / 2.0; + xOffset = (BlurredImage.ActualWidth - imageWidth * scale) / 2.0; + + args.DrawingSession.DrawImage(_blurEffect, + new Rect(xOffset, yOffset, imageWidth * scale, imageHeight * scale), + _altBackBitmap.Bounds); + } + } + private void BlurredImage_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) { args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction()); @@ -177,12 +234,21 @@ private async Task CreateResourcesAsync(CanvasControl sender) { #if !EFCOREHACK _backgroundBitmap = - await CanvasBitmap.LoadAsync(sender, ((BitmapImage) BackgroundImageSource).UriSource); + await CanvasBitmap.LoadAsync(sender, ((BitmapImage)BackgroundImageSource).UriSource); _blurEffect = new GaussianBlurEffect { Source = _backgroundBitmap, BlurAmount = BlurFactor }; + + _altBackBitmap = + await CanvasBitmap.LoadAsync(sender, ((BitmapImage)BackgroundImageSource).UriSource); + + _altBlurEffect = new GaussianBlurEffect + { + Source = _altBackBitmap, + BlurAmount = BlurFactor + }; #endif } } diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/EatsControl.xaml b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/EatsControl.xaml index 28ca3d1..89d4fa4 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/EatsControl.xaml +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Controls/EatsControl.xaml @@ -10,56 +10,190 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + - - - - - - - + + + + + + + + + + + + + - @@ -75,7 +209,9 @@ Height="135" Width="135" Margin="4"/> - + --> + + + + + Always diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Styles/Styles.xaml b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Styles/Styles.xaml index 921c7dd..247d11e 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Styles/Styles.xaml +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Styles/Styles.xaml @@ -5,10 +5,10 @@ xmlns:controls="using:Microsoft.Labs.SightsToSee.Controls"> - #D1D3D4 - #545455 + #2DA092 + #FFFFFF - #434A4F + #E6E6E6 #2DA092 #2DA092 #2D6B67 @@ -35,7 +35,7 @@ - + @@ -523,6 +523,222 @@ + + + + + + @@ -699,7 +915,6 @@ - @@ -1786,14 +2001,236 @@ - + + + + + + + 0 720 1200 - + + + 0 diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/EatsControlViewModel.cs b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/EatsControlViewModel.cs index 70dd803..cdc0a80 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/EatsControlViewModel.cs +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/EatsControlViewModel.cs @@ -1,8 +1,10 @@ using System.Collections.ObjectModel; using Windows.Devices.Geolocation; +using System.Linq; using Microsoft.Labs.SightsToSee.Library.Models; using Microsoft.Labs.SightsToSee.Models; using Microsoft.Labs.SightsToSee.Mvvm; +using System.Collections.Generic; namespace Microsoft.Labs.SightsToSee.Views { @@ -10,6 +12,7 @@ public class EatsControlViewModel : ViewModelBase { private Geopoint _centerLocation; private ObservableCollection _eats; + private ObservableCollection _eatsGroups; private bool _isDisplayingSightEats; private bool _isLoadingEats; @@ -24,7 +27,17 @@ public bool IsLoadingEats public ObservableCollection Eats { get { return _eats; } - set { Set(ref _eats, value); } + set + { + Set(ref _eats, value); + BuildEatGroups(); + } + } + + public ObservableCollection EatGroups + { + get { return _eatsGroups; } + set { Set(ref _eatsGroups, value); } } public Geopoint CenterLocation @@ -43,5 +56,24 @@ public bool IsDisplayingSightEats public Trip Trip { get; set; } public string Title => IsDisplayingSightEats ? $"Here are the nearest restaurants to {Sight.Name}" : $"Here are restaurants in {Trip.Name}"; + + private void BuildEatGroups() + { + + Restaurant r = new Restaurant(); + + var grouped = from eat in Eats + group eat by eat.CulinaryStyle + into grp + orderby grp.Key ascending + select new EatsGroup + { + GroupName = grp.Key, + ListOfEats = grp.ToList() + }; + + EatGroups = new ObservableCollection(grouped.ToList()); + } } -} \ No newline at end of file +} + diff --git a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml index ca23795..95961f0 100644 --- a/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml +++ b/Workshop/Module1-AdaptiveUI/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml @@ -74,12 +74,21 @@ - - - + + - + + + + + + + + + + + @@ -92,13 +101,16 @@ + + - + + @@ -113,13 +125,14 @@ + + - - + @@ -128,14 +141,14 @@ - - - - + + + + @@ -157,71 +170,38 @@ BackgroundImageSource="{x:Bind ViewModel.SightImage, Mode=OneWay}" /> - - - - - - - - - - - - - - - - - - - - - + + - - - + + Grid.Row="1" + Margin="0,12,0,0"> - + + x:Name="Column0Content" + HorizontalAlignment="Left" + RelativePanel.Below="Column1Content"> - + Style="{ThemeResource CaptionTextBlockStyle}" + Text="{x:Bind ViewModel.CurrentSight.LongDescription, Mode=OneWay}" + Margin="0,0,0,36" /> + @@ -233,23 +213,26 @@ Notes - + - + + - + + HorizontalAlignment="Left" + RelativePanel.AlignTopWithPanel="True" + > @@ -257,14 +240,14 @@ + HorizontalAlignment="Left"> @@ -301,7 +284,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -127,29 +134,45 @@ - + + + + + + + + + + - + + + + - + + + + - - - - + + + + @@ -202,7 +225,7 @@ - + diff --git a/Workshop/Module1-AdaptiveUI/Source/Setup/scripts/snippets/AdaptiveUI.vsi b/Workshop/Module1-AdaptiveUI/Source/Setup/scripts/snippets/AdaptiveUI.vsi index 1c41ab9..af6d07e 100644 Binary files a/Workshop/Module1-AdaptiveUI/Source/Setup/scripts/snippets/AdaptiveUI.vsi and b/Workshop/Module1-AdaptiveUI/Source/Setup/scripts/snippets/AdaptiveUI.vsi differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/debug_mode.png b/Workshop/Module2-MorePersonalComputing/Images/debug_mode.png index eb3c3eb..c81f0c8 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/debug_mode.png and b/Workshop/Module2-MorePersonalComputing/Images/debug_mode.png differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/handwriting.png b/Workshop/Module2-MorePersonalComputing/Images/handwriting.png index a20e3d5..9c1ba34 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/handwriting.png and b/Workshop/Module2-MorePersonalComputing/Images/handwriting.png differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/nearby_sights.png b/Workshop/Module2-MorePersonalComputing/Images/nearby_sights.png index dd06056..c047cfc 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/nearby_sights.png and b/Workshop/Module2-MorePersonalComputing/Images/nearby_sights.png differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/notes_inkcanvas.png b/Workshop/Module2-MorePersonalComputing/Images/notes_inkcanvas.png index 49ce101..17bf7ba 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/notes_inkcanvas.png and b/Workshop/Module2-MorePersonalComputing/Images/notes_inkcanvas.png differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/notes_inktoolbar.png b/Workshop/Module2-MorePersonalComputing/Images/notes_inktoolbar.png index 3f3dc83..cce9a1e 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/notes_inktoolbar.png and b/Workshop/Module2-MorePersonalComputing/Images/notes_inktoolbar.png differ diff --git a/Workshop/Module2-MorePersonalComputing/Images/toast.png b/Workshop/Module2-MorePersonalComputing/Images/toast.png index ffe3081..d5d3e88 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Images/toast.png and b/Workshop/Module2-MorePersonalComputing/Images/toast.png differ diff --git a/Workshop/Module2-MorePersonalComputing/README.md b/Workshop/Module2-MorePersonalComputing/README.md index 8ed02d9..6f501ee 100644 --- a/Workshop/Module2-MorePersonalComputing/README.md +++ b/Workshop/Module2-MorePersonalComputing/README.md @@ -48,7 +48,7 @@ Estimated time to complete this module: **40 to 50 minutes** ### Exercise 1: Inking ### We will be implementing the following Inking features: -- Notes recorded using a simplified OneNote model +- Notes recorded using inking - Inking on photos - Ink Toolbar (preview of the RS1 Ink Toolbar for color and pen selection) - Optical character recognition @@ -58,8 +58,14 @@ We will be implementing the following Inking features: We'll start by adding an InkCanvas that can be used for taking notes. -1. Open the starter project at **<Lab Root>\Module 2\Begin\**. +1. Open the starter project at **C:\Labs\CodeLabs-UWP\Workshop\Module 2-MorePersonalComputing\Begin\Microsoft.Labs.SightsToSee** and open the solution file. +1. Once the project has opened, set your Solution Configuration to **Debug** and your Solution Platform to **x86**. Select **Local Machine** from the Debug Target dropdown menu. + + ![Configure your app to run on the Local Machine](Images/debug_mode.png?raw=true "Configure your app to run on the Local Machine") + + _Configure your app to run on the Local Machine_ + 1. Run the starter project on the **Local Machine** and then exit the application. > **Note:** You may notice there is a new prompt for location as well as a BackgroundTasks project in the Solution. We'll be using these features later in the Module. @@ -72,7 +78,7 @@ We'll start by adding an InkCanvas that can be used for taking notes. Let's get started by added a simple InkCanvas to the Notes field. -1. Open **SightsDetailControl.xaml**. +1. Open **SightDetailPage.xaml** in the __Views__ folder of project __Microsoft.Labs.SightsToSee__. 1. Expand the **M2_EnableInkButton** snippet below the **Notes** title TextBlock. The button will go in the second column of the grid. @@ -88,7 +94,7 @@ We'll start by adding an InkCanvas that can be used for taking notes. > **Note:** The visibility of this button is tied to the **NotesAreInking** boolean through a **BooleanToVisibility** converter. -1. Expand the **M2_EnableInk** snippet in the **SightsDetailPageViewModel**. This method will set the **IsNotesInking** bool to true. We're going to use this property to handle visibility for the Ink and Text elements. +1. Expand the **M2_EnableInk** snippet in the **SightDetailPageViewModel**. This method will set the **IsNotesInking** bool to true. We're going to use this property to handle visibility for the Ink and Text elements. (Code Snippet - _M2_EnableInk_) @@ -99,7 +105,7 @@ We'll start by adding an InkCanvas that can be used for taking notes. } ```` -1. Return to **SightsDetailControl.xaml**. Expand the **M2_NotesInkCanvas** snippet below the Notes TextBox. +1. Return to **SightDetailPage.xaml**. Expand the **M2_NotesInkCanvas** snippet below the Notes TextBox. (Code Snippet - _M2_NotesInkCanvas_) @@ -116,9 +122,8 @@ We'll start by adding an InkCanvas that can be used for taking notes. > **Note:** The InkCanvas is contained in a Grid with a white background, because the InkCanvas on its own would display with a transparent background. -1. In the **SightsDetailControl** code-behind, expand the **M2_NotesInputs** snippet after `InitializeComponent()` in the constructor. +1. In the **SightDetailPage** code-behind, expand the **M2_NotesInputs** snippet after `InitializeComponent()` in the constructor. -1. Build and run your app. (Code Snippet - _M2_NotesInputs_) ````C# @@ -127,25 +132,25 @@ We'll start by adding an InkCanvas that can be used for taking notes. CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch; ```` +1. Build and run your app. - -1. Open the **SightDetailControl** for a Sight and use the **EnableInkButton** to change the Notes field to an InkCanvas. +1. Select a Sight in the app, which navigates to the **SightDetailPage** showing details for a Sight and use the **EnableInkButton** that you added at the top right of the Notes field to change notes input to an InkCanvas. ![The Simple InkCanvas](Images/notes_inkcanvas.png "The Simple InkCanvas") - *__Figure__: The simple Notes InkCanvas.* + _The simple Notes InkCanvas_ - The default pen for the InkCanvas is a simple black line. Right now, we are also not saving the Ink. In the next task, we're going to set up an Ink Toolbar to handle pen color, saving, and clearing the Notes Ink Canvas. +The default pen for the InkCanvas is a simple black line. Right now, we are also not saving the Ink. In the next task, we're going to set up an Ink Toolbar to handle pen color, saving, and clearing the Notes Ink Canvas. #### Task 2 – Add the Redstone Ink Toolbar #### In Redstone, you'll have the option of adding the Redstone Ink Toolbar to any InkCanvas. We're going to use a preview of the toolbar for our Notes InkCanvas. The toolbar is customizable, so we'll also add our custom Save button to it. -1. Notice that the `xmlns:c="using:InkToolbarPreview"` namespace has been added to the top-level **UserControl** in **SightDetailControl.xaml**. We've added an **InkToolbar** example for image annotation, and it is also using this namespace. We've also added the `using InkToolbarPreview` namespace to the **SightsDetailControl** code-behind. +1. Notice that the `xmlns:c="using:InkToolbarPreview"` namespace has been added to the top-level **<Page>** element at the top of **SightDetailPage.xaml**. We've added an **InkToolbar** example for image annotation, and it is also using this namespace. We've also added the `using InkToolbarPreview` namespace to the **SightDetailPage** code-behind. -1. Expand the **M2_InkToolbar** snippet after the EnableInkButton in **SightDetailControl.xaml**. +1. Expand the **M2_InkToolbar** snippet after the EnableInkButton in **SightDetailPage.xaml**. (Code Snippet - _M2_InkToolbar_) @@ -176,7 +181,7 @@ In Redstone, you'll have the option of adding the Redstone Ink Toolbar to any In > **Notes:** One of the buttons on the Toolbar is commented out. We'll enable it in the next task. For now, you can ignore it. -1. Open the **SightDetailControl** code-behind. +1. Open the **SightDetailPage** code-behind. 1. Scroll down to the `#region NotesInkToolbar` and expand the **M2_SaveUndo** snippet in the region. @@ -210,7 +215,7 @@ In Redstone, you'll have the option of adding the Redstone Ink Toolbar to any In > **Note:** The **EraserClearAll()** method already exists for the image annotation InkToolbar, so we are reusing it for the Notes Ink Toolbar clear method as well. - > There is also a style already defined for the image annotation InkToolbar, which makes Red, Green, and Blue ink available. The style is defined in **SightDetailControl.xaml**. + > There is also a style already defined for the image annotation InkToolbar, which makes Red, Green, and Blue ink available. The style is defined in **SightDetailPage.xaml**. 1. Expand the **M2_SetupNotes** snippet inside the **SetupNotesInkAsync** task in the code-behind. This method restores Ink that has been saved to the Sight. @@ -237,16 +242,16 @@ In Redstone, you'll have the option of adding the Redstone Ink Toolbar to any In ![The InkToolbar](Images/notes_inktoolbar.png "The InkToolbar") - *__Figure__: The InkToolbar.* + _The InkToolbar_ #### Task 3 - Adding OCR Ink to text capability #### Now that we've added the ability to record notes with Ink, it would be useful to recognize those notes as text. In this task, we're going to add the ability to use Optical Character Recognition to convert Ink notes to text. -1. Uncomment the remaining **InkToolbarCustomToggleButton** on the Notes InkToolbar in **SightDetailControl.xaml**. This button will pop open a dialog where the user can complete the speech recognition process. +1. Uncomment the remaining **InkToolbarCustomToggleButton** on the Notes InkToolbar in **SightDetailPage.xaml**. This button will pop open a dialog where the user can complete the speech recognition process. -1. Next, let's create the dialog. Expand the **M2_OcrDialog** snippet at the bottom of the main Grid in the **SightsDetailControl** XAML. +1. Next, let's create the dialog. Expand the **M2_OcrDialog** snippet at the bottom of the main Grid in the **SightDetailPage** XAML. (Code Snippet - _M2_OcrDialog_) @@ -276,7 +281,7 @@ Now that we've added the ability to record notes with Ink, it would be useful to If the result is acceptable, the user can select the primary key on the dialog to finalize the conversion. If not acceptable, the user can cancel and return to the InkCanvas. -1. Open the **SightDetailControl** code-behind and expand the **M2_Recognizers** snippet above the constructor. +1. Open the **SightDetailPage** code-behind and expand the **M2_Recognizers** snippet above the constructor. (Code Snippet - _M2_Recognizers_) @@ -425,7 +430,7 @@ Now that we've added the ability to record notes with Ink, it would be useful to ![Recognize handwritten text with OCR](Images/handwriting.png "Recognize handwritten text with OCR") - *__Figure__: Recognize handwritten text with OCR.* + _Recognize handwritten text with OCR_ ### Exercise 2: Cortana Integration and Speech Commands ### @@ -880,7 +885,7 @@ Voice commands give your users a convenient, hands-free way to interact with you ![Nearby Sights](Images/nearby_sights.png "Nearby Sights") - *__Figure__: Nearby Sights display as content tiles in the Cortana pane.* + _Nearby Sights display as content tiles in the Cortana pane_ ### Exercise 3: Implementing interactive toast notifications ### @@ -890,7 +895,7 @@ Voice commands give your users a convenient, hands-free way to interact with you Toast notifications are a great way to quickly interact with a user outside of an app. In this task, we're going to build and trigger a toast notification for a Sight when it is added to My Sights. -1. Open **SightDetailControl.xaml** and expand the **M2_DatePicker** snippet after the **Caption** TextBlock. This snippet includes a **CalendarDatePicker** and **TimePicker**. +1. Open **SightDetailPage.xaml** and expand the **M2_DatePicker** snippet after the **Caption** TextBlock. This snippet includes a **CalendarDatePicker** and **TimePicker**. (Code Snippet - _M2_DatePicker_) @@ -962,7 +967,7 @@ Toast notifications are a great way to quickly interact with a user outside of a ![Interactive Toast Notification](Images/toast.png "Interactive Toast Notification") - *__Figure__: The interactive toast notification.* + _The interactive toast notification_ ## Summary ## diff --git a/Workshop/Module2-MorePersonalComputing/Source/Setup/scripts/snippets/MorePersonalComputing.vsi b/Workshop/Module2-MorePersonalComputing/Source/Setup/scripts/snippets/MorePersonalComputing.vsi index d5e50cb..46adc89 100644 Binary files a/Workshop/Module2-MorePersonalComputing/Source/Setup/scripts/snippets/MorePersonalComputing.vsi and b/Workshop/Module2-MorePersonalComputing/Source/Setup/scripts/snippets/MorePersonalComputing.vsi differ diff --git a/Workshop/Module3-ConnectedApps/Images/ProjectSettings.png b/Workshop/Module3-ConnectedApps/Images/ProjectSettings.png new file mode 100644 index 0000000..a7c8550 Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Images/ProjectSettings.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/Unauthorized.png b/Workshop/Module3-ConnectedApps/Images/Unauthorized.png new file mode 100644 index 0000000..933f18c Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Images/Unauthorized.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/Uninstall.png b/Workshop/Module3-ConnectedApps/Images/Uninstall.png new file mode 100644 index 0000000..a1f2f59 Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Images/Uninstall.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/debug_mode.png b/Workshop/Module3-ConnectedApps/Images/debug_mode.png index eb3c3eb..de9744d 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/debug_mode.png and b/Workshop/Module3-ConnectedApps/Images/debug_mode.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/directions_button.png b/Workshop/Module3-ConnectedApps/Images/directions_button.png index 2ad966b..967de8e 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/directions_button.png and b/Workshop/Module3-ConnectedApps/Images/directions_button.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/drag_and_drop.png b/Workshop/Module3-ConnectedApps/Images/drag_and_drop.png index d66e9fd..95fd19a 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/drag_and_drop.png and b/Workshop/Module3-ConnectedApps/Images/drag_and_drop.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/empty_extensions_list.png b/Workshop/Module3-ConnectedApps/Images/empty_extensions_list.png index aa875a1..cb9cc0c 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/empty_extensions_list.png and b/Workshop/Module3-ConnectedApps/Images/empty_extensions_list.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/enable_extension.png b/Workshop/Module3-ConnectedApps/Images/enable_extension.png index 27520b3..fdeaf67 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/enable_extension.png and b/Workshop/Module3-ConnectedApps/Images/enable_extension.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/launchforresults_button.png b/Workshop/Module3-ConnectedApps/Images/launchforresults_button.png index 7bafdee..61af7a1 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/launchforresults_button.png and b/Workshop/Module3-ConnectedApps/Images/launchforresults_button.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/open_as_code.png b/Workshop/Module3-ConnectedApps/Images/open_as_code.png index f5c12a5..5fafc07 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/open_as_code.png and b/Workshop/Module3-ConnectedApps/Images/open_as_code.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/quickstart_app.png b/Workshop/Module3-ConnectedApps/Images/quickstart_app.png index ba52d9f..30c335e 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/quickstart_app.png and b/Workshop/Module3-ConnectedApps/Images/quickstart_app.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/share_button.png b/Workshop/Module3-ConnectedApps/Images/share_button.png index 92b7ad2..9ff09c2 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/share_button.png and b/Workshop/Module3-ConnectedApps/Images/share_button.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/synchronising.png b/Workshop/Module3-ConnectedApps/Images/synchronising.png new file mode 100644 index 0000000..501aed2 Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Images/synchronising.png differ diff --git a/Workshop/Module3-ConnectedApps/Images/uninstall_extension.png b/Workshop/Module3-ConnectedApps/Images/uninstall_extension.png index cbc175b..6b0a750 100644 Binary files a/Workshop/Module3-ConnectedApps/Images/uninstall_extension.png and b/Workshop/Module3-ConnectedApps/Images/uninstall_extension.png differ diff --git a/Workshop/Module3-ConnectedApps/README.md b/Workshop/Module3-ConnectedApps/README.md index 2fb30a3..fd83108 100644 --- a/Workshop/Module3-ConnectedApps/README.md +++ b/Workshop/Module3-ConnectedApps/README.md @@ -1,4 +1,4 @@ - + # Connected Apps: Across Devices and App-to-App # --- @@ -7,7 +7,8 @@ ## Overview ## Windows 10 Redstone comes with more ways than ever before to create great user experiences across devices and across apps. -In this lab, you will learn how to connect your app to the cloud so users can get their data on whichever device they pick up. You will enhance the app to load additional app data through an App Extension. You will use the LaunchUri and LaunchForResults APIs to get directions from BingMaps and to connect to a photo processing apps to add effects to pictures. +In this module, you will learn how to connect your app to the cloud so users can get their data on whichever device they pick up. +You will enhance the app to load additional app data through an App Extension, and you will use the LaunchUri and LaunchForResults APIs to get directions from BingMaps and to connect to a photo processing apps to add effects to pictures. You will add capabilities to the app to share files and content with other apps by adding support for Drag and Drop and the Share contract. @@ -40,16 +41,13 @@ The following is required to complete this module: This module includes the following exercises: -1. [Connecting the app to an Azure App Service Mobile Apps cloud backend](#Exercise1) -1. [Loading resources from App Extensions](#Exercise2) -1. [Launching other apps using Launch Uri and Launch for Results](#Exercise3) -1. [Sharing files and content with other apps](#Exercise4) +1. [Connecting the app to an Azure App Service Mobile Apps cloud backend](#Exercise1) +1. [Loading resources from App Extensions](#Exercise2) +1. [Launching other apps using Launch Uri and Launch for Results](#Exercise3) +1. [Sharing files and content with other apps](#Exercise4) -Estimated time to complete this module: **40 to 50 minutes** - - -Let’s get started with Azure. +Estimated time to complete this module: **40 to 50 minutes**. ### Exercise 1: Connecting the app to an Azure App Service Mobile Apps cloud backend ### @@ -57,10 +55,126 @@ Let’s get started with Azure. #### Task 1 - Connect the app to the cloud #### -Introduction +The UWP makes it easy to create an app that runs across many different device families, but when designing a mobile app, you should think about how you will create a **Connected Mobile Experience** for your users. Users should enjoy a great experience from your app whichever of their devices they use, and for many apps that means you will need to store some data in the cloud. + +You can store data in the user's OneDrive, or store small amounts of data in the roaming folders and settings, but for a fully featured mobile Backend as a Service, you can use Microsoft Azure App Service Mobile App. In the [Azure portal](http://portal.azure.com), you can easily create a mobile REST service backed by cloud storage such as SQL Azure and implement authentication, push notifications and more, with support for cross-platform clients. + +Unfortunately, time does not allow us to cover creation of the backend service nor a deep dive on the code to interact with the backend service, but in this task you will connect the app to a pre-built backend service. To learn more about creating and programming an Azure App Service Mobile App, see [https://azure.microsoft.com/en-us/documentation/learning-paths/appservice-mobileapps/](https://azure.microsoft.com/en-us/documentation/learning-paths/appservice-mobileapps/). + +1. Open the starter project at **C:\Labs\CodeLabs-UWP\Workshop\Module3-ConnectedApps\Source\Begin\Microsoft.Labs.Sights2See\Microsoft.Labs.Sights2See.sln**. + +1. Once the project has opened, set your Solution Configuration to **Debug** and your Solution Platform to **x86**. Select **Local Machine** from the Debug Target dropdown menu. + + ![Debug Configuration](Images/debug_mode.png "Debug Configuration") + + _Configure your app to run on the Local Machine_ + +1. If you have run through modules 1 and 2 of this workshop, you will have been working with an app that only stored data locally, in a SQLite database stored in the local folder for the app. This means that when the user selects sights for their trip, or enters notes or ink annotations, they take effect only on the device where the app is running. If the user opens the same app on a different device, all their work entered on the original device is not visible. This is not a good connected mobile experience. + + You will now switch the app to store data using Azure Mobile Apps APIs. These connect to the cloud service to store data. + + Switching has been made easy in this app because all data storage operations are encapsulated in a data model service, described by an interface we have created called **IDataModelService**. You can find this interface in the **Microsoft.Labs.SightsToSee.Library** project, in **Services\DataModelService\IDataModelService.cs**. Up until now in this workshop, the app has been using SqliteDataModelService which implements IDataModelService and which stores data locally. You will now switch to use AzureDataModelService which implements the same interface but which works against the cloud. + +1. In the **Services\DataModelService** folder, open **DataModelServiceFactory.cs**. As you can see, this class selects either SqliteDataModelService or AzureDataModelService dependent on the value of the **SQLITE** preprocessor directive. + + ````C# + public static class DataModelServiceFactory + { + private static IDataModelService _dataModelService; + + public static IDataModelService CurrentDataModelService() + { + if (_dataModelService == null) + { +#if SQLITE + _dataModelService = new SqliteDataModelService(); +#else + _dataModelService = new AzureDataModelService(); +#endif + } + + return _dataModelService; + } + } + ```` + +1. Right-click the **Microsoft.Labs.SightsToSee.Library** Project and then click **Properties** on the context menu to open the Properties window. Select the **Build** tab, and select **All Platforms** in the **Platform** dropdown. Edit the **Conditional compilation symbols** to remove **SQLITE**. + + ![Remove SQLITE from Conditional compilation symbols](Images/ProjectSettings.png "Remove SQLITE from Conditional compilation symbols") + + _Remove SQLITE from Conditional compilation symbols_ + +1. Repeat this for the **Microsoft.Labs.Sights2See** project properties. There are a few changes to the logic in this project to handle initialization of the cloud service storage at the appropriate time of app startup, and these changes are also conditional on the SQLITE preprocessor directive being undefined. + +1. Rebuild the solution and run the project on the local machine. You will see the app startup and the first thing it does is connects to the cloud to try to synchronize data, but you will see an error message that Sync fails. + + ![Sync fails with an Unauthorized error](Images/Unauthorized.png "Sync fails with an Unauthorized error") + + _Sync fails with an Unauthorized error_ + + The sync fails because the Azure App Service Mobile Apps service has been configured to require authentication using Microsoft Account credentials, and we do not have any logic in the app yet to perform authentication and to get an authorization token that it can then pass to the Azure backend in order to get access to the service. + + Authentication is required to identify each user of the application so that data stored in the SQL Azure database (the cloud data backing store for this service) can be partitioned per user effectively. In the Trip object definition in the cloud service, there is an extra column of **User** which is set by the backend logic based on the authenticated identity. + + Note that although this service has been setup to require Microsoft Account authentication, Azure App Service Mobile App also supports authentication against Twitter, Google, Facebook, Azure Active Directory or a custom authentication provider. + + +### Task 2 - Implement authentication + +In this task, you will enable client-directed authentication for this application. With an Azure App Service Mobile App, you can set up your app so that the server handles the authentication process whereby the client calls the backend service and the service displays the login dialog to the end user. -1. tbd +In this application, you will enable client-directed authentication, where the client app handles authentication, gets the authorization token and then passes that to the backend service with every service request. By doing this on the client side, we can use the UWP **WebAuthenticationCoreManager** API to perform the authentication, which has the advantage that if run on a system where the user has already logged in with a Microsoft Account, the app can get an authentication token without prompting the user for credentials - a true Single Sign On (SSO) experience. + +1. In the **Microsoft.Labs.Sights2See.Library** project, open file **Services\DataModelService\AzureDataModelService.cs**. Find the method **AuthenticateAsync** and uncomment the call to the AuthenticationService where directed, and remove or comment out the last two lines of this method. It should now look like the following. + ````C# + public async Task> AuthenticateAsync() + { + await InitializeAsync(); + + // Authentication required for the cloud storage + // M3_Exercise_1_Task_2 + // UNCOMMENT the next line + return await new AuthenticationService(this.client).AuthenticateAsync(); + + // This is the async equivalent of an empty method body + // M3_Exercise_1_Task_2 + // REMOVE the next two lines + //await Task.FromResult(true); + //return Tuple.Create(true, string.Empty); + } + ```` + +1. Right-click **AuthenticationService** in the code line you have just uncommented, and then click **Go to Definition** in the context menu. + + Examine the class you have just opened. You will see that it includes code for either server-directed or client-directed authentication, with the latter enabled currently because the SERVER_INITIATED compilation constant is not currently defined. You can uncomment the #define at the top of this class later on in your own time, if you want to see the user experience that offers. + + However, in this workshop, we are using the client-directed authentication stream which is further down in the class. The **MSAAuthenticationHelper** class at the bottom of this code file shows how to use the UWP **WebAuthenticationCoreManager** API to get an authorization token for a Microsoft Account, which it will be able to do silently if the user has already signed onto their Windows 10 device using MSA credentials. + +1. Before you run the app again, as a precaution UNINSTALL the app from your system. This will ensure that the WebAuthenticationCoreManager identoty provider sees your app as a new one and initializes correctly. Click the Windows button bottom left, click **All Apps** and navigate to the Sights2See app in the apps list. Right-click the Sights2See app and then click **Uninstall**. + + ![Uninstall the Sights2See app](Images/Uninstall.png "Uninstall the Sights2See app") + + _Uninstall the Sights2See app_ + +1. Run the app again. This time you will be prompted for credentials if you are logged onto a Windows 10 device and you haven't used Microsoft Account credentials to do so, or if your machine account has already authenticated using MSA credentials, you will connect to the cloud service silently. + + ![Synchronizing data with the cloud service](Images/synchronising.png "Synchronizing data with the cloud service") + + _Synchronizing data with the cloud service_ + +1. Now, use the app as normal: select some Sights for your trip, enter some Notes on a sight, use inking to annotate a picture. Next you will view those same changes on a different device! + + +### Task 3 - Run the connected app on different devices, synchronizing data through the cloud + +With your data stored in the cloud, now the user can enjoy your app whichever of their devices they pick up. + +1. Close the app if it is still running. + +1. Now either select the **Mobile Emulator Preview 10.0.14291.0 WVGA 4inch 512MB** device in the target dropdown, or if you want to use a real phone, connect the phone to your PC using a USB cable and then change the Solution Platform to **ARM** and select **Device** as the output device. + +1. Run the app from Visual Studio and it will launch on your mobile emulator or real Windows 10 mobile device. You will have to log in with MSA credentials and then the app will synchronize with the cloud. You will see the same selection of Sights and the same inking annotations you created previously on the PC. ### Exercise 2: Loading resources from App Extensions ### @@ -72,120 +186,129 @@ App Extensions allow you to add data to your application from other UWP Store ap You can install and uninstall extension apps while the host app is running, and it will raise appropriate events to add and remove data as required without relaunching. -For this module, we've added a project called __AdditionalSights__ to the solution. The AdditionalSights app is an extension app that provides six more Sights in a __json__ file. +For this module, we've added a project called **AdditionalSights** to the solution. The AdditionalSights app is an extension app that provides six more Sights in a **json** file. Let's take a look at how the extension app is set up. -1. Open the AdditionalSights __Package.appxmanifest__ as code. +1. Open the AdditionalSights **Package.appxmanifest** as code. + + > **Note:** The extension type we're using is in preview, so it isn't yet available in the Manifest Editor. - >__Note:__ The extension type we're using is in preview, so it isn't yet available in the Manifest Editor. + ![View the Package manifest as code](Images/open_as_code.png "View the Package manifest as code") - ![View the Package manifest as code](Images/open_as_code.png "View the Package manifest as code") + _View the Package manifest as code_ - *__Figure__: View the Package manifest as code.* +1. The extension is declared in the manifest with the category **windows.appExtension**. It exposes up a name, display name, description, and **Public** folder. The Public folder is where we'll find the consumable data. - The extension is declared in the manifest with the category __windows.appExtension__. It exposes up a name, display name, description, and __Public__ folder. The Public folder is where we'll find the consumable data. + ````XML + + + + + + ```` - ```XML - - - - - - ``` +1. Expand the **Public** folder. You will see the json file containing additional Sights. We'll install the extension app later on. -1. Expand the __Public__ folder. You will see the json file containing additional Sights. We'll install the extension app later on. +1. Let's move over to **SightsToSee**, which is our host app. Open the SightsToSee **Package.appxmanifest** as code. -1. Let's move over to __SightsToSee__, which is our host app. - Open the SightsToSee __Package.appxmanifest__ as code. +1. Expand the **M3_ExtensionHost** snippet inside the **Extension** node. This code declares the SightsToSee app as a host for extensions with the name **SanFranPack.1.0**. -1. Expand the __M3_ExtensionHost__ snippet inside the __Extension__ node. + (Code Snippet - _M3_ExtensionHost_) - ```XML + ````XML SanFranPack.1.0 - ``` + ```` - What it does: - - Declares the SightsToSee app as a host for extensions with the name SanFranPack.1.0 +1. In the **SightsToSee** app, open the helper **Services > App Extensions > ExtensionManager.cs**. We've provided this helper to manage the loading and unloading of extensions. -1. In the SightsToSee app, open the helper __Services > App Extensions > ExtensionManager.cs__. + ##### **Initialize()** ##### - We've provided this helper to manage the loading and unloading of extensions. + - We're going to call **Initialize** from **App.xaml.cs**. - ##### __Initialize()__ ##### + - This is where we hook up event handlers to deal with adding and removing app extensions. - - We're going to call __Initialize__ from __App.xaml.cs__. + - Extensions can be loaded and unloaded dynamically while the host app is running. - - This is where we hook up event handlers to deal with adding and removing app extensions. + - By default, when you call **Initialize** on app startup, it will load extensions that have already been installed. The event handlers handle loading and unloading of extensions that are installed or uninstalled after that point. - - Extensions can be loaded and unloaded dynamically while the host app is running. + ##### Scroll down to the **Load** task ##### - - By default, when you call __Initialize__ on app startup, it will load extensions that have already been installed. The event handlers handle loading and unloading of extensions that are installed or uninstalled after that point. + - If an extension is enabled but not yet loaded, this task will check to make sure it is OK to load. - ##### Scroll down to the __Load__ task. ##### + - It will then create a local copy of the json file from the extension app and load the new Sights into the trip. - - If an extension is enabled but not yet loaded, this task will check to make sure it is OK to load. + ##### Unload ##### - - It will then create a local copy of the json file from the extension app and load the new Sights into the trip. + - The **Unload** task first gets the data again from the extension app - ##### Unload ##### + - It uses a lock to make sure the user can't rerun the code until it has completed - - The __Unload__ task first gets the data again from the extension app + > **Note:** Code inside a **lock** can't be awaited. - - It uses a lock to make sure the user can't rerun the code until it has completed + - It then checks the Sights in the extension file against the existing Sights in the app. If they are not in **My Sights**, the extension Sights will be removed. - >__Note:__ Code inside a __lock__ can't be awaited. +1. Now that we've seen how extensions work and that we have the helper to support loading and unloading, we can set up the app to do the work. Open **App.xaml.cs** - - It then checks the Sights in the extension file against the existing Sights in the app. If they are not in __My Sights__, the extension Sights will be removed. +1. Expand the **M3_ExtensionManager** snippet anywhere in the **App** class. This code creates a new ExtensionManager to handle extensions with the name **SanFranPack.1.0** and sets up an ExtensionManager property that we can access. -1. Now that we've seen how extensions work and that we have the helper to support loading and unloading, we can set up the app to do the work. Open __App.xaml.cs__ + (Code Snippet - _M3_ExtensionManager_) -1. Expand the __M3_ExtensionManager__ snippet anywhere in the __App__ class. This code creates a new ExtensionManager to handle extensions with the name __SanFranPack.1.0__ and sets up an ExtensionManager property that we can access. + ````C# + public ExtensionManager ExtensionManager + { + get { return _extensionManager; } + } -1. In the __OnLaunched__ method, expand the __M3_Initialize__ snippet after the VCD load. This line will call the __Initialize()__ method in the ExtensionManager helper on app startup. + private ExtensionManager _extensionManager = new Facts.ExtensionManager("SanFranPack.1.0"); + ```` -1. We've added some basic options to the app settings page to support loading and unloading of app extensions. Open __Views > SettingsPage.xaml__. Uncomment the __Extensions ListView__. This list will populate when extensions are available. +1. In the **OnLaunched** method, expand the **M3_Initialize** snippet after the VCD load. This line will call the **Initialize()** method in the ExtensionManager helper on app startup. -1. Open __ViewModels > SettingsPageViewModel.cs__. Uncomment the __Extensions ObservableCollection__. + (Code Snippet - _M3_Initialize_) -1. Deploy the __SightsToSee__ app and run it from the Start Menu. Navigate to the __Settings__ page. + ````C# + ExtensionManager.Initialize(); + ```` - You can see that the Extensions list is empty. +1. We've added some basic options to the app settings page to support loading and unloading of app extensions. Open **Views > SettingsPage.xaml**. Uncomment the **Extensions ListView**. This list will populate when extensions are available. - Before we can load the extension, it first needs to be installed on the machine. You can install an extension app directly from the Windows Store, sideload it, or deploy it if you have the source code. +1. Open **ViewModels > SettingsPageViewModel.cs**. Uncomment the **Extensions ObservableCollection**. + +1. Deploy the **SightsToSee** app and run it from the Start Menu. Navigate to the **Settings** page. You can see that the Extensions list is empty. Before we can load the extension, it first needs to be installed on the machine. You can install an extension app directly from the Windows Store, sideload it, or deploy it if you have the source code. ![The Empty Extensions List](Images/empty_extensions_list.png "The Empty Extensions List") - *__Figure__: The extensions list is empty until an extension app is installed.* + _The extensions list is empty until an extension app is installed_ -1. Since we have the source code, Deploy the __AdditionalSights__ project to install it. Keep the __Settings__ page open as it deploys. +1. Since we have the source code, Deploy the **AdditionalSights** project to install it. Keep the **Settings** page open as it deploys. - >__Note:__ You can sideload an appx bundle with PowerShell commands. + > **Note:** You can sideload an appx bundle with PowerShell commands. - - As soon as the __AddtionalSights__ app is installed, it appears in the __Extensions__ list. Use the toggle to enable it. + - As soon as the **AddtionalSights** app is installed, it appears in the **Extensions** list. Use the toggle to enable it. ![Enable the extension app](Images/enable_extension.png "Enable the extension app") - *__Figure__: Enable the extension app.* + _Enable the extension app_ -1. Open your San Francisco trip to see the new Sights that have been added. Add one to __My Sights__. +1. Open your San Francisco trip to see the new Sights that have been added. Add one to **My Sights**. -1. Find the AdditionalSights app in the Start Menu. Right-click and uninstall it. +1. Find the **AdditionalSights** app in the **Start Menu**. Right-click and uninstall it. ![Uninstall the extension app](Images/uninstall_extension.png "Uninstall the extension app") - *__Figure__: Uninstall the extension app.* + _Uninstall the extension app_ 1. Return to the San Francisco trip. Any additional Sights that weren't added to My Sights will have been removed. ### Exercise 3: Launching other apps using Launch Uri and Launch for Results ### -Inter-app communication can be used to +Inter-app communication can be used to: - Launch links in a Web browser @@ -204,75 +327,129 @@ We're going launch the Maps app and use it to get directions to a Sight. Then we Let's start with a simple LaunchUri scenario. -1. Open __SightDetailPage.xaml__ and expand the __M3_DirectionsButton__ snippet in the __TitleCommandBar__. +1. Open **SightDetailPage.xaml** and expand the **M3_DirectionsButton** snippet in the **TitleCommandBar**. -1. Expand the __M3_MobileDirectionsButton__ snippet in the __MobileCommandBar__. + (Code Snippet - _M3_DirectionsButton_) - >__Note:__ There are two command bars because of the adaptive design of the app. Current design guidelines recommend that Mobile command bars appear at the bottom of the page and that Desktop command bars appear at the top. + ````C# + + + + + + ```` -1. The app bar buttons you just added are hooked up to a __GetDirectionsAsync__ method in the view model. In the next step, we'll create that method. +1. Expand the **M3_MobileDirectionsButton** snippet in the **MobileCommandBar**. -1. Open the __SightDetailPageViewModel__. Expand the __M3_GetDirections__ snippet anywhere in the view model. + > **Note:** There are two command bars because of the adaptive design of the app. Current design guidelines recommend that Mobile command bars appear at the bottom of the page and that Desktop command bars appear at the top. - What it does: + (Code Snippet - _M3_MobileDirectionsButton_) + + ````C# + + + + + + ```` + +1. The app bar buttons you just added are hooked up to a **GetDirectionsAsync** method in the view model. In the next step, we'll create that method. + +1. Open the **SightDetailPageViewModel**. Expand the **M3_GetDirections** snippet anywhere in the view model. + + (Code Snippet - _M3_GetDirections_) - - The mapsUri is set to use the bingmaps: protocol. + ````C# + public async void GetDirectionsAsync() + { + var mapsUri = + new Uri($@"bingmaps:?rtp=~pos.{CurrentSight.Latitude}_{CurrentSight.Longitude}_{CurrentSight.Name}"); - - The mapsUri uses a query to pass the current Sight's latitude, longitude, and name. + // Launch the Windows Maps app + var launcherOptions = new LauncherOptions(); + launcherOptions.TargetApplicationPackageFamilyName = "Microsoft.WindowsMaps_8wekyb3d8bbwe"; + await Launcher.LaunchUriAsync(mapsUri, launcherOptions); + } + ```` - >__Note:__ the mapsUri string is constructed using C# 6 string interpolation. + What it does: - - We're using __LauncherOptions__ to specify a package family name for the target app. The package family name ensures that only the official Maps app will launch. + - The mapsUri is set to use the bingmaps: protocol. - >__Note:__ If you leave out the package family name, your user can choose between all apps on the system that uses the protocol specified in the launch URI. + - The mapsUri uses a query to pass the current Sight's latitude, longitude, and name. - - __LaunchUriAsync__ starts the app associated with the bingmaps protocol and the package family name defined in the LauncherOptions. + > **Note:** the mapsUri string is constructed using C# 6 string interpolation. + + - We're using **LauncherOptions** to specify a package family name for the target app. The package family name ensures that only the official Maps app will launch. + + > **Note:** If you leave out the package family name, your user can choose between all apps on the system that uses the protocol specified in the launch URI. + + - **LaunchUriAsync** starts the app associated with the bingmaps protocol and the package family name defined in the LauncherOptions. 1. Build and run your app. Open a Sight detail page and use the directions button on the app bar to launch the Maps app. ![The Directions Button](Images/directions_button.png "The Directions Button") - *__Figure__: Use the Directions button on the app bar to launch the Maps app and get directions to the Sight.* + _Use the Directions button on the app bar to launch the Maps app and get directions to the Sight_ -#### Task 2 - Connect to another app using LaunchUriForResults #### +#### Task 2 - Connect to another app using LaunchUriForResults #### -Beyond launching a target app and passing it data, we can launch an app and receive results back. +Beyond launching a target app and passing it data, we can launch an app and receive results back. We're going to add a button to the InkToolbar we're using for image annotation and use it to launch a photoprocessing app. The photoprocessing app will apply a Lumia filter to the image and return the altered version to our SightsToSee app. -- We're going to add a button to the InkToolbar we're using for image annotation and use it to launch a photoprocessing app. +1. Before we can launch the photoprocessing app, we need to install it on the system. Open the **<LabRoot>\Module3\Begin\ImageProcessingApp\PhotoEditingLaunchForResults.sln** solution. -- The photoprocessing app will apply a Lumia filter to the image and return the altered version to our SightsToSee app. +1. Set the **QuickStart** project as the **StartUp Project** if it isn't already. The QuickStart app applies a black and white Lumia filter to a photograph and allows the user to adjust brightness. +1. Build and deploy the **QuickStart** app. -1. Before we can launch the photoprocessing app, we need to install it on the system. Open the __<LabRoot>\Module3\Begin\ImageProcessingApp\PhotoEditingLaunchForResults.sln__ solution. + ![The QuickStart App](Images/quickstart_app.png "The QuickStart App") -1. Set the __QuickStart__ project as the __StartUp Project__ if it isn't already. The QuickStart app applies a black and white Lumia filter to a photograph and allows the user to adjust brightness. + _The QuickStart app_ -1. Build and deploy the __QuickStart__ app. +1. Return to the **SightsToSee** Module 3 solution and open **SightDetailPage.xaml**. - ![The QuickStart App](Images/quickstart_app.png "The QuickStart App") +1. Add a new button to the ImageInkToolbar by expanding the **M3_LaunchButton** snippet after the Undo button. + + (Code Snippet - _M3_LaunchButton_) - *__Figure__: The QuickStart app.* + ````C# + + + + ```` -1. Return to the __SightsToSee__ Module 3 solution and open __SightDetailPage.xaml__. +1. Open the **SightDetailPage** code-behind. Find the **OnLaunchForResults** event handler. -1. Add a new button to the ImageInkToolbar by expanding the __M3_LaunchButton__ snippet after the Undo button. +1. Expand the **M3_OpenPicker** snippet inside the event handler. -1. Open the __SightDetailPage__ code-behind. Find the __OnLaunchForResults__ event handler. + (Code Snippet - _M3_OpenPicker_) -1. Expand the __M3_OpenPicker__ snippet inside the event handler. + ````C# + FileOpenPicker openPicker = new FileOpenPicker(); + openPicker.ViewMode = PickerViewMode.Thumbnail; + openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + openPicker.FileTypeFilter.Add(".jpg"); + openPicker.FileTypeFilter.Add(".jpeg"); + openPicker.FileTypeFilter.Add(".png"); + + StorageFile file = await openPicker.PickSingleFileAsync(); + ```` What it does: - - Creates a new __FileOpenPicker__ in thumbnail mode + - Creates a new **FileOpenPicker** in thumbnail mode - - Sets the suggested start location to the user's __Picture Library__ + - Sets the suggested start location to the user's **Picture Library** - - Looks for files of type __jpg__, __jpeg__, and __png__ + - Looks for files of type **jpg**, **jpeg**, and **png** - Creates a storage file to save the user's image selection locally -1. Now that we have an image, we can send it to our photoprocessing app. Expand the __M3_LaunchForResults__ snippet below the previous snippet in the __OnLaunchForResults__ event handler. +1. Now that we have an image, we can send it to our photoprocessing app. Expand the **M3_LaunchForResults** snippet below the previous snippet in the **OnLaunchForResults** event handler. What it does: @@ -286,17 +463,15 @@ Beyond launching a target app and passing it data, we can launch an app and rece - If the response status is Success, we copy the new image file to the local folder and add it to the Sight record. -1. Build and run your app. Open a Sight detail page and use the __LaunchForResults__ button on the ImageInkToolbar to launch the file picker. +1. Build and run your app. Open a Sight detail page and use the **LaunchForResults** button on the ImageInkToolbar to launch the file picker. ![The LaunchForResults Button](Images/launchforresults_button.png "The LaunchForResults Button") - *__Figure__: The LaunchForResults Button.* + _The LaunchForResults Button_ 1. Select an image from the filesystem. When the QuickStart app opens, set a brightness level for the modified image. -1. Use the __Save the image__ button to save and return the image to the SightsToSee app. - - You will see the modified image appear in the Sight gallery. +1. Use the **Save the image** button to save and return the image to the SightsToSee app. You will see the modified image appear in the Sight gallery. @@ -307,96 +482,170 @@ Beyond launching a target app and passing it data, we can launch an app and rece Adding drag and drop is a quick way to make your app more user-friendly. We're going to add drag and drop capability to the gallery grid in the SightDetailPage so users can easily add new photos to a Sight. -1. Open __SightDetailPage.xaml__ and find the __GalleryGrid__. +1. Open **SightDetailPage.xaml** and find the **GalleryGrid**. -1. Add the __AllowDrop__, __Drop__, and __DragOver__ attributes to the opening tag of the GalleryGrid (type or copy/paste): +1. Add the **AllowDrop**, **Drop**, and **DragOver** attributes to the opening tag of the GalleryGrid (type or copy/paste): - ```XAML - AllowDrop="True" - Drop="{x:Bind ViewModel.SightFile_DropAsync}" - DragOver="{x:Bind ViewModel.SightFile_DragOver}" - ``` + ````XAML + AllowDrop="True" + Drop="{x:Bind ViewModel.SightFile_DropAsync}" + DragOver="{x:Bind ViewModel.SightFile_DragOver}" + ```` - What it does: + What it does: + + - **AllowDrop="True"** enables the GalleryGrid as a drop target + + - We are wiring up the Drop and DragOver events to methods in the view model. We'll add those methods in the following steps. + +1. Open the **SightDetailPageViewModel**. Expand the **M3_DragOver** snippet anywhere in the view model. - - __AllowDrop="True"__ enables the GalleryGrid as a drop target + (Code Snippet - _M3_DragOver_) - - We are wiring up the Drop and DragOver events to methods in the view model. We'll add those methods in the following steps. + ````C# + public void SightFile_DragOver(object sender, DragEventArgs e) + { + e.AcceptedOperation = DataPackageOperation.Copy; -1. Open the __SightDetailPageViewModel__. Expand the __M3_DragOver__ snippet anywhere in the view model. + // Customize the look of the DragUI + if (e.DragUIOverride != null) + { + e.DragUIOverride.Caption = "Attach photo"; + e.DragUIOverride.IsCaptionVisible = true; + e.DragUIOverride.IsContentVisible = true; + e.DragUIOverride.IsGlyphVisible = true; + } + } + ```` What it does: - - Enables the __Copy__ operation for the originator of the drag event + - Enables the **Copy** operation for the originator of the drag event - - Handles the __DragOver__ event + - Handles the **DragOver** event - - Customizes the __DragUI__ with a caption and content preview + - Customizes the **DragUI** with a caption and content preview -1. Expand the __M3_DropAsync__ snippet below the DragOver event handler. +1. Expand the **M3_DropAsync** snippet below the DragOver event handler. What it does: - Copies the incoming storage items to the local folder - - Calls the __AddSightFileAsync__ task, which creates and saves a SightFile record for each new image + - Calls the **AddSightFileAsync** task, which creates and saves a SightFile record for each new image - - __AddSightFileAsync__ also adds each new image to the CurrentSightFiles observable collection, so they will appear immediately in the gallery grid. + - **AddSightFileAsync** also adds each new image to the CurrentSightFiles observable collection, so they will appear immediately in the gallery grid. - >__Note:__ You can drag and drop multiple images at once. + > **Note:** You can drag and drop multiple images at once. 1. Build and run the app. Drag and drop an image or multiple images onto the gallery grid in a Sight detail view. ![Drag and Drop](Images/drag_and_drop.png "Drag and Drop") - *__Figure__: Drag and drop to add images to the Sight gallery.* + _Drag and drop to add images to the Sight gallery_ #### Task 2 – Using the Share contract to share content with other apps #### The Share contract is an easy way to share data between apps. You can share links, text, photos, and videos. We're going to add a Share button to the Sight detail app bar to share the sight name, photo, and description in HTML format. ->__Note:__ Visit http://msdn.microsoft.com/en-us/library/windows/apps/hh465251.aspx to read the guidelines on sharing content in UWP apps. +> **Note:** Visit http://msdn.microsoft.com/en-us/library/windows/apps/hh465251.aspx to read the guidelines on sharing content in UWP apps. -1. Add the app share button to the command bar in __SightDetailPage.xaml__. There are two command bars: one for Mobile and one for larger windows. +1. Add the app share button to the command bar in **SightDetailPage.xaml**. There are two command bars: one for Mobile and one for larger windows. - - Expand the __M3_ShareButton__ snippet into the __TitleCommandBar__. + - Expand the **M3_ShareButton** snippet into the **TitleCommandBar**. - - Expand the __M3_MobileShareButton__ snippet into the __MobileCommandBar__. + (Code Snippet - _M3_ShareButton_) -1. Now let's add the code to support the share button. Open the __SightDetailPageViewModel__ and expand the __M3_ShareSight__ snippet anywhere in the ViewModel. + ````C# + + + + + + ```` - __What it does:__ + + - Expand the **M3_MobileShareButton** snippet into the **MobileCommandBar**. + + (Code Snippet - _M3_MobileShareButton_) + + ````C# + + + + + + ```` + +1. Now let's add the code to support the share button. Open the **SightDetailPageViewModel** and expand the **M3_ShareSight** snippet anywhere in the ViewModel. + + (Code Snippet - _M3_ShareSight_) + + ````C# + public void ShareSight() + { + var dataTransferManager = DataTransferManager.GetForCurrentView(); + dataTransferManager.DataRequested += DataTransferManager_DataRequested; + + DataTransferManager.ShowShareUI(); + } + ```` + + + **What it does:** - The DataTransferManager initiates an exchange of content with other apps. - - __GetForCurrentView()__ returns the DataTransferManager associated with the current window. + - **GetForCurrentView()** returns the DataTransferManager associated with the current window. + + - We're also subscribing to the **DataRequested** event, which occurs when a share operation starts. + + - **ShowShareUI** opens the system Share flyout. + +1. With the **ShareSight()** method, we've initiated the share operation. Next, we'll handle the **DataRequested** event. Expand the **M3_DataRequested** snippet below the **ShareSight** method. - - We're also subscribing to the __DataRequested__ event, which occurs when a share operation starts. + > **Note:** The Request property lets you access the DataRequest object and give it data or a failure message. - - __ShowShareUI__ opens the system Share flyout. + (Code Snippet - _M3_DataRequested_) -1. With the __ShareSight()__ method, we've initiated the share operation. Next, we'll handle the __DataRequested__ event. + ````C# + private void DataTransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) + { + var request = args.Request; + request.Data.Properties.Title = $"I'm visiting the {CurrentSight.Name}"; + request.Data.Properties.Description = $"{CurrentSight.Description}"; + request.Data.SetText($"{CurrentSight.Description}"); - Expand the __M3_DataRequested__ snippet below the __ShareSight__ method. + var localImage = SightImage.UriSource.AbsoluteUri; + string htmlPayload = $"

{CurrentSight.Description}

"; + var htmlFormat = HtmlFormatHelper.CreateHtmlFormat(htmlPayload); + request.Data.SetHtmlFormat(htmlFormat); - >__Note:__ The Request property lets you access the DataRequest object and give it data or a failure message. + // Because the HTML contains a local image, we need to add it to the ResourceMap. + var streamRef = RandomAccessStreamReference.CreateFromUri(new Uri(localImage)); + request.Data.ResourceMap[localImage] = streamRef; + } + ```` - __What it does:__ + **What it does:** - Sets the Data title and description fields to the current Sight name and description - - Creates an HTML payload string with the __src__ of the `````` tag set to the absolute URI of the Sight image + - Creates an HTML payload string with the **src** of the `````` tag set to the absolute URI of the Sight image - Formats the payload string as HTML - Adds the localImage to the ResourceMap -1. Build and run the app. Open a Sight detail view and use the Share button to initiate the Share process. +1. Build and run the app. Open a Sight detail view and use the **Share** button to initiate the Share process. ![The Share Contract](Images/share_button.png "The Share Contract") - *__Figure__: Use the Share button to open the Share contract.* + _Use the Share button to open the Share contract_ diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj index 6a6d728..8dd9a27 100644 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj +++ b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj @@ -46,7 +46,7 @@ ARM true bin\ARM\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE ;2008 full ARM @@ -123,9 +123,7 @@ SQLite for Universal Windows Platform - - - + ..\libs\Microsoft.WindowsAzure.Mobile.Files.dll @@ -140,7 +138,7 @@ true bin\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;EFCORE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE ;2008 true full diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Services/DataModelService/AzureDataModelService.cs b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Services/DataModelService/AzureDataModelService.cs index 4980237..b8d8aea 100644 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Services/DataModelService/AzureDataModelService.cs +++ b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Services/DataModelService/AzureDataModelService.cs @@ -54,7 +54,15 @@ public async Task> AuthenticateAsync() await InitializeAsync(); // Authentication required for the cloud storage - return await new AuthenticationService(this.client).AuthenticateAsync(); + // M3_Exercise_1_Task_2 + // UNCOMMENT the next line + //return await new AuthenticationService(this.client).AuthenticateAsync(); + + // This is the async equivalent of an empty method body + // M3_Exercise_1_Task_2 + // REMOVE the next two lines + await Task.FromResult(true); + return Tuple.Create(true, string.Empty); } public async Task> LoadTripsAsync() diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs deleted file mode 100644 index b90536f..0000000 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs +++ /dev/null @@ -1,3192 +0,0 @@ -// -// Copyright (c) 2009-2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE -#define USE_CSHARP_SQLITE -#endif - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else -using Sqlite3DatabaseHandle = System.IntPtr; -using Sqlite3Statement = System.IntPtr; -#endif - -namespace Microsoft.Labs.SightsToSee.Library.Sqlite -{ - public class SQLiteException : System.Exception - { - public SQLite3.Result Result { get; private set; } - - protected SQLiteException(SQLite3.Result r, string message) : base(message) - { - Result = r; - } - - public static SQLiteException New(SQLite3.Result r, string message) - { - return new SQLiteException(r, message); - } - } - - [Flags] - public enum SQLiteOpenFlags - { - ReadOnly = 1, ReadWrite = 2, Create = 4, - NoMutex = 0x8000, FullMutex = 0x10000, - SharedCache = 0x20000, PrivateCache = 0x40000, - ProtectionComplete = 0x00100000, - ProtectionCompleteUnlessOpen = 0x00200000, - ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, - ProtectionNone = 0x00400000 - } - - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable - { - private bool _open; - private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; - private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; - - private int _trasactionDepth = 0; - private Random _rand = new Random(); - - public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); - - public string DatabasePath { get; private set; } - - public bool TimeExecution { get; set; } - - public bool Trace { get; set; } - - public bool StoreDateTimeAsTicks { get; private set; } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false) - : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) - { - DatabasePath = databasePath; - -#if NETFX_CORE - SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); -#endif - - Sqlite3DatabaseHandle handle; - -#if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); -#else - // open using the byte[] - // in the case where the path may include Unicode - // force open to using UTF-8 using sqlite3_open_v2 - var databasePathAsBytes = GetNullTerminatedUtf8(DatabasePath); - var r = SQLite3.Open(databasePathAsBytes, out handle, (int)openFlags, IntPtr.Zero); -#endif - - Handle = handle; - if (r != SQLite3.Result.OK) - { - throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r)); - } - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds(0.1); - } - - static SQLiteConnection() - { - if (_preserveDuringLinkMagic) - { - var ti = new ColumnInfo(); - ti.Name = "magic"; - } - } - - static byte[] GetNullTerminatedUtf8(string s) - { - var utf8Length = System.Text.Encoding.UTF8.GetByteCount(s); - var bytes = new byte[utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic = false; - - /// - /// Sets a busy handler to sleep the specified amount of time when a table is locked. - /// The handler will sleep multiple times until a total time of has accumulated. - /// - public TimeSpan BusyTimeout - { - get { return _busyTimeout; } - set - { - _busyTimeout = value; - if (Handle != NullHandle) - { - SQLite3.BusyTimeout(Handle, (int)_busyTimeout.TotalMilliseconds); - } - } - } - - /// - /// Returns the mappings from types to tables that the connection - /// currently understands. - /// - public IEnumerable TableMappings - { - get - { - if (_tables == null) - { - return Enumerable.Empty(); - } - else { - return _tables.Values; - } - } - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The type whose mapping to the database is returned. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping(Type type) - { - if (_mappings == null) - { - _mappings = new Dictionary(); - } - TableMapping map; - if (!_mappings.TryGetValue(type.FullName, out map)) - { - map = new TableMapping(type); - _mappings[type.FullName] = map; - } - return map; - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping() - { - return GetMapping(typeof(T)); - } - - private struct IndexedColumn - { - public int Order; - public string ColumnName; - } - - private struct IndexInfo - { - public string IndexName; - public string TableName; - public bool Unique; - public List Columns; - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - public int DropTable() - { - var map = GetMapping(typeof(T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute(query); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// - /// The number of entries added to the database schema. - /// - public int CreateTable() - { - return CreateTable(typeof(T)); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// Type to reflect to a database table. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty) - { - if (_tables == null) - { - _tables = new Dictionary(); - } - TableMapping map; - if (!_tables.TryGetValue(ty.FullName, out map)) - { - map = GetMapping(ty); - _tables.Add(ty.FullName, map); - } - var query = "create table if not exists \"" + map.TableName + "\"(\n"; - - var decls = map.Columns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks)); - var decl = string.Join(",\n", decls.ToArray()); - query += decl; - query += ")"; - - var count = Execute(query); - - if (count == 0) - { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable(map); - } - - var indexes = new Dictionary(); - foreach (var c in map.Columns) - { - foreach (var i in c.Indices) - { - var iname = i.Name ?? map.TableName + "_" + c.Name; - IndexInfo iinfo; - if (!indexes.TryGetValue(iname, out iinfo)) - { - iinfo = new IndexInfo - { - IndexName = iname, - TableName = map.TableName, - Unique = i.Unique, - Columns = new List() - }; - indexes.Add(iname, iinfo); - } - - if (i.Unique != iinfo.Unique) - throw new Exception("All the columns in an index must have the same value for their Unique property"); - - iinfo.Columns.Add(new IndexedColumn - { - Order = i.Order, - ColumnName = c.Name - }); - } - } - - foreach (var indexName in indexes.Keys) - { - var index = indexes[indexName]; - const string sqlFormat = "create {3} index if not exists \"{0}\" on \"{1}\"(\"{2}\")"; - var columns = String.Join("\",\"", index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray()); - var sql = String.Format(sqlFormat, indexName, index.TableName, columns, index.Unique ? "unique" : ""); - count += Execute(sql); - } - - return count; - } - - public class ColumnInfo - { - // public int cid { get; set; } - - [Column("name")] - public string Name { get; set; } - - // [Column ("type")] - // public string ColumnType { get; set; } - - // public int notnull { get; set; } - - // public string dflt_value { get; set; } - - // public int pk { get; set; } - - public override string ToString() - { - return Name; - } - } - - public IEnumerable GetTableInfo(string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query(query); - } - - void MigrateTable(TableMapping map) - { - var existingCols = GetTableInfo(map.TableName); - - var toBeAdded = new List(); - - foreach (var p in map.Columns) - { - var found = false; - foreach (var c in existingCols) - { - found = (string.Compare(p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); - if (found) - break; - } - if (!found) - { - toBeAdded.Add(p); - } - } - - foreach (var p in toBeAdded) - { - var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(p, StoreDateTimeAsTicks); - Execute(addCol); - } - } - - /// - /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. - /// - /// - protected virtual SQLiteCommand NewCommand() - { - return new SQLiteCommand(this); - } - - /// - /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' - /// in the command text for each of the arguments. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the command text. - /// - /// - /// A - /// - public SQLiteCommand CreateCommand(string cmdText, params object[] ps) - { - if (!_open) - { - throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); - } - else { - var cmd = NewCommand(); - cmd.CommandText = cmdText; - foreach (var o in ps) - { - cmd.Bind(o); - } - return cmd; - } - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// Use this method instead of Query when you don't expect rows back. Such cases include - /// INSERTs, UPDATEs, and DELETEs. - /// You can set the Trace or TimeExecution properties of the connection - /// to profile execution. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// The number of rows modified in the database as a result of this execution. - /// - public int Execute(string query, params object[] args) - { - var cmd = CreateCommand(query, args); - - if (TimeExecution) - { - if (_sw == null) - { - _sw = new System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteNonQuery(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar(string query, params object[] args) - { - var cmd = CreateCommand(query, args); - - if (TimeExecution) - { - if (_sw == null) - { - _sw = new System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteScalar(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteQuery(map); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); - } - - /// - /// Returns a queryable interface to the table represented by the given type. - /// - /// - /// A queryable object that is able to translate Where, OrderBy, and Take - /// queries into native SQL. - /// - public TableQuery Table() where T : new() - { - return new TableQuery(this); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key. Throws a not found exception - /// if the object is not found. - /// - public T Get(object pk) where T : new() - { - var map = GetMapping(typeof(T)); - return Query(map.GetByPrimaryKeySql, pk).First(); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get(Expression> predicate) where T : new() - { - return Table().Where(predicate).First(); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public T Find(object pk) where T : new() - { - var map = GetMapping(typeof(T)); - return Query(map.GetByPrimaryKeySql, pk).FirstOrDefault(); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The TableMapping used to identify the object type. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public object Find(object pk, TableMapping map) - { - return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault(); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate or null - /// if the object is not found. - /// - public T Find(Expression> predicate) where T : new() - { - return Table().Where(predicate).FirstOrDefault(); - } - - /// - /// Whether has been called and the database is waiting for a . - /// - public bool IsInTransaction - { - get { return _trasactionDepth > 0; } - } - - /// - /// Begins a new transaction. Call to end the transaction. - /// - /// Throws if a transaction has already begun. - public void BeginTransaction() - { - // The BEGIN command only works if the transaction stack is empty, - // or in other words if there are no pending transactions. - // If the transaction stack is not empty when the BEGIN command is invoked, - // then the command fails with an error. - // Rather than crash with an error, we will just ignore calls to BeginTransaction - // that would result in an error. - if (Interlocked.CompareExchange(ref _trasactionDepth, 1, 0) == 0) - { - try - { - Execute("begin transaction"); - } - catch (Exception ex) - { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) - { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) - { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo(null, true); - break; - } - } - else { - // Call decrement and not VolatileWrite in case we've already - // created a transaction point in SaveTransactionPoint since the catch. - Interlocked.Decrement(ref _trasactionDepth); - } - - throw; - } - } - else { - // Calling BeginTransaction on an already open transaction is invalid - throw new System.InvalidOperationException("Cannot begin a transaction while already in a transaction."); - } - } - - /// - /// Creates a savepoint in the database at the current point in the transaction timeline. - /// Begins a new transaction if one is not in progress. - /// - /// Call to undo transactions since the returned savepoint. - /// Call to commit transactions after the savepoint returned here. - /// Call to end the transaction, committing all changes. - /// - /// A string naming the savepoint. - public string SaveTransactionPoint() - { - int depth = Interlocked.Increment(ref _trasactionDepth) - 1; - string retVal = "S" + (short)_rand.Next(short.MaxValue) + "D" + depth; - - try - { - Execute("savepoint " + retVal); - } - catch (Exception ex) - { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) - { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) - { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo(null, true); - break; - } - } - else { - Interlocked.Decrement(ref _trasactionDepth); - } - - throw; - } - - return retVal; - } - - /// - /// Rolls back the transaction that was begun by or . - /// - public void Rollback() - { - RollbackTo(null, false); - } - - /// - /// Rolls back the savepoint created by or SaveTransactionPoint. - /// - /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to - public void RollbackTo(string savepoint) - { - RollbackTo(savepoint, false); - } - - /// - /// Rolls back the transaction that was begun by . - /// - /// true to avoid throwing exceptions, false otherwise - private void RollbackTo(string savepoint, bool noThrow) - { - // Rolling back without a TO clause rolls backs all transactions - // and leaves the transaction stack empty. - try - { - if (String.IsNullOrEmpty(savepoint)) - { - if (Interlocked.Exchange(ref _trasactionDepth, 0) > 0) - { - Execute("rollback"); - } - } - else { - DoSavePointExecute(savepoint, "rollback to "); - } - } - catch (SQLiteException) - { - if (!noThrow) - throw; - - } - // No need to rollback if there are no transactions open. - } - - /// - /// Releases a savepoint returned from . Releasing a savepoint - /// makes changes since that savepoint permanent if the savepoint began the transaction, - /// or otherwise the changes are permanent pending a call to . - /// - /// The RELEASE command is like a COMMIT for a SAVEPOINT. - /// - /// The name of the savepoint to release. The string should be the result of a call to - public void Release(string savepoint) - { - DoSavePointExecute(savepoint, "release "); - } - - private void DoSavePointExecute(string savepoint, string cmd) - { - // Validate the savepoint - int firstLen = savepoint.IndexOf('D'); - if (firstLen >= 2 && savepoint.Length > firstLen + 1) - { - int depth; - if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth)) - { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _trasactionDepth) - { -#if NETFX_CORE - Volatile.Write(ref _trasactionDepth, depth); -#elif SILVERLIGHT - _trasactionDepth = depth; -#else - Thread.VolatileWrite (ref _trasactionDepth, depth); -#endif - Execute(cmd + savepoint); - return; - } - } - } - - throw new ArgumentException("savePoint", "savePoint is not valid, and should be the result of a call to SaveTransactionPoint."); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit() - { - if (Interlocked.Exchange(ref _trasactionDepth, 0) != 0) - { - Execute("commit"); - } - // Do nothing on a commit with no open transaction - } - - /// - /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an - /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception - /// is rethrown. - /// - /// - /// The to perform within a transaction. can contain any number - /// of operations on the connection but should never call or - /// . - /// - public void RunInTransaction(Action action) - { - try - { - var savePoint = SaveTransactionPoint(); - action(); - Release(savePoint); - } - catch (Exception) - { - Rollback(); - throw; - } - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects, string extra) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r, extra); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects, Type objType) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r, objType); - } - }); - return c; - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj) - { - if (obj == null) - { - return 0; - } - return Insert(obj, "", obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace(object obj) - { - if (obj == null) - { - return 0; - } - return Insert(obj, "OR REPLACE", obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, Type objType) - { - return Insert(obj, "", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace(object obj, Type objType) - { - return Insert(obj, "OR REPLACE", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, string extra) - { - if (obj == null) - { - return 0; - } - return Insert(obj, extra, obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, string extra, Type objType) - { - if (obj == null || objType == null) - { - return 0; - } - - var map = GetMapping(objType); - - var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; - var vals = new object[cols.Length]; - for (var i = 0; i < vals.Length; i++) - { - vals[i] = cols[i].GetValue(obj); - } - - var insertCmd = map.GetInsertCommand(this, extra); - var count = insertCmd.ExecuteNonQuery(vals); - - if (map.HasAutoIncPK) - { - var id = SQLite3.LastInsertRowid(Handle); - map.SetAutoIncPK(obj, id); - } - - return count; - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows updated. - /// - public int Update(object obj) - { - if (obj == null) - { - return 0; - } - return Update(obj, obj.GetType()); - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows updated. - /// - public int Update(object obj, Type objType) - { - if (obj == null || objType == null) - { - return 0; - } - - var map = GetMapping(objType); - - var pk = map.PK; - - if (pk == null) - { - throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK"); - } - - var cols = from p in map.Columns - where p != pk - select p; - var vals = from c in cols - select c.GetValue(obj); - var ps = new List(vals); - ps.Add(pk.GetValue(obj)); - var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols - select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name); - return Execute(q, ps.ToArray()); - } - - /// - /// Updates all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows modified. - /// - public int UpdateAll(System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Update(r); - } - }); - return c; - } - - /// - /// Deletes the given object from the database using its primary key. - /// - /// - /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows deleted. - /// - public int Delete(object objectToDelete) - { - var map = GetMapping(objectToDelete.GetType()); - var pk = map.PK; - if (pk == null) - { - throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute(q, pk.GetValue(objectToDelete)); - } - - /// - /// Deletes the object with the specified primary key. - /// - /// - /// The primary key of the object to delete. - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of object. - /// - public int Delete(object primaryKey) - { - var map = GetMapping(typeof(T)); - var pk = map.PK; - if (pk == null) - { - throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute(q, primaryKey); - } - - /// - /// Deletes all the objects from the specified table. - /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the - /// specified table. Do you really want to do that? - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of objects to delete. - /// - public int DeleteAll() - { - var map = GetMapping(typeof(T)); - var query = string.Format("delete from \"{0}\"", map.TableName); - return Execute(query); - } - - ~SQLiteConnection() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Close(); - } - - public void Close() - { - if (_open && Handle != NullHandle) - { - try - { - if (_mappings != null) - { - foreach (var sqlInsertCommand in _mappings.Values) - { - sqlInsertCommand.Dispose(); - } - } - var r = SQLite3.Close(Handle); - if (r != SQLite3.Result.OK) - { - string msg = SQLite3.GetErrmsg(Handle); - throw SQLiteException.New(r, msg); - } - } - finally - { - Handle = NullHandle; - _open = false; - } - } - } - } - - /// - /// Represents a parsed connection string. - /// - class SQLiteConnectionString - { - public string ConnectionString { get; private set; } - public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - -#if NETFX_CORE - static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; -#endif - - public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks) - { - ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - -#if NETFX_CORE - DatabasePath = System.IO.Path.Combine(MetroStyleDataPath, databasePath); -#else - DatabasePath = databasePath; -#endif - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public string Name { get; set; } - - public TableAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public string Name { get; set; } - - public ColumnAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class PrimaryKeyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class AutoIncrementAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class IndexedAttribute : Attribute - { - public string Name { get; set; } - public int Order { get; set; } - public virtual bool Unique { get; set; } - - public IndexedAttribute() - { - } - - public IndexedAttribute(string name, int order) - { - Name = name; - Order = order; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class UniqueAttribute : IndexedAttribute - { - public override bool Unique - { - get { return true; } - set { /* throw? */ } - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class MaxLengthAttribute : Attribute - { - public int Value { get; private set; } - - public MaxLengthAttribute(int length) - { - Value = length; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class CollationAttribute : Attribute - { - public string Value { get; private set; } - - public CollationAttribute(string collation) - { - Value = collation; - } - } - - public class TableMapping - { - public Type MappedType { get; private set; } - - public string TableName { get; private set; } - - public Column[] Columns { get; private set; } - - public Column PK { get; private set; } - - public string GetByPrimaryKeySql { get; private set; } - - Column _autoPk = null; - Column[] _insertColumns = null; - Column[] _insertOrReplaceColumns = null; - - public TableMapping(Type type) - { - MappedType = type; - -#if NETFX_CORE - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); -#else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); -#endif - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; - -#if !NETFX_CORE - var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); -#else - var props = from p in MappedType.GetRuntimeProperties() - where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic)) - select p; -#endif - var cols = new List(); - foreach (var p in props) - { -#if !NETFX_CORE - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; -#else - var ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Count() > 0; -#endif - if (p.CanWrite && !ignore) - { - cols.Add(new Column(p)); - } - } - Columns = cols.ToArray(); - foreach (var c in Columns) - { - if (c.IsAutoInc && c.IsPK) - { - _autoPk = c; - } - if (c.IsPK) - { - PK = c; - } - } - - HasAutoIncPK = _autoPk != null; - - if (PK != null) - { - GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); - } - else { - // People should not be calling Get/Find without a PK - GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", TableName); - } - } - - public bool HasAutoIncPK { get; private set; } - - public void SetAutoIncPK(object obj, long id) - { - if (_autoPk != null) - { - _autoPk.SetValue(obj, Convert.ChangeType(id, _autoPk.ColumnType, null)); - } - } - - public Column[] InsertColumns - { - get - { - if (_insertColumns == null) - { - _insertColumns = Columns.Where(c => !c.IsAutoInc).ToArray(); - } - return _insertColumns; - } - } - - public Column[] InsertOrReplaceColumns - { - get - { - if (_insertOrReplaceColumns == null) - { - _insertOrReplaceColumns = Columns.ToArray(); - } - return _insertOrReplaceColumns; - } - } - - public Column FindColumnWithPropertyName(string propertyName) - { - var exact = Columns.Where(c => c.PropertyName == propertyName).FirstOrDefault(); - return exact; - } - - public Column FindColumn(string columnName) - { - var exact = Columns.Where(c => c.Name == columnName).FirstOrDefault(); - return exact; - } - - PreparedSqlLiteInsertCommand _insertCommand; - string _insertCommandExtra = null; - - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) - { - if (_insertCommand == null) - { - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - else if (_insertCommandExtra != extra) - { - _insertCommand.Dispose(); - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - return _insertCommand; - } - - private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) - { - var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { - var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - if (replacing) - { - cols = InsertOrReplaceColumns; - } - - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); - insertCommand.CommandText = insertSql; - return insertCommand; - } - - protected internal void Dispose() - { - if (_insertCommand != null) - { - _insertCommand.Dispose(); - _insertCommand = null; - } - } - - public class Column - { - PropertyInfo _prop; - - public string Name { get; private set; } - - public string PropertyName { get { return _prop.Name; } } - - public Type ColumnType { get; private set; } - - public string Collation { get; private set; } - - public bool IsAutoInc { get; private set; } - - public bool IsPK { get; private set; } - - public IEnumerable Indices { get; set; } - - public bool IsNullable { get; private set; } - - public int MaxStringLength { get; private set; } - - public Column(PropertyInfo prop) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); - IsAutoInc = Orm.IsAutoInc(prop); - IsPK = Orm.IsPK(prop); - Indices = Orm.GetIndices(prop); - IsNullable = !IsPK; - MaxStringLength = Orm.MaxStringLength(prop); - } - - public void SetValue(object obj, object val) - { - _prop.SetValue(obj, val, null); - } - - public object GetValue(object obj) - { - return _prop.GetValue(obj, null); - } - } - } - - public static class Orm - { - public const int DefaultMaxStringLength = 140; - - public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks) - { - string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks) + " "; - - if (p.IsPK) - { - decl += "primary key "; - } - if (p.IsAutoInc) - { - decl += "autoincrement "; - } - if (!p.IsNullable) - { - decl += "not null "; - } - if (!string.IsNullOrEmpty(p.Collation)) - { - decl += "collate " + p.Collation + " "; - } - - return decl; - } - - public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks) - { - var clrType = p.ColumnType; - if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32)) - { - return "integer"; - } - else if (clrType == typeof(UInt32) || clrType == typeof(Int64)) - { - return "bigint"; - } - else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) - { - return "float"; - } - else if (clrType == typeof(String)) - { - int len = p.MaxStringLength; - return "varchar(" + len + ")"; - } - else if (clrType == typeof(DateTime)) - { - return storeDateTimeAsTicks ? "bigint" : "datetime"; -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } - else if (clrType.GetTypeInfo().IsEnum) - { -#endif - return "integer"; - } - else if (clrType == typeof(byte[])) - { - return "blob"; - } - else if (clrType == typeof(Guid)) - { - return "varchar(36)"; - } - else { - throw new NotSupportedException("Don't know about " + clrType); - } - } - - public static bool IsPK(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static string Collation(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(CollationAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - { - return ((CollationAttribute)attrs.First()).Value; -#endif - } - else { - return string.Empty; - } - } - - public static bool IsAutoInc(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(AutoIncrementAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static IEnumerable GetIndices(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); - } - - public static int MaxStringLength(PropertyInfo p) - { - var attrs = p.GetCustomAttributes(typeof(MaxLengthAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((MaxLengthAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - { - return ((MaxLengthAttribute)attrs.First()).Value; -#endif - } - else { - return DefaultMaxStringLength; - } - } - } - - public partial class SQLiteCommand - { - SQLiteConnection _conn; - private List _bindings; - - public string CommandText { get; set; } - - internal SQLiteCommand(SQLiteConnection conn) - { - _conn = conn; - _bindings = new List(); - CommandText = ""; - } - - public int ExecuteNonQuery() - { - if (_conn.Trace) - { - Debug.WriteLine("Executing: " + this); - } - - var r = SQLite3.Result.OK; - var stmt = Prepare(); - r = SQLite3.Step(stmt); - Finalize(stmt); - if (r == SQLite3.Result.Done) - { - int rowsAffected = SQLite3.Changes(_conn.Handle); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) - { - string msg = SQLite3.GetErrmsg(_conn.Handle); - throw SQLiteException.New(r, msg); - } - else { - throw SQLiteException.New(r, r.ToString()); - } - } - - public IEnumerable ExecuteDeferredQuery() - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); - } - - public List ExecuteQuery() - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); - } - - public List ExecuteQuery(TableMapping map) - { - return ExecuteDeferredQuery(map).ToList(); - } - - /// - /// Invoked every time an instance is loaded from the database. - /// - /// - /// The newly created object. - /// - /// - /// This can be overridden in combination with the - /// method to hook into the life-cycle of objects. - /// - /// Type safety is not possible because MonoTouch does not support virtual generic methods. - /// - protected virtual void OnInstanceCreated(object obj) - { - // Can be overridden. - } - - public IEnumerable ExecuteDeferredQuery(TableMapping map) - { - if (_conn.Trace) - { - Debug.WriteLine("Executing Query: " + this); - } - - var stmt = Prepare(); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)]; - - for (int i = 0; i < cols.Length; i++) - { - var name = SQLite3.ColumnName16(stmt, i); - cols[i] = map.FindColumn(name); - } - - while (SQLite3.Step(stmt) == SQLite3.Result.Row) - { - var obj = Activator.CreateInstance(map.MappedType); - for (int i = 0; i < cols.Length; i++) - { - if (cols[i] == null) - continue; - var colType = SQLite3.ColumnType(stmt, i); - var val = ReadCol(stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue(obj, val); - } - OnInstanceCreated(obj); - yield return (T)obj; - } - } - finally - { - SQLite3.Finalize(stmt); - } - } - - public T ExecuteScalar() - { - if (_conn.Trace) - { - Debug.WriteLine("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare(); - if (SQLite3.Step(stmt) == SQLite3.Result.Row) - { - var colType = SQLite3.ColumnType(stmt, 0); - val = (T)ReadCol(stmt, 0, colType, typeof(T)); - } - Finalize(stmt); - - return val; - } - - public void Bind(string name, object val) - { - _bindings.Add(new Binding - { - Name = name, - Value = val - }); - } - - public void Bind(object val) - { - Bind(null, val); - } - - public override string ToString() - { - var parts = new string[1 + _bindings.Count]; - parts[0] = CommandText; - var i = 1; - foreach (var b in _bindings) - { - parts[i] = string.Format(" {0}: {1}", i - 1, b.Value); - i++; - } - return string.Join(Environment.NewLine, parts); - } - - Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2(_conn.Handle, CommandText); - BindAll(stmt); - return stmt; - } - - void Finalize(Sqlite3Statement stmt) - { - SQLite3.Finalize(stmt); - } - - void BindAll(Sqlite3Statement stmt) - { - int nextIdx = 1; - foreach (var b in _bindings) - { - if (b.Name != null) - { - b.Index = SQLite3.BindParameterIndex(stmt, b.Name); - } - else { - b.Index = nextIdx++; - } - - BindParameter(stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); - } - } - - internal static IntPtr NegativePointer = new IntPtr(-1); - - internal static void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) - { - SQLite3.BindNull(stmt, index); - } - else { - if (value is Int32) - { - SQLite3.BindInt(stmt, index, (int)value); - } - else if (value is String) - { - SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer); - } - else if (value is Byte || value is UInt16 || value is SByte || value is Int16) - { - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is Boolean) - { - SQLite3.BindInt(stmt, index, (bool)value ? 1 : 0); - } - else if (value is UInt32 || value is Int64) - { - SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); - } - else if (value is Single || value is Double || value is Decimal) - { - SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); - } - else if (value is DateTime) - { - if (storeDateTimeAsTicks) - { - SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks); - } - else { - SQLite3.BindText(stmt, index, ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } -#if !NETFX_CORE - } else if (value.GetType().IsEnum) { -#else - } - else if (value.GetType().GetTypeInfo().IsEnum) - { -#endif - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is byte[]) - { - SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); - } - else if (value is Guid) - { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } - else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - } - } - - class Binding - { - public string Name { get; set; } - - public object Value { get; set; } - - public int Index { get; set; } - } - - object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) - { - return null; - } - else { - if (clrType == typeof(String)) - { - return SQLite3.ColumnString(stmt, index); - } - else if (clrType == typeof(Int32)) - { - return (int)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Boolean)) - { - return SQLite3.ColumnInt(stmt, index) == 1; - } - else if (clrType == typeof(double)) - { - return SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(float)) - { - return (float)SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(DateTime)) - { - if (_conn.StoreDateTimeAsTicks) - { - return new DateTime(SQLite3.ColumnInt64(stmt, index)); - } - else { - var text = SQLite3.ColumnString(stmt, index); - return DateTime.Parse(text); - } -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } - else if (clrType.GetTypeInfo().IsEnum) - { -#endif - return SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Int64)) - { - return SQLite3.ColumnInt64(stmt, index); - } - else if (clrType == typeof(UInt32)) - { - return (uint)SQLite3.ColumnInt64(stmt, index); - } - else if (clrType == typeof(decimal)) - { - return (decimal)SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(Byte)) - { - return (byte)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(UInt16)) - { - return (ushort)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Int16)) - { - return (short)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(sbyte)) - { - return (sbyte)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(byte[])) - { - return SQLite3.ColumnByteArray(stmt, index); - } - else if (clrType == typeof(Guid)) - { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } - else { - throw new NotSupportedException("Don't know how to read " + clrType); - } - } - } - } - - /// - /// Since the insert never changed, we only need to prepare once. - /// - public class PreparedSqlLiteInsertCommand : IDisposable - { - public bool Initialized { get; set; } - - protected SQLiteConnection Connection { get; set; } - - public string CommandText { get; set; } - - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); - - internal PreparedSqlLiteInsertCommand(SQLiteConnection conn) - { - Connection = conn; - } - - public int ExecuteNonQuery(object[] source) - { - if (Connection.Trace) - { - Debug.WriteLine("Executing: " + CommandText); - } - - var r = SQLite3.Result.OK; - - if (!Initialized) - { - Statement = Prepare(); - Initialized = true; - } - - //bind the values. - if (source != null) - { - for (int i = 0; i < source.Length; i++) - { - SQLiteCommand.BindParameter(Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks); - } - } - r = SQLite3.Step(Statement); - - if (r == SQLite3.Result.Done) - { - int rowsAffected = SQLite3.Changes(Connection.Handle); - SQLite3.Reset(Statement); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) - { - string msg = SQLite3.GetErrmsg(Connection.Handle); - SQLite3.Reset(Statement); - throw SQLiteException.New(r, msg); - } - else { - SQLite3.Reset(Statement); - throw SQLiteException.New(r, r.ToString()); - } - } - - protected virtual Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2(Connection.Handle, CommandText); - return stmt; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (Statement != NullStatement) - { - try - { - SQLite3.Finalize(Statement); - } - finally - { - Statement = NullStatement; - Connection = null; - } - } - } - - ~PreparedSqlLiteInsertCommand() - { - Dispose(false); - } - } - - public abstract class BaseTableQuery - { - protected class Ordering - { - public string ColumnName { get; set; } - public bool Ascending { get; set; } - } - } - - public class TableQuery : BaseTableQuery, IEnumerable - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - Expression _where; - List _orderBys; - int? _limit; - int? _offset; - - BaseTableQuery _joinInner; - Expression _joinInnerKeySelector; - BaseTableQuery _joinOuter; - Expression _joinOuterKeySelector; - Expression _joinSelector; - - Expression _selector; - - TableQuery(SQLiteConnection conn, TableMapping table) - { - Connection = conn; - Table = table; - } - - public TableQuery(SQLiteConnection conn) - { - Connection = conn; - Table = Connection.GetMapping(typeof(T)); - } - - public TableQuery Clone() - { - var q = new TableQuery(Connection, Table); - q._where = _where; - q._deferred = _deferred; - if (_orderBys != null) - { - q._orderBys = new List(_orderBys); - } - q._limit = _limit; - q._offset = _offset; - q._joinInner = _joinInner; - q._joinInnerKeySelector = _joinInnerKeySelector; - q._joinOuter = _joinOuter; - q._joinOuterKeySelector = _joinOuterKeySelector; - q._joinSelector = _joinSelector; - q._selector = _selector; - return q; - } - - public TableQuery Where(Expression> predExpr) - { - if (predExpr.NodeType == ExpressionType.Lambda) - { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone(); - q.AddWhere(pred); - return q; - } - else { - throw new NotSupportedException("Must be a predicate"); - } - } - - public TableQuery Take(int n) - { - var q = Clone(); - q._limit = n; - return q; - } - - public TableQuery Skip(int n) - { - var q = Clone(); - q._offset = n; - return q; - } - - public T ElementAt(int index) - { - return Skip(index).Take(1).First(); - } - - bool _deferred = false; - public TableQuery Deferred() - { - var q = Clone(); - q._deferred = true; - return q; - } - - public TableQuery OrderBy(Expression> orderExpr) - { - return AddOrderBy(orderExpr, true); - } - - public TableQuery OrderByDescending(Expression> orderExpr) - { - return AddOrderBy(orderExpr, false); - } - - private TableQuery AddOrderBy(Expression> orderExpr, bool asc) - { - if (orderExpr.NodeType == ExpressionType.Lambda) - { - var lambda = (LambdaExpression)orderExpr; - - MemberExpression mem = null; - - var unary = lambda.Body as UnaryExpression; - if (unary != null && unary.NodeType == ExpressionType.Convert) - { - mem = unary.Operand as MemberExpression; - } - else { - mem = lambda.Body as MemberExpression; - } - - if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) - { - var q = Clone(); - if (q._orderBys == null) - { - q._orderBys = new List(); - } - q._orderBys.Add(new Ordering - { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return q; - } - else { - throw new NotSupportedException("Order By does not support: " + orderExpr); - } - } - else { - throw new NotSupportedException("Must be a predicate"); - } - } - - private void AddWhere(Expression pred) - { - if (_where == null) - { - _where = pred; - } - else { - _where = Expression.AndAlso(_where, pred); - } - } - - public TableQuery Join( - TableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - var q = new TableQuery(Connection, Connection.GetMapping(typeof(TResult))) - { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public TableQuery Select(Expression> selector) - { - var q = Clone(); - q._selector = selector; - return q; - } - - private SQLiteCommand GenerateCommand(string selectionList) - { - if (_joinInner != null && _joinOuter != null) - { - throw new NotSupportedException("Joins are not supported."); - } - else { - var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; - var args = new List(); - if (_where != null) - { - var w = CompileExpr(_where, args); - cmdText += " where " + w.CommandText; - } - if ((_orderBys != null) && (_orderBys.Count > 0)) - { - var t = string.Join(", ", _orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); - cmdText += " order by " + t; - } - if (_limit.HasValue) - { - cmdText += " limit " + _limit.Value; - } - if (_offset.HasValue) - { - if (!_limit.HasValue) - { - cmdText += " limit -1 "; - } - cmdText += " offset " + _offset.Value; - } - return Connection.CreateCommand(cmdText, args.ToArray()); - } - } - - class CompileResult - { - public string CommandText { get; set; } - - public object Value { get; set; } - } - - private CompileResult CompileExpr(Expression expr, List queryArgs) - { - if (expr == null) - { - throw new NotSupportedException("Expression is NULL"); - } - else if (expr is BinaryExpression) - { - var bin = (BinaryExpression)expr; - - var leftr = CompileExpr(bin.Left, queryArgs); - var rightr = CompileExpr(bin.Right, queryArgs); - - //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") - string text; - if (leftr.CommandText == "?" && leftr.Value == null) - text = CompileNullBinaryExpression(bin, rightr); - else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); - else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; - return new CompileResult { CommandText = text }; - } - else if (expr.NodeType == ExpressionType.Call) - { - - var call = (MethodCallExpression)expr; - var args = new CompileResult[call.Arguments.Count]; - var obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null; - - for (var i = 0; i < args.Length; i++) - { - args[i] = CompileExpr(call.Arguments[i], queryArgs); - } - - var sqlCall = ""; - - if (call.Method.Name == "Like" && args.Length == 2) - { - sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 2) - { - sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 1) - { - if (call.Object != null && call.Object.Type == typeof(string)) - { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + " || '%'))"; - } - else { - sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")"; - } - } - else if (call.Method.Name == "StartsWith" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; - } - else if (call.Method.Name == "EndsWith" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; - } - else if (call.Method.Name == "Equals" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; - } - else { - sqlCall = call.Method.Name.ToLower() + "(" + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")"; - } - return new CompileResult { CommandText = sqlCall }; - - } - else if (expr.NodeType == ExpressionType.Constant) - { - var c = (ConstantExpression)expr; - queryArgs.Add(c.Value); - return new CompileResult - { - CommandText = "?", - Value = c.Value - }; - } - else if (expr.NodeType == ExpressionType.Convert) - { - var u = (UnaryExpression)expr; - var ty = u.Type; - var valr = CompileExpr(u.Operand, queryArgs); - return new CompileResult - { - CommandText = valr.CommandText, - Value = valr.Value != null ? ConvertTo(valr.Value, ty) : null - }; - } - else if (expr.NodeType == ExpressionType.MemberAccess) - { - var mem = (MemberExpression)expr; - - if (mem.Expression != null && mem.Expression.NodeType == ExpressionType.Parameter) - { - // - // This is a column of our table, output just the column name - // Need to translate it if that column name is mapped - // - var columnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name; - return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } - else { - object obj = null; - if (mem.Expression != null) - { - var r = CompileExpr(mem.Expression, queryArgs); - if (r.Value == null) - { - throw new NotSupportedException("Member access failed to compile expression"); - } - if (r.CommandText == "?") - { - queryArgs.RemoveAt(queryArgs.Count - 1); - } - obj = r.Value; - } - - // - // Get the member value - // - object val = null; - -#if !NETFX_CORE - if (mem.Member.MemberType == MemberTypes.Property) { -#else - if (mem.Member is PropertyInfo) - { -#endif - var m = (PropertyInfo)mem.Member; - val = m.GetValue(obj, null); -#if !NETFX_CORE - } else if (mem.Member.MemberType == MemberTypes.Field) { -#else - } - else if (mem.Member is FieldInfo) - { -#endif -#if SILVERLIGHT - val = Expression.Lambda (expr).Compile ().DynamicInvoke (); -#else - var m = (FieldInfo)mem.Member; - val = m.GetValue(obj); -#endif - } - else { -#if !NETFX_CORE - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType.ToString ()); -#else - throw new NotSupportedException("MemberExpr: " + mem.Member.DeclaringType.ToString()); -#endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string)) - { - var sb = new System.Text.StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (System.Collections.IEnumerable)val) - { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult - { - CommandText = sb.ToString(), - Value = val - }; - } - else { - queryArgs.Add(val); - return new CompileResult - { - CommandText = "?", - Value = val - }; - } - } - } - throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString()); - } - - static object ConvertTo(object obj, Type t) - { - Type nut = Nullable.GetUnderlyingType(t); - - if (nut != null) - { - if (obj == null) return null; - return Convert.ChangeType(obj, nut); - } - else { - return Convert.ChangeType(obj, t); - } - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) - { - if (expression.NodeType == ExpressionType.Equal) - return "(" + parameter.CommandText + " is ?)"; - else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else - throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); - } - - string GetSqlName(Expression expr) - { - var n = expr.NodeType; - if (n == ExpressionType.GreaterThan) - return ">"; - else if (n == ExpressionType.GreaterThanOrEqual) - { - return ">="; - } - else if (n == ExpressionType.LessThan) - { - return "<"; - } - else if (n == ExpressionType.LessThanOrEqual) - { - return "<="; - } - else if (n == ExpressionType.And) - { - return "&"; - } - else if (n == ExpressionType.AndAlso) - { - return "and"; - } - else if (n == ExpressionType.Or) - { - return "|"; - } - else if (n == ExpressionType.OrElse) - { - return "or"; - } - else if (n == ExpressionType.Equal) - { - return "="; - } - else if (n == ExpressionType.NotEqual) - { - return "!="; - } - else { - throw new System.NotSupportedException("Cannot get SQL for: " + n.ToString()); - } - } - - public int Count() - { - return GenerateCommand("count(*)").ExecuteScalar(); - } - - public IEnumerator GetEnumerator() - { - if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public T First() - { - var query = Take(1); - return query.ToList().First(); - } - - public T FirstOrDefault() - { - var query = this.Take(1); - return query.ToList().FirstOrDefault(); - } - } - - public static class SQLite3 - { - public enum Result : int - { - OK = 0, - Error = 1, - Internal = 2, - Perm = 3, - Abort = 4, - Busy = 5, - Locked = 6, - NoMem = 7, - ReadOnly = 8, - Interrupt = 9, - IOError = 10, - Corrupt = 11, - NotFound = 12, - Full = 13, - CannotOpen = 14, - LockErr = 15, - Empty = 16, - SchemaChngd = 17, - TooBig = 18, - Constraint = 19, - Mismatch = 20, - Misuse = 21, - NotImplementedLFS = 22, - AccessDenied = 23, - Format = 24, - Range = 25, - NonDBFile = 26, - Row = 100, - Done = 101 - } - - public enum ConfigOption : int - { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - -#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE - [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Close(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Config(ConfigOption option); - - [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int SetDirectory(uint directoryType, string directoryPath); - - [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] - public static extern Result BusyTimeout(IntPtr db, int milliseconds); - - [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] - public static extern int Changes(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); - - public static IntPtr Prepare2(IntPtr db, string query) - { - IntPtr stmt; - var r = Prepare2(db, query, query.Length, out stmt, IntPtr.Zero); - if (r != Result.OK) - { - throw SQLiteException.New(r, GetErrmsg(db)); - } - return stmt; - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Step(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Reset(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Finalize(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] - public static extern long LastInsertRowid(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr Errmsg(IntPtr db); - - public static string GetErrmsg(IntPtr db) - { - return Marshal.PtrToStringUni(Errmsg(db)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindNull(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt(IntPtr stmt, int index, int val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt64(IntPtr stmt, int index, long val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindDouble(IntPtr stmt, int index, double val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnCount(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnName(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr ColumnName16Internal(IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] - public static extern ColType ColumnType(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnInt(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern long ColumnInt64(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] - public static extern double ColumnDouble(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnBytes(IntPtr stmt, int index); - - public static string ColumnString(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(SQLite3.ColumnText16(stmt, index)); - } - - public static byte[] ColumnByteArray(IntPtr stmt, int index) - { - int length = ColumnBytes(stmt, index); - byte[] result = new byte[length]; - if (length > 0) - Marshal.Copy(ColumnBlob(stmt, index), result, 0, length); - return result; - } -#else - public static Result Open(string filename, out Sqlite3DatabaseHandle db) - { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } - - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) - { -#if USE_WP8_NATIVE_SQLITE - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); -#else - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); -#endif - } - - public static Result Close(Sqlite3DatabaseHandle db) - { - return (Result)Sqlite3.sqlite3_close(db); - } - - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) - { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } - - public static int Changes(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_changes(db); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) - { - Sqlite3Statement stmt = default(Sqlite3Statement); -#if USE_WP8_NATIVE_SQLITE - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); -#else - stmt = new Sqlite3Statement(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); -#endif - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } - - public static Result Step(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_step(stmt); - } - - public static Result Reset(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_reset(stmt); - } - - public static Result Finalize(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } - - public static long LastInsertRowid(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_last_insert_rowid(db); - } - - public static string GetErrmsg(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3Statement stmt, string name) - { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3Statement stmt, int index, int val) - { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3Statement stmt, int index, long val) - { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3Statement stmt, int index, double val) - { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_text(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); -#endif - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); -#endif - } - - public static int ColumnCount(Sqlite3Statement stmt) - { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3Statement stmt, int index) - { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) - { - return ColumnBlob(stmt, index); - } -#endif - - public enum ColType : int - { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } -} diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs deleted file mode 100644 index 3e25387..0000000 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs +++ /dev/null @@ -1,510 +0,0 @@ -// -// Copyright (c) 2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Labs.SightsToSee.Library.Sqlite -{ - public partial class SQLiteAsyncConnection - { - SQLiteConnectionString _connectionString; - - public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = false) - { - _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); - } - - SQLiteConnectionWithLock GetConnection() - { - return SQLiteConnectionPool.Shared.GetConnection(_connectionString); - } - - public Task CreateTableAsync() - where T : new() - { - return CreateTablesAsync(typeof(T)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - where T4 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3), typeof(T4)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - where T4 : new() - where T5 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); - } - - public Task CreateTablesAsync(params Type[] types) - { - return Task.Factory.StartNew(() => { - CreateTablesResult result = new CreateTablesResult(); - var conn = GetConnection(); - using (conn.Lock()) - { - foreach (Type type in types) - { - int aResult = conn.CreateTable(type); - result.Results[type] = aResult; - } - } - return result; - }); - } - - public Task DropTableAsync() - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.DropTable(); - } - }); - } - - public Task InsertAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Insert(item); - } - }); - } - - public Task UpdateAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Update(item); - } - }); - } - - public Task DeleteAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Delete(item); - } - }); - } - - public Task GetAsync(object pk) - where T : new() - { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get(pk); - } - }); - } - - public Task FindAsync(object pk) - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Find(pk); - } - }); - } - - public Task GetAsync(Expression> predicate) - where T : new() - { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get(predicate); - } - }); - } - - public Task FindAsync(Expression> predicate) - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Find(predicate); - } - }); - } - - public Task ExecuteAsync(string query, params object[] args) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Execute(query, args); - } - }); - } - - public Task InsertAllAsync(IEnumerable items) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.InsertAll(items); - } - }); - } - - [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] - public Task RunInTransactionAsync(Action action) - { - return Task.Factory.StartNew(() => { - var conn = this.GetConnection(); - using (conn.Lock()) - { - conn.BeginTransaction(); - try - { - action(this); - conn.Commit(); - } - catch (Exception) - { - conn.Rollback(); - throw; - } - } - }); - } - - public Task RunInTransactionAsync(Action action) - { - return Task.Factory.StartNew(() => - { - var conn = this.GetConnection(); - using (conn.Lock()) - { - conn.BeginTransaction(); - try - { - action(conn); - conn.Commit(); - } - catch (Exception) - { - conn.Rollback(); - throw; - } - } - }); - } - - public AsyncTableQuery Table() - where T : new() - { - // - // This isn't async as the underlying connection doesn't go out to the database - // until the query is performed. The Async methods are on the query iteself. - // - var conn = GetConnection(); - return new AsyncTableQuery(conn.Table()); - } - - public Task ExecuteScalarAsync(string sql, params object[] args) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - var command = conn.CreateCommand(sql, args); - return command.ExecuteScalar(); - } - }); - } - - public Task> QueryAsync(string sql, params object[] args) - where T : new() - { - return Task>.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Query(sql, args); - } - }); - } - } - - // - // TODO: Bind to AsyncConnection.GetConnection instead so that delayed - // execution can still work after a Pool.Reset. - // - public class AsyncTableQuery - where T : new() - { - TableQuery _innerQuery; - - public AsyncTableQuery(TableQuery innerQuery) - { - _innerQuery = innerQuery; - } - - public AsyncTableQuery Where(Expression> predExpr) - { - return new AsyncTableQuery(_innerQuery.Where(predExpr)); - } - - public AsyncTableQuery Skip(int n) - { - return new AsyncTableQuery(_innerQuery.Skip(n)); - } - - public AsyncTableQuery Take(int n) - { - return new AsyncTableQuery(_innerQuery.Take(n)); - } - - public AsyncTableQuery OrderBy(Expression> orderExpr) - { - return new AsyncTableQuery(_innerQuery.OrderBy(orderExpr)); - } - - public AsyncTableQuery OrderByDescending(Expression> orderExpr) - { - return new AsyncTableQuery(_innerQuery.OrderByDescending(orderExpr)); - } - - public Task> ToListAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.ToList(); - } - }); - } - - public Task CountAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.Count(); - } - }); - } - - public Task ElementAtAsync(int index) - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.ElementAt(index); - } - }); - } - - public Task FirstAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.First(); - } - }); - } - - public Task FirstOrDefaultAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.FirstOrDefault(); - } - }); - } - } - - public class CreateTablesResult - { - public Dictionary Results { get; private set; } - - internal CreateTablesResult() - { - this.Results = new Dictionary(); - } - } - - class SQLiteConnectionPool - { - class Entry - { - public SQLiteConnectionString ConnectionString { get; private set; } - public SQLiteConnectionWithLock Connection { get; private set; } - - public Entry(SQLiteConnectionString connectionString) - { - ConnectionString = connectionString; - Connection = new SQLiteConnectionWithLock(connectionString); - } - - public void OnApplicationSuspended() - { - Connection.Dispose(); - Connection = null; - } - } - - readonly Dictionary _entries = new Dictionary(); - readonly object _entriesLock = new object(); - - static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool(); - - /// - /// Gets the singleton instance of the connection tool. - /// - public static SQLiteConnectionPool Shared - { - get - { - return _shared; - } - } - - public SQLiteConnectionWithLock GetConnection(SQLiteConnectionString connectionString) - { - lock (_entriesLock) - { - Entry entry; - string key = connectionString.ConnectionString; - - if (!_entries.TryGetValue(key, out entry)) - { - entry = new Entry(connectionString); - _entries[key] = entry; - } - - return entry.Connection; - } - } - - /// - /// Closes all connections managed by this pool. - /// - public void Reset() - { - lock (_entriesLock) - { - foreach (var entry in _entries.Values) - { - entry.OnApplicationSuspended(); - } - _entries.Clear(); - } - } - - /// - /// Call this method when the application is suspended. - /// - /// Behaviour here is to close any open connections. - public void ApplicationSuspended() - { - Reset(); - } - } - - class SQLiteConnectionWithLock : SQLiteConnection - { - readonly object _lockPoint = new object(); - - public SQLiteConnectionWithLock(SQLiteConnectionString connectionString) - : base(connectionString.DatabasePath, connectionString.StoreDateTimeAsTicks) - { - } - - public IDisposable Lock() - { - return new LockWrapper(_lockPoint); - } - - private class LockWrapper : IDisposable - { - object _lockPoint; - - public LockWrapper(object lockPoint) - { - _lockPoint = lockPoint; - Monitor.Enter(_lockPoint); - } - - public void Dispose() - { - Monitor.Exit(_lockPoint); - } - } - } -} - diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj index 90efe5f..902c2d3 100644 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj +++ b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj @@ -25,7 +25,7 @@ true bin\x86\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE;CODE_ANALYSIS + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE ;2008 full x86 @@ -48,7 +48,7 @@ true bin\ARM\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE ;2008 full ARM diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx new file mode 100644 index 0000000..a066ddc Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx differ diff --git a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Services/AppExtensions/ExtensionManager.cs b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Services/AppExtensions/ExtensionManager.cs index adade9f..2c4bcdb 100644 --- a/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Services/AppExtensions/ExtensionManager.cs +++ b/Workshop/Module3-ConnectedApps/Source/Begin/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Services/AppExtensions/ExtensionManager.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using Windows.ApplicationModel; -using Windows.ApplicationModel.AppExtension; +using Windows.ApplicationModel.AppExtensions; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj index 0bce4dd..efff9f5 100644 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj +++ b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Microsoft.Labs.SightsToSee.Library.csproj @@ -24,7 +24,7 @@ x86 true bin\x86\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP ;2008 full x86 @@ -46,7 +46,7 @@ ARM true bin\ARM\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP ;2008 full ARM @@ -68,7 +68,7 @@ x64 true bin\x64\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP ;2008 full x64 @@ -123,9 +123,7 @@ SQLite for Universal Windows Platform - - - + ..\libs\Microsoft.WindowsAzure.Mobile.Files.dll @@ -140,7 +138,7 @@ true bin\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP ;2008 true full diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs deleted file mode 100644 index b90536f..0000000 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/Sqlite.cs +++ /dev/null @@ -1,3192 +0,0 @@ -// -// Copyright (c) 2009-2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE -#define USE_CSHARP_SQLITE -#endif - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else -using Sqlite3DatabaseHandle = System.IntPtr; -using Sqlite3Statement = System.IntPtr; -#endif - -namespace Microsoft.Labs.SightsToSee.Library.Sqlite -{ - public class SQLiteException : System.Exception - { - public SQLite3.Result Result { get; private set; } - - protected SQLiteException(SQLite3.Result r, string message) : base(message) - { - Result = r; - } - - public static SQLiteException New(SQLite3.Result r, string message) - { - return new SQLiteException(r, message); - } - } - - [Flags] - public enum SQLiteOpenFlags - { - ReadOnly = 1, ReadWrite = 2, Create = 4, - NoMutex = 0x8000, FullMutex = 0x10000, - SharedCache = 0x20000, PrivateCache = 0x40000, - ProtectionComplete = 0x00100000, - ProtectionCompleteUnlessOpen = 0x00200000, - ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, - ProtectionNone = 0x00400000 - } - - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable - { - private bool _open; - private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; - private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; - - private int _trasactionDepth = 0; - private Random _rand = new Random(); - - public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); - - public string DatabasePath { get; private set; } - - public bool TimeExecution { get; set; } - - public bool Trace { get; set; } - - public bool StoreDateTimeAsTicks { get; private set; } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false) - : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) - { - DatabasePath = databasePath; - -#if NETFX_CORE - SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); -#endif - - Sqlite3DatabaseHandle handle; - -#if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); -#else - // open using the byte[] - // in the case where the path may include Unicode - // force open to using UTF-8 using sqlite3_open_v2 - var databasePathAsBytes = GetNullTerminatedUtf8(DatabasePath); - var r = SQLite3.Open(databasePathAsBytes, out handle, (int)openFlags, IntPtr.Zero); -#endif - - Handle = handle; - if (r != SQLite3.Result.OK) - { - throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r)); - } - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds(0.1); - } - - static SQLiteConnection() - { - if (_preserveDuringLinkMagic) - { - var ti = new ColumnInfo(); - ti.Name = "magic"; - } - } - - static byte[] GetNullTerminatedUtf8(string s) - { - var utf8Length = System.Text.Encoding.UTF8.GetByteCount(s); - var bytes = new byte[utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic = false; - - /// - /// Sets a busy handler to sleep the specified amount of time when a table is locked. - /// The handler will sleep multiple times until a total time of has accumulated. - /// - public TimeSpan BusyTimeout - { - get { return _busyTimeout; } - set - { - _busyTimeout = value; - if (Handle != NullHandle) - { - SQLite3.BusyTimeout(Handle, (int)_busyTimeout.TotalMilliseconds); - } - } - } - - /// - /// Returns the mappings from types to tables that the connection - /// currently understands. - /// - public IEnumerable TableMappings - { - get - { - if (_tables == null) - { - return Enumerable.Empty(); - } - else { - return _tables.Values; - } - } - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The type whose mapping to the database is returned. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping(Type type) - { - if (_mappings == null) - { - _mappings = new Dictionary(); - } - TableMapping map; - if (!_mappings.TryGetValue(type.FullName, out map)) - { - map = new TableMapping(type); - _mappings[type.FullName] = map; - } - return map; - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping() - { - return GetMapping(typeof(T)); - } - - private struct IndexedColumn - { - public int Order; - public string ColumnName; - } - - private struct IndexInfo - { - public string IndexName; - public string TableName; - public bool Unique; - public List Columns; - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - public int DropTable() - { - var map = GetMapping(typeof(T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute(query); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// - /// The number of entries added to the database schema. - /// - public int CreateTable() - { - return CreateTable(typeof(T)); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// Type to reflect to a database table. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty) - { - if (_tables == null) - { - _tables = new Dictionary(); - } - TableMapping map; - if (!_tables.TryGetValue(ty.FullName, out map)) - { - map = GetMapping(ty); - _tables.Add(ty.FullName, map); - } - var query = "create table if not exists \"" + map.TableName + "\"(\n"; - - var decls = map.Columns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks)); - var decl = string.Join(",\n", decls.ToArray()); - query += decl; - query += ")"; - - var count = Execute(query); - - if (count == 0) - { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable(map); - } - - var indexes = new Dictionary(); - foreach (var c in map.Columns) - { - foreach (var i in c.Indices) - { - var iname = i.Name ?? map.TableName + "_" + c.Name; - IndexInfo iinfo; - if (!indexes.TryGetValue(iname, out iinfo)) - { - iinfo = new IndexInfo - { - IndexName = iname, - TableName = map.TableName, - Unique = i.Unique, - Columns = new List() - }; - indexes.Add(iname, iinfo); - } - - if (i.Unique != iinfo.Unique) - throw new Exception("All the columns in an index must have the same value for their Unique property"); - - iinfo.Columns.Add(new IndexedColumn - { - Order = i.Order, - ColumnName = c.Name - }); - } - } - - foreach (var indexName in indexes.Keys) - { - var index = indexes[indexName]; - const string sqlFormat = "create {3} index if not exists \"{0}\" on \"{1}\"(\"{2}\")"; - var columns = String.Join("\",\"", index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray()); - var sql = String.Format(sqlFormat, indexName, index.TableName, columns, index.Unique ? "unique" : ""); - count += Execute(sql); - } - - return count; - } - - public class ColumnInfo - { - // public int cid { get; set; } - - [Column("name")] - public string Name { get; set; } - - // [Column ("type")] - // public string ColumnType { get; set; } - - // public int notnull { get; set; } - - // public string dflt_value { get; set; } - - // public int pk { get; set; } - - public override string ToString() - { - return Name; - } - } - - public IEnumerable GetTableInfo(string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query(query); - } - - void MigrateTable(TableMapping map) - { - var existingCols = GetTableInfo(map.TableName); - - var toBeAdded = new List(); - - foreach (var p in map.Columns) - { - var found = false; - foreach (var c in existingCols) - { - found = (string.Compare(p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); - if (found) - break; - } - if (!found) - { - toBeAdded.Add(p); - } - } - - foreach (var p in toBeAdded) - { - var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(p, StoreDateTimeAsTicks); - Execute(addCol); - } - } - - /// - /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. - /// - /// - protected virtual SQLiteCommand NewCommand() - { - return new SQLiteCommand(this); - } - - /// - /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' - /// in the command text for each of the arguments. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the command text. - /// - /// - /// A - /// - public SQLiteCommand CreateCommand(string cmdText, params object[] ps) - { - if (!_open) - { - throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); - } - else { - var cmd = NewCommand(); - cmd.CommandText = cmdText; - foreach (var o in ps) - { - cmd.Bind(o); - } - return cmd; - } - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// Use this method instead of Query when you don't expect rows back. Such cases include - /// INSERTs, UPDATEs, and DELETEs. - /// You can set the Trace or TimeExecution properties of the connection - /// to profile execution. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// The number of rows modified in the database as a result of this execution. - /// - public int Execute(string query, params object[] args) - { - var cmd = CreateCommand(query, args); - - if (TimeExecution) - { - if (_sw == null) - { - _sw = new System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteNonQuery(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar(string query, params object[] args) - { - var cmd = CreateCommand(query, args); - - if (TimeExecution) - { - if (_sw == null) - { - _sw = new System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteScalar(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteQuery(map); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); - } - - /// - /// Returns a queryable interface to the table represented by the given type. - /// - /// - /// A queryable object that is able to translate Where, OrderBy, and Take - /// queries into native SQL. - /// - public TableQuery Table() where T : new() - { - return new TableQuery(this); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key. Throws a not found exception - /// if the object is not found. - /// - public T Get(object pk) where T : new() - { - var map = GetMapping(typeof(T)); - return Query(map.GetByPrimaryKeySql, pk).First(); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get(Expression> predicate) where T : new() - { - return Table().Where(predicate).First(); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public T Find(object pk) where T : new() - { - var map = GetMapping(typeof(T)); - return Query(map.GetByPrimaryKeySql, pk).FirstOrDefault(); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The TableMapping used to identify the object type. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public object Find(object pk, TableMapping map) - { - return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault(); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate or null - /// if the object is not found. - /// - public T Find(Expression> predicate) where T : new() - { - return Table().Where(predicate).FirstOrDefault(); - } - - /// - /// Whether has been called and the database is waiting for a . - /// - public bool IsInTransaction - { - get { return _trasactionDepth > 0; } - } - - /// - /// Begins a new transaction. Call to end the transaction. - /// - /// Throws if a transaction has already begun. - public void BeginTransaction() - { - // The BEGIN command only works if the transaction stack is empty, - // or in other words if there are no pending transactions. - // If the transaction stack is not empty when the BEGIN command is invoked, - // then the command fails with an error. - // Rather than crash with an error, we will just ignore calls to BeginTransaction - // that would result in an error. - if (Interlocked.CompareExchange(ref _trasactionDepth, 1, 0) == 0) - { - try - { - Execute("begin transaction"); - } - catch (Exception ex) - { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) - { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) - { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo(null, true); - break; - } - } - else { - // Call decrement and not VolatileWrite in case we've already - // created a transaction point in SaveTransactionPoint since the catch. - Interlocked.Decrement(ref _trasactionDepth); - } - - throw; - } - } - else { - // Calling BeginTransaction on an already open transaction is invalid - throw new System.InvalidOperationException("Cannot begin a transaction while already in a transaction."); - } - } - - /// - /// Creates a savepoint in the database at the current point in the transaction timeline. - /// Begins a new transaction if one is not in progress. - /// - /// Call to undo transactions since the returned savepoint. - /// Call to commit transactions after the savepoint returned here. - /// Call to end the transaction, committing all changes. - /// - /// A string naming the savepoint. - public string SaveTransactionPoint() - { - int depth = Interlocked.Increment(ref _trasactionDepth) - 1; - string retVal = "S" + (short)_rand.Next(short.MaxValue) + "D" + depth; - - try - { - Execute("savepoint " + retVal); - } - catch (Exception ex) - { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) - { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) - { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo(null, true); - break; - } - } - else { - Interlocked.Decrement(ref _trasactionDepth); - } - - throw; - } - - return retVal; - } - - /// - /// Rolls back the transaction that was begun by or . - /// - public void Rollback() - { - RollbackTo(null, false); - } - - /// - /// Rolls back the savepoint created by or SaveTransactionPoint. - /// - /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to - public void RollbackTo(string savepoint) - { - RollbackTo(savepoint, false); - } - - /// - /// Rolls back the transaction that was begun by . - /// - /// true to avoid throwing exceptions, false otherwise - private void RollbackTo(string savepoint, bool noThrow) - { - // Rolling back without a TO clause rolls backs all transactions - // and leaves the transaction stack empty. - try - { - if (String.IsNullOrEmpty(savepoint)) - { - if (Interlocked.Exchange(ref _trasactionDepth, 0) > 0) - { - Execute("rollback"); - } - } - else { - DoSavePointExecute(savepoint, "rollback to "); - } - } - catch (SQLiteException) - { - if (!noThrow) - throw; - - } - // No need to rollback if there are no transactions open. - } - - /// - /// Releases a savepoint returned from . Releasing a savepoint - /// makes changes since that savepoint permanent if the savepoint began the transaction, - /// or otherwise the changes are permanent pending a call to . - /// - /// The RELEASE command is like a COMMIT for a SAVEPOINT. - /// - /// The name of the savepoint to release. The string should be the result of a call to - public void Release(string savepoint) - { - DoSavePointExecute(savepoint, "release "); - } - - private void DoSavePointExecute(string savepoint, string cmd) - { - // Validate the savepoint - int firstLen = savepoint.IndexOf('D'); - if (firstLen >= 2 && savepoint.Length > firstLen + 1) - { - int depth; - if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth)) - { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _trasactionDepth) - { -#if NETFX_CORE - Volatile.Write(ref _trasactionDepth, depth); -#elif SILVERLIGHT - _trasactionDepth = depth; -#else - Thread.VolatileWrite (ref _trasactionDepth, depth); -#endif - Execute(cmd + savepoint); - return; - } - } - } - - throw new ArgumentException("savePoint", "savePoint is not valid, and should be the result of a call to SaveTransactionPoint."); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit() - { - if (Interlocked.Exchange(ref _trasactionDepth, 0) != 0) - { - Execute("commit"); - } - // Do nothing on a commit with no open transaction - } - - /// - /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an - /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception - /// is rethrown. - /// - /// - /// The to perform within a transaction. can contain any number - /// of operations on the connection but should never call or - /// . - /// - public void RunInTransaction(Action action) - { - try - { - var savePoint = SaveTransactionPoint(); - action(); - Release(savePoint); - } - catch (Exception) - { - Rollback(); - throw; - } - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects, string extra) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r, extra); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll(System.Collections.IEnumerable objects, Type objType) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Insert(r, objType); - } - }); - return c; - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj) - { - if (obj == null) - { - return 0; - } - return Insert(obj, "", obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace(object obj) - { - if (obj == null) - { - return 0; - } - return Insert(obj, "OR REPLACE", obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, Type objType) - { - return Insert(obj, "", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace(object obj, Type objType) - { - return Insert(obj, "OR REPLACE", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, string extra) - { - if (obj == null) - { - return 0; - } - return Insert(obj, extra, obj.GetType()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert(object obj, string extra, Type objType) - { - if (obj == null || objType == null) - { - return 0; - } - - var map = GetMapping(objType); - - var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; - var vals = new object[cols.Length]; - for (var i = 0; i < vals.Length; i++) - { - vals[i] = cols[i].GetValue(obj); - } - - var insertCmd = map.GetInsertCommand(this, extra); - var count = insertCmd.ExecuteNonQuery(vals); - - if (map.HasAutoIncPK) - { - var id = SQLite3.LastInsertRowid(Handle); - map.SetAutoIncPK(obj, id); - } - - return count; - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows updated. - /// - public int Update(object obj) - { - if (obj == null) - { - return 0; - } - return Update(obj, obj.GetType()); - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows updated. - /// - public int Update(object obj, Type objType) - { - if (obj == null || objType == null) - { - return 0; - } - - var map = GetMapping(objType); - - var pk = map.PK; - - if (pk == null) - { - throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK"); - } - - var cols = from p in map.Columns - where p != pk - select p; - var vals = from c in cols - select c.GetValue(obj); - var ps = new List(vals); - ps.Add(pk.GetValue(obj)); - var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols - select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name); - return Execute(q, ps.ToArray()); - } - - /// - /// Updates all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows modified. - /// - public int UpdateAll(System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) - { - c += Update(r); - } - }); - return c; - } - - /// - /// Deletes the given object from the database using its primary key. - /// - /// - /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows deleted. - /// - public int Delete(object objectToDelete) - { - var map = GetMapping(objectToDelete.GetType()); - var pk = map.PK; - if (pk == null) - { - throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute(q, pk.GetValue(objectToDelete)); - } - - /// - /// Deletes the object with the specified primary key. - /// - /// - /// The primary key of the object to delete. - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of object. - /// - public int Delete(object primaryKey) - { - var map = GetMapping(typeof(T)); - var pk = map.PK; - if (pk == null) - { - throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute(q, primaryKey); - } - - /// - /// Deletes all the objects from the specified table. - /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the - /// specified table. Do you really want to do that? - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of objects to delete. - /// - public int DeleteAll() - { - var map = GetMapping(typeof(T)); - var query = string.Format("delete from \"{0}\"", map.TableName); - return Execute(query); - } - - ~SQLiteConnection() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Close(); - } - - public void Close() - { - if (_open && Handle != NullHandle) - { - try - { - if (_mappings != null) - { - foreach (var sqlInsertCommand in _mappings.Values) - { - sqlInsertCommand.Dispose(); - } - } - var r = SQLite3.Close(Handle); - if (r != SQLite3.Result.OK) - { - string msg = SQLite3.GetErrmsg(Handle); - throw SQLiteException.New(r, msg); - } - } - finally - { - Handle = NullHandle; - _open = false; - } - } - } - } - - /// - /// Represents a parsed connection string. - /// - class SQLiteConnectionString - { - public string ConnectionString { get; private set; } - public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - -#if NETFX_CORE - static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; -#endif - - public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks) - { - ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - -#if NETFX_CORE - DatabasePath = System.IO.Path.Combine(MetroStyleDataPath, databasePath); -#else - DatabasePath = databasePath; -#endif - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public string Name { get; set; } - - public TableAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public string Name { get; set; } - - public ColumnAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class PrimaryKeyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class AutoIncrementAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class IndexedAttribute : Attribute - { - public string Name { get; set; } - public int Order { get; set; } - public virtual bool Unique { get; set; } - - public IndexedAttribute() - { - } - - public IndexedAttribute(string name, int order) - { - Name = name; - Order = order; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class UniqueAttribute : IndexedAttribute - { - public override bool Unique - { - get { return true; } - set { /* throw? */ } - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class MaxLengthAttribute : Attribute - { - public int Value { get; private set; } - - public MaxLengthAttribute(int length) - { - Value = length; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class CollationAttribute : Attribute - { - public string Value { get; private set; } - - public CollationAttribute(string collation) - { - Value = collation; - } - } - - public class TableMapping - { - public Type MappedType { get; private set; } - - public string TableName { get; private set; } - - public Column[] Columns { get; private set; } - - public Column PK { get; private set; } - - public string GetByPrimaryKeySql { get; private set; } - - Column _autoPk = null; - Column[] _insertColumns = null; - Column[] _insertOrReplaceColumns = null; - - public TableMapping(Type type) - { - MappedType = type; - -#if NETFX_CORE - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); -#else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); -#endif - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; - -#if !NETFX_CORE - var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); -#else - var props = from p in MappedType.GetRuntimeProperties() - where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic)) - select p; -#endif - var cols = new List(); - foreach (var p in props) - { -#if !NETFX_CORE - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; -#else - var ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Count() > 0; -#endif - if (p.CanWrite && !ignore) - { - cols.Add(new Column(p)); - } - } - Columns = cols.ToArray(); - foreach (var c in Columns) - { - if (c.IsAutoInc && c.IsPK) - { - _autoPk = c; - } - if (c.IsPK) - { - PK = c; - } - } - - HasAutoIncPK = _autoPk != null; - - if (PK != null) - { - GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); - } - else { - // People should not be calling Get/Find without a PK - GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", TableName); - } - } - - public bool HasAutoIncPK { get; private set; } - - public void SetAutoIncPK(object obj, long id) - { - if (_autoPk != null) - { - _autoPk.SetValue(obj, Convert.ChangeType(id, _autoPk.ColumnType, null)); - } - } - - public Column[] InsertColumns - { - get - { - if (_insertColumns == null) - { - _insertColumns = Columns.Where(c => !c.IsAutoInc).ToArray(); - } - return _insertColumns; - } - } - - public Column[] InsertOrReplaceColumns - { - get - { - if (_insertOrReplaceColumns == null) - { - _insertOrReplaceColumns = Columns.ToArray(); - } - return _insertOrReplaceColumns; - } - } - - public Column FindColumnWithPropertyName(string propertyName) - { - var exact = Columns.Where(c => c.PropertyName == propertyName).FirstOrDefault(); - return exact; - } - - public Column FindColumn(string columnName) - { - var exact = Columns.Where(c => c.Name == columnName).FirstOrDefault(); - return exact; - } - - PreparedSqlLiteInsertCommand _insertCommand; - string _insertCommandExtra = null; - - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) - { - if (_insertCommand == null) - { - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - else if (_insertCommandExtra != extra) - { - _insertCommand.Dispose(); - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - return _insertCommand; - } - - private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) - { - var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { - var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - if (replacing) - { - cols = InsertOrReplaceColumns; - } - - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); - insertCommand.CommandText = insertSql; - return insertCommand; - } - - protected internal void Dispose() - { - if (_insertCommand != null) - { - _insertCommand.Dispose(); - _insertCommand = null; - } - } - - public class Column - { - PropertyInfo _prop; - - public string Name { get; private set; } - - public string PropertyName { get { return _prop.Name; } } - - public Type ColumnType { get; private set; } - - public string Collation { get; private set; } - - public bool IsAutoInc { get; private set; } - - public bool IsPK { get; private set; } - - public IEnumerable Indices { get; set; } - - public bool IsNullable { get; private set; } - - public int MaxStringLength { get; private set; } - - public Column(PropertyInfo prop) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); - IsAutoInc = Orm.IsAutoInc(prop); - IsPK = Orm.IsPK(prop); - Indices = Orm.GetIndices(prop); - IsNullable = !IsPK; - MaxStringLength = Orm.MaxStringLength(prop); - } - - public void SetValue(object obj, object val) - { - _prop.SetValue(obj, val, null); - } - - public object GetValue(object obj) - { - return _prop.GetValue(obj, null); - } - } - } - - public static class Orm - { - public const int DefaultMaxStringLength = 140; - - public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks) - { - string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks) + " "; - - if (p.IsPK) - { - decl += "primary key "; - } - if (p.IsAutoInc) - { - decl += "autoincrement "; - } - if (!p.IsNullable) - { - decl += "not null "; - } - if (!string.IsNullOrEmpty(p.Collation)) - { - decl += "collate " + p.Collation + " "; - } - - return decl; - } - - public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks) - { - var clrType = p.ColumnType; - if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32)) - { - return "integer"; - } - else if (clrType == typeof(UInt32) || clrType == typeof(Int64)) - { - return "bigint"; - } - else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) - { - return "float"; - } - else if (clrType == typeof(String)) - { - int len = p.MaxStringLength; - return "varchar(" + len + ")"; - } - else if (clrType == typeof(DateTime)) - { - return storeDateTimeAsTicks ? "bigint" : "datetime"; -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } - else if (clrType.GetTypeInfo().IsEnum) - { -#endif - return "integer"; - } - else if (clrType == typeof(byte[])) - { - return "blob"; - } - else if (clrType == typeof(Guid)) - { - return "varchar(36)"; - } - else { - throw new NotSupportedException("Don't know about " + clrType); - } - } - - public static bool IsPK(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static string Collation(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(CollationAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - { - return ((CollationAttribute)attrs.First()).Value; -#endif - } - else { - return string.Empty; - } - } - - public static bool IsAutoInc(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(AutoIncrementAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static IEnumerable GetIndices(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); - } - - public static int MaxStringLength(PropertyInfo p) - { - var attrs = p.GetCustomAttributes(typeof(MaxLengthAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((MaxLengthAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - { - return ((MaxLengthAttribute)attrs.First()).Value; -#endif - } - else { - return DefaultMaxStringLength; - } - } - } - - public partial class SQLiteCommand - { - SQLiteConnection _conn; - private List _bindings; - - public string CommandText { get; set; } - - internal SQLiteCommand(SQLiteConnection conn) - { - _conn = conn; - _bindings = new List(); - CommandText = ""; - } - - public int ExecuteNonQuery() - { - if (_conn.Trace) - { - Debug.WriteLine("Executing: " + this); - } - - var r = SQLite3.Result.OK; - var stmt = Prepare(); - r = SQLite3.Step(stmt); - Finalize(stmt); - if (r == SQLite3.Result.Done) - { - int rowsAffected = SQLite3.Changes(_conn.Handle); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) - { - string msg = SQLite3.GetErrmsg(_conn.Handle); - throw SQLiteException.New(r, msg); - } - else { - throw SQLiteException.New(r, r.ToString()); - } - } - - public IEnumerable ExecuteDeferredQuery() - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); - } - - public List ExecuteQuery() - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); - } - - public List ExecuteQuery(TableMapping map) - { - return ExecuteDeferredQuery(map).ToList(); - } - - /// - /// Invoked every time an instance is loaded from the database. - /// - /// - /// The newly created object. - /// - /// - /// This can be overridden in combination with the - /// method to hook into the life-cycle of objects. - /// - /// Type safety is not possible because MonoTouch does not support virtual generic methods. - /// - protected virtual void OnInstanceCreated(object obj) - { - // Can be overridden. - } - - public IEnumerable ExecuteDeferredQuery(TableMapping map) - { - if (_conn.Trace) - { - Debug.WriteLine("Executing Query: " + this); - } - - var stmt = Prepare(); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)]; - - for (int i = 0; i < cols.Length; i++) - { - var name = SQLite3.ColumnName16(stmt, i); - cols[i] = map.FindColumn(name); - } - - while (SQLite3.Step(stmt) == SQLite3.Result.Row) - { - var obj = Activator.CreateInstance(map.MappedType); - for (int i = 0; i < cols.Length; i++) - { - if (cols[i] == null) - continue; - var colType = SQLite3.ColumnType(stmt, i); - var val = ReadCol(stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue(obj, val); - } - OnInstanceCreated(obj); - yield return (T)obj; - } - } - finally - { - SQLite3.Finalize(stmt); - } - } - - public T ExecuteScalar() - { - if (_conn.Trace) - { - Debug.WriteLine("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare(); - if (SQLite3.Step(stmt) == SQLite3.Result.Row) - { - var colType = SQLite3.ColumnType(stmt, 0); - val = (T)ReadCol(stmt, 0, colType, typeof(T)); - } - Finalize(stmt); - - return val; - } - - public void Bind(string name, object val) - { - _bindings.Add(new Binding - { - Name = name, - Value = val - }); - } - - public void Bind(object val) - { - Bind(null, val); - } - - public override string ToString() - { - var parts = new string[1 + _bindings.Count]; - parts[0] = CommandText; - var i = 1; - foreach (var b in _bindings) - { - parts[i] = string.Format(" {0}: {1}", i - 1, b.Value); - i++; - } - return string.Join(Environment.NewLine, parts); - } - - Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2(_conn.Handle, CommandText); - BindAll(stmt); - return stmt; - } - - void Finalize(Sqlite3Statement stmt) - { - SQLite3.Finalize(stmt); - } - - void BindAll(Sqlite3Statement stmt) - { - int nextIdx = 1; - foreach (var b in _bindings) - { - if (b.Name != null) - { - b.Index = SQLite3.BindParameterIndex(stmt, b.Name); - } - else { - b.Index = nextIdx++; - } - - BindParameter(stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); - } - } - - internal static IntPtr NegativePointer = new IntPtr(-1); - - internal static void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) - { - SQLite3.BindNull(stmt, index); - } - else { - if (value is Int32) - { - SQLite3.BindInt(stmt, index, (int)value); - } - else if (value is String) - { - SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer); - } - else if (value is Byte || value is UInt16 || value is SByte || value is Int16) - { - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is Boolean) - { - SQLite3.BindInt(stmt, index, (bool)value ? 1 : 0); - } - else if (value is UInt32 || value is Int64) - { - SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); - } - else if (value is Single || value is Double || value is Decimal) - { - SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); - } - else if (value is DateTime) - { - if (storeDateTimeAsTicks) - { - SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks); - } - else { - SQLite3.BindText(stmt, index, ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } -#if !NETFX_CORE - } else if (value.GetType().IsEnum) { -#else - } - else if (value.GetType().GetTypeInfo().IsEnum) - { -#endif - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is byte[]) - { - SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); - } - else if (value is Guid) - { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } - else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - } - } - - class Binding - { - public string Name { get; set; } - - public object Value { get; set; } - - public int Index { get; set; } - } - - object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) - { - return null; - } - else { - if (clrType == typeof(String)) - { - return SQLite3.ColumnString(stmt, index); - } - else if (clrType == typeof(Int32)) - { - return (int)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Boolean)) - { - return SQLite3.ColumnInt(stmt, index) == 1; - } - else if (clrType == typeof(double)) - { - return SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(float)) - { - return (float)SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(DateTime)) - { - if (_conn.StoreDateTimeAsTicks) - { - return new DateTime(SQLite3.ColumnInt64(stmt, index)); - } - else { - var text = SQLite3.ColumnString(stmt, index); - return DateTime.Parse(text); - } -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } - else if (clrType.GetTypeInfo().IsEnum) - { -#endif - return SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Int64)) - { - return SQLite3.ColumnInt64(stmt, index); - } - else if (clrType == typeof(UInt32)) - { - return (uint)SQLite3.ColumnInt64(stmt, index); - } - else if (clrType == typeof(decimal)) - { - return (decimal)SQLite3.ColumnDouble(stmt, index); - } - else if (clrType == typeof(Byte)) - { - return (byte)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(UInt16)) - { - return (ushort)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(Int16)) - { - return (short)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(sbyte)) - { - return (sbyte)SQLite3.ColumnInt(stmt, index); - } - else if (clrType == typeof(byte[])) - { - return SQLite3.ColumnByteArray(stmt, index); - } - else if (clrType == typeof(Guid)) - { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } - else { - throw new NotSupportedException("Don't know how to read " + clrType); - } - } - } - } - - /// - /// Since the insert never changed, we only need to prepare once. - /// - public class PreparedSqlLiteInsertCommand : IDisposable - { - public bool Initialized { get; set; } - - protected SQLiteConnection Connection { get; set; } - - public string CommandText { get; set; } - - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); - - internal PreparedSqlLiteInsertCommand(SQLiteConnection conn) - { - Connection = conn; - } - - public int ExecuteNonQuery(object[] source) - { - if (Connection.Trace) - { - Debug.WriteLine("Executing: " + CommandText); - } - - var r = SQLite3.Result.OK; - - if (!Initialized) - { - Statement = Prepare(); - Initialized = true; - } - - //bind the values. - if (source != null) - { - for (int i = 0; i < source.Length; i++) - { - SQLiteCommand.BindParameter(Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks); - } - } - r = SQLite3.Step(Statement); - - if (r == SQLite3.Result.Done) - { - int rowsAffected = SQLite3.Changes(Connection.Handle); - SQLite3.Reset(Statement); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) - { - string msg = SQLite3.GetErrmsg(Connection.Handle); - SQLite3.Reset(Statement); - throw SQLiteException.New(r, msg); - } - else { - SQLite3.Reset(Statement); - throw SQLiteException.New(r, r.ToString()); - } - } - - protected virtual Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2(Connection.Handle, CommandText); - return stmt; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (Statement != NullStatement) - { - try - { - SQLite3.Finalize(Statement); - } - finally - { - Statement = NullStatement; - Connection = null; - } - } - } - - ~PreparedSqlLiteInsertCommand() - { - Dispose(false); - } - } - - public abstract class BaseTableQuery - { - protected class Ordering - { - public string ColumnName { get; set; } - public bool Ascending { get; set; } - } - } - - public class TableQuery : BaseTableQuery, IEnumerable - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - Expression _where; - List _orderBys; - int? _limit; - int? _offset; - - BaseTableQuery _joinInner; - Expression _joinInnerKeySelector; - BaseTableQuery _joinOuter; - Expression _joinOuterKeySelector; - Expression _joinSelector; - - Expression _selector; - - TableQuery(SQLiteConnection conn, TableMapping table) - { - Connection = conn; - Table = table; - } - - public TableQuery(SQLiteConnection conn) - { - Connection = conn; - Table = Connection.GetMapping(typeof(T)); - } - - public TableQuery Clone() - { - var q = new TableQuery(Connection, Table); - q._where = _where; - q._deferred = _deferred; - if (_orderBys != null) - { - q._orderBys = new List(_orderBys); - } - q._limit = _limit; - q._offset = _offset; - q._joinInner = _joinInner; - q._joinInnerKeySelector = _joinInnerKeySelector; - q._joinOuter = _joinOuter; - q._joinOuterKeySelector = _joinOuterKeySelector; - q._joinSelector = _joinSelector; - q._selector = _selector; - return q; - } - - public TableQuery Where(Expression> predExpr) - { - if (predExpr.NodeType == ExpressionType.Lambda) - { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone(); - q.AddWhere(pred); - return q; - } - else { - throw new NotSupportedException("Must be a predicate"); - } - } - - public TableQuery Take(int n) - { - var q = Clone(); - q._limit = n; - return q; - } - - public TableQuery Skip(int n) - { - var q = Clone(); - q._offset = n; - return q; - } - - public T ElementAt(int index) - { - return Skip(index).Take(1).First(); - } - - bool _deferred = false; - public TableQuery Deferred() - { - var q = Clone(); - q._deferred = true; - return q; - } - - public TableQuery OrderBy(Expression> orderExpr) - { - return AddOrderBy(orderExpr, true); - } - - public TableQuery OrderByDescending(Expression> orderExpr) - { - return AddOrderBy(orderExpr, false); - } - - private TableQuery AddOrderBy(Expression> orderExpr, bool asc) - { - if (orderExpr.NodeType == ExpressionType.Lambda) - { - var lambda = (LambdaExpression)orderExpr; - - MemberExpression mem = null; - - var unary = lambda.Body as UnaryExpression; - if (unary != null && unary.NodeType == ExpressionType.Convert) - { - mem = unary.Operand as MemberExpression; - } - else { - mem = lambda.Body as MemberExpression; - } - - if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) - { - var q = Clone(); - if (q._orderBys == null) - { - q._orderBys = new List(); - } - q._orderBys.Add(new Ordering - { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return q; - } - else { - throw new NotSupportedException("Order By does not support: " + orderExpr); - } - } - else { - throw new NotSupportedException("Must be a predicate"); - } - } - - private void AddWhere(Expression pred) - { - if (_where == null) - { - _where = pred; - } - else { - _where = Expression.AndAlso(_where, pred); - } - } - - public TableQuery Join( - TableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - var q = new TableQuery(Connection, Connection.GetMapping(typeof(TResult))) - { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public TableQuery Select(Expression> selector) - { - var q = Clone(); - q._selector = selector; - return q; - } - - private SQLiteCommand GenerateCommand(string selectionList) - { - if (_joinInner != null && _joinOuter != null) - { - throw new NotSupportedException("Joins are not supported."); - } - else { - var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; - var args = new List(); - if (_where != null) - { - var w = CompileExpr(_where, args); - cmdText += " where " + w.CommandText; - } - if ((_orderBys != null) && (_orderBys.Count > 0)) - { - var t = string.Join(", ", _orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); - cmdText += " order by " + t; - } - if (_limit.HasValue) - { - cmdText += " limit " + _limit.Value; - } - if (_offset.HasValue) - { - if (!_limit.HasValue) - { - cmdText += " limit -1 "; - } - cmdText += " offset " + _offset.Value; - } - return Connection.CreateCommand(cmdText, args.ToArray()); - } - } - - class CompileResult - { - public string CommandText { get; set; } - - public object Value { get; set; } - } - - private CompileResult CompileExpr(Expression expr, List queryArgs) - { - if (expr == null) - { - throw new NotSupportedException("Expression is NULL"); - } - else if (expr is BinaryExpression) - { - var bin = (BinaryExpression)expr; - - var leftr = CompileExpr(bin.Left, queryArgs); - var rightr = CompileExpr(bin.Right, queryArgs); - - //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") - string text; - if (leftr.CommandText == "?" && leftr.Value == null) - text = CompileNullBinaryExpression(bin, rightr); - else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); - else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; - return new CompileResult { CommandText = text }; - } - else if (expr.NodeType == ExpressionType.Call) - { - - var call = (MethodCallExpression)expr; - var args = new CompileResult[call.Arguments.Count]; - var obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null; - - for (var i = 0; i < args.Length; i++) - { - args[i] = CompileExpr(call.Arguments[i], queryArgs); - } - - var sqlCall = ""; - - if (call.Method.Name == "Like" && args.Length == 2) - { - sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 2) - { - sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 1) - { - if (call.Object != null && call.Object.Type == typeof(string)) - { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + " || '%'))"; - } - else { - sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")"; - } - } - else if (call.Method.Name == "StartsWith" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; - } - else if (call.Method.Name == "EndsWith" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; - } - else if (call.Method.Name == "Equals" && args.Length == 1) - { - sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; - } - else { - sqlCall = call.Method.Name.ToLower() + "(" + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")"; - } - return new CompileResult { CommandText = sqlCall }; - - } - else if (expr.NodeType == ExpressionType.Constant) - { - var c = (ConstantExpression)expr; - queryArgs.Add(c.Value); - return new CompileResult - { - CommandText = "?", - Value = c.Value - }; - } - else if (expr.NodeType == ExpressionType.Convert) - { - var u = (UnaryExpression)expr; - var ty = u.Type; - var valr = CompileExpr(u.Operand, queryArgs); - return new CompileResult - { - CommandText = valr.CommandText, - Value = valr.Value != null ? ConvertTo(valr.Value, ty) : null - }; - } - else if (expr.NodeType == ExpressionType.MemberAccess) - { - var mem = (MemberExpression)expr; - - if (mem.Expression != null && mem.Expression.NodeType == ExpressionType.Parameter) - { - // - // This is a column of our table, output just the column name - // Need to translate it if that column name is mapped - // - var columnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name; - return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } - else { - object obj = null; - if (mem.Expression != null) - { - var r = CompileExpr(mem.Expression, queryArgs); - if (r.Value == null) - { - throw new NotSupportedException("Member access failed to compile expression"); - } - if (r.CommandText == "?") - { - queryArgs.RemoveAt(queryArgs.Count - 1); - } - obj = r.Value; - } - - // - // Get the member value - // - object val = null; - -#if !NETFX_CORE - if (mem.Member.MemberType == MemberTypes.Property) { -#else - if (mem.Member is PropertyInfo) - { -#endif - var m = (PropertyInfo)mem.Member; - val = m.GetValue(obj, null); -#if !NETFX_CORE - } else if (mem.Member.MemberType == MemberTypes.Field) { -#else - } - else if (mem.Member is FieldInfo) - { -#endif -#if SILVERLIGHT - val = Expression.Lambda (expr).Compile ().DynamicInvoke (); -#else - var m = (FieldInfo)mem.Member; - val = m.GetValue(obj); -#endif - } - else { -#if !NETFX_CORE - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType.ToString ()); -#else - throw new NotSupportedException("MemberExpr: " + mem.Member.DeclaringType.ToString()); -#endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string)) - { - var sb = new System.Text.StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (System.Collections.IEnumerable)val) - { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult - { - CommandText = sb.ToString(), - Value = val - }; - } - else { - queryArgs.Add(val); - return new CompileResult - { - CommandText = "?", - Value = val - }; - } - } - } - throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString()); - } - - static object ConvertTo(object obj, Type t) - { - Type nut = Nullable.GetUnderlyingType(t); - - if (nut != null) - { - if (obj == null) return null; - return Convert.ChangeType(obj, nut); - } - else { - return Convert.ChangeType(obj, t); - } - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) - { - if (expression.NodeType == ExpressionType.Equal) - return "(" + parameter.CommandText + " is ?)"; - else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else - throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); - } - - string GetSqlName(Expression expr) - { - var n = expr.NodeType; - if (n == ExpressionType.GreaterThan) - return ">"; - else if (n == ExpressionType.GreaterThanOrEqual) - { - return ">="; - } - else if (n == ExpressionType.LessThan) - { - return "<"; - } - else if (n == ExpressionType.LessThanOrEqual) - { - return "<="; - } - else if (n == ExpressionType.And) - { - return "&"; - } - else if (n == ExpressionType.AndAlso) - { - return "and"; - } - else if (n == ExpressionType.Or) - { - return "|"; - } - else if (n == ExpressionType.OrElse) - { - return "or"; - } - else if (n == ExpressionType.Equal) - { - return "="; - } - else if (n == ExpressionType.NotEqual) - { - return "!="; - } - else { - throw new System.NotSupportedException("Cannot get SQL for: " + n.ToString()); - } - } - - public int Count() - { - return GenerateCommand("count(*)").ExecuteScalar(); - } - - public IEnumerator GetEnumerator() - { - if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public T First() - { - var query = Take(1); - return query.ToList().First(); - } - - public T FirstOrDefault() - { - var query = this.Take(1); - return query.ToList().FirstOrDefault(); - } - } - - public static class SQLite3 - { - public enum Result : int - { - OK = 0, - Error = 1, - Internal = 2, - Perm = 3, - Abort = 4, - Busy = 5, - Locked = 6, - NoMem = 7, - ReadOnly = 8, - Interrupt = 9, - IOError = 10, - Corrupt = 11, - NotFound = 12, - Full = 13, - CannotOpen = 14, - LockErr = 15, - Empty = 16, - SchemaChngd = 17, - TooBig = 18, - Constraint = 19, - Mismatch = 20, - Misuse = 21, - NotImplementedLFS = 22, - AccessDenied = 23, - Format = 24, - Range = 25, - NonDBFile = 26, - Row = 100, - Done = 101 - } - - public enum ConfigOption : int - { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - -#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE - [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Close(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Config(ConfigOption option); - - [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int SetDirectory(uint directoryType, string directoryPath); - - [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] - public static extern Result BusyTimeout(IntPtr db, int milliseconds); - - [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] - public static extern int Changes(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); - - public static IntPtr Prepare2(IntPtr db, string query) - { - IntPtr stmt; - var r = Prepare2(db, query, query.Length, out stmt, IntPtr.Zero); - if (r != Result.OK) - { - throw SQLiteException.New(r, GetErrmsg(db)); - } - return stmt; - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Step(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Reset(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Finalize(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] - public static extern long LastInsertRowid(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr Errmsg(IntPtr db); - - public static string GetErrmsg(IntPtr db) - { - return Marshal.PtrToStringUni(Errmsg(db)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindNull(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt(IntPtr stmt, int index, int val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt64(IntPtr stmt, int index, long val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindDouble(IntPtr stmt, int index, double val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnCount(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnName(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr ColumnName16Internal(IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] - public static extern ColType ColumnType(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnInt(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern long ColumnInt64(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] - public static extern double ColumnDouble(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnBytes(IntPtr stmt, int index); - - public static string ColumnString(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(SQLite3.ColumnText16(stmt, index)); - } - - public static byte[] ColumnByteArray(IntPtr stmt, int index) - { - int length = ColumnBytes(stmt, index); - byte[] result = new byte[length]; - if (length > 0) - Marshal.Copy(ColumnBlob(stmt, index), result, 0, length); - return result; - } -#else - public static Result Open(string filename, out Sqlite3DatabaseHandle db) - { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } - - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) - { -#if USE_WP8_NATIVE_SQLITE - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); -#else - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); -#endif - } - - public static Result Close(Sqlite3DatabaseHandle db) - { - return (Result)Sqlite3.sqlite3_close(db); - } - - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) - { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } - - public static int Changes(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_changes(db); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) - { - Sqlite3Statement stmt = default(Sqlite3Statement); -#if USE_WP8_NATIVE_SQLITE - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); -#else - stmt = new Sqlite3Statement(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); -#endif - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } - - public static Result Step(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_step(stmt); - } - - public static Result Reset(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_reset(stmt); - } - - public static Result Finalize(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } - - public static long LastInsertRowid(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_last_insert_rowid(db); - } - - public static string GetErrmsg(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3Statement stmt, string name) - { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3Statement stmt, int index, int val) - { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3Statement stmt, int index, long val) - { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3Statement stmt, int index, double val) - { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_text(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); -#endif - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); -#endif - } - - public static int ColumnCount(Sqlite3Statement stmt) - { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3Statement stmt, int index) - { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) - { - return ColumnBlob(stmt, index); - } -#endif - - public enum ColType : int - { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } -} diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs deleted file mode 100644 index 3e25387..0000000 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.Library/Sqlite/SqliteAsync.cs +++ /dev/null @@ -1,510 +0,0 @@ -// -// Copyright (c) 2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Labs.SightsToSee.Library.Sqlite -{ - public partial class SQLiteAsyncConnection - { - SQLiteConnectionString _connectionString; - - public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = false) - { - _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); - } - - SQLiteConnectionWithLock GetConnection() - { - return SQLiteConnectionPool.Shared.GetConnection(_connectionString); - } - - public Task CreateTableAsync() - where T : new() - { - return CreateTablesAsync(typeof(T)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - where T4 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3), typeof(T4)); - } - - public Task CreateTablesAsync() - where T : new() - where T2 : new() - where T3 : new() - where T4 : new() - where T5 : new() - { - return CreateTablesAsync(typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); - } - - public Task CreateTablesAsync(params Type[] types) - { - return Task.Factory.StartNew(() => { - CreateTablesResult result = new CreateTablesResult(); - var conn = GetConnection(); - using (conn.Lock()) - { - foreach (Type type in types) - { - int aResult = conn.CreateTable(type); - result.Results[type] = aResult; - } - } - return result; - }); - } - - public Task DropTableAsync() - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.DropTable(); - } - }); - } - - public Task InsertAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Insert(item); - } - }); - } - - public Task UpdateAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Update(item); - } - }); - } - - public Task DeleteAsync(object item) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Delete(item); - } - }); - } - - public Task GetAsync(object pk) - where T : new() - { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get(pk); - } - }); - } - - public Task FindAsync(object pk) - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Find(pk); - } - }); - } - - public Task GetAsync(Expression> predicate) - where T : new() - { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get(predicate); - } - }); - } - - public Task FindAsync(Expression> predicate) - where T : new() - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Find(predicate); - } - }); - } - - public Task ExecuteAsync(string query, params object[] args) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Execute(query, args); - } - }); - } - - public Task InsertAllAsync(IEnumerable items) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.InsertAll(items); - } - }); - } - - [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] - public Task RunInTransactionAsync(Action action) - { - return Task.Factory.StartNew(() => { - var conn = this.GetConnection(); - using (conn.Lock()) - { - conn.BeginTransaction(); - try - { - action(this); - conn.Commit(); - } - catch (Exception) - { - conn.Rollback(); - throw; - } - } - }); - } - - public Task RunInTransactionAsync(Action action) - { - return Task.Factory.StartNew(() => - { - var conn = this.GetConnection(); - using (conn.Lock()) - { - conn.BeginTransaction(); - try - { - action(conn); - conn.Commit(); - } - catch (Exception) - { - conn.Rollback(); - throw; - } - } - }); - } - - public AsyncTableQuery Table() - where T : new() - { - // - // This isn't async as the underlying connection doesn't go out to the database - // until the query is performed. The Async methods are on the query iteself. - // - var conn = GetConnection(); - return new AsyncTableQuery(conn.Table()); - } - - public Task ExecuteScalarAsync(string sql, params object[] args) - { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - var command = conn.CreateCommand(sql, args); - return command.ExecuteScalar(); - } - }); - } - - public Task> QueryAsync(string sql, params object[] args) - where T : new() - { - return Task>.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Query(sql, args); - } - }); - } - } - - // - // TODO: Bind to AsyncConnection.GetConnection instead so that delayed - // execution can still work after a Pool.Reset. - // - public class AsyncTableQuery - where T : new() - { - TableQuery _innerQuery; - - public AsyncTableQuery(TableQuery innerQuery) - { - _innerQuery = innerQuery; - } - - public AsyncTableQuery Where(Expression> predExpr) - { - return new AsyncTableQuery(_innerQuery.Where(predExpr)); - } - - public AsyncTableQuery Skip(int n) - { - return new AsyncTableQuery(_innerQuery.Skip(n)); - } - - public AsyncTableQuery Take(int n) - { - return new AsyncTableQuery(_innerQuery.Take(n)); - } - - public AsyncTableQuery OrderBy(Expression> orderExpr) - { - return new AsyncTableQuery(_innerQuery.OrderBy(orderExpr)); - } - - public AsyncTableQuery OrderByDescending(Expression> orderExpr) - { - return new AsyncTableQuery(_innerQuery.OrderByDescending(orderExpr)); - } - - public Task> ToListAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.ToList(); - } - }); - } - - public Task CountAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.Count(); - } - }); - } - - public Task ElementAtAsync(int index) - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.ElementAt(index); - } - }); - } - - public Task FirstAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.First(); - } - }); - } - - public Task FirstOrDefaultAsync() - { - return Task.Factory.StartNew(() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock()) - { - return _innerQuery.FirstOrDefault(); - } - }); - } - } - - public class CreateTablesResult - { - public Dictionary Results { get; private set; } - - internal CreateTablesResult() - { - this.Results = new Dictionary(); - } - } - - class SQLiteConnectionPool - { - class Entry - { - public SQLiteConnectionString ConnectionString { get; private set; } - public SQLiteConnectionWithLock Connection { get; private set; } - - public Entry(SQLiteConnectionString connectionString) - { - ConnectionString = connectionString; - Connection = new SQLiteConnectionWithLock(connectionString); - } - - public void OnApplicationSuspended() - { - Connection.Dispose(); - Connection = null; - } - } - - readonly Dictionary _entries = new Dictionary(); - readonly object _entriesLock = new object(); - - static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool(); - - /// - /// Gets the singleton instance of the connection tool. - /// - public static SQLiteConnectionPool Shared - { - get - { - return _shared; - } - } - - public SQLiteConnectionWithLock GetConnection(SQLiteConnectionString connectionString) - { - lock (_entriesLock) - { - Entry entry; - string key = connectionString.ConnectionString; - - if (!_entries.TryGetValue(key, out entry)) - { - entry = new Entry(connectionString); - _entries[key] = entry; - } - - return entry.Connection; - } - } - - /// - /// Closes all connections managed by this pool. - /// - public void Reset() - { - lock (_entriesLock) - { - foreach (var entry in _entries.Values) - { - entry.OnApplicationSuspended(); - } - _entries.Clear(); - } - } - - /// - /// Call this method when the application is suspended. - /// - /// Behaviour here is to close any open connections. - public void ApplicationSuspended() - { - Reset(); - } - } - - class SQLiteConnectionWithLock : SQLiteConnection - { - readonly object _lockPoint = new object(); - - public SQLiteConnectionWithLock(SQLiteConnectionString connectionString) - : base(connectionString.DatabasePath, connectionString.StoreDateTimeAsTicks) - { - } - - public IDisposable Lock() - { - return new LockWrapper(_lockPoint); - } - - private class LockWrapper : IDisposable - { - object _lockPoint; - - public LockWrapper(object lockPoint) - { - _lockPoint = lockPoint; - Monitor.Enter(_lockPoint); - } - - public void Dispose() - { - Monitor.Exit(_lockPoint); - } - } - } -} - diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj index a1236f5..af8eb8e 100644 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj +++ b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee.csproj @@ -25,7 +25,7 @@ true bin\x86\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS ;2008 full x86 @@ -48,7 +48,7 @@ true bin\ARM\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS ;2008 full ARM @@ -71,7 +71,7 @@ true bin\x64\Debug\ - TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;SQLITE + TRACE;DEBUG;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS ;2008 full x64 diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx new file mode 100644 index 0000000..a066ddc Binary files /dev/null and b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee_StoreKey.pfx differ diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml index 5ab59e2..44856ad 100644 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml +++ b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/SightDetailPage.xaml @@ -95,8 +95,7 @@ - - + @@ -104,9 +103,7 @@ - - - + @@ -119,8 +116,7 @@ - - + @@ -142,7 +138,6 @@ - @@ -164,7 +159,7 @@ - + @@ -191,8 +186,6 @@ - - diff --git a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/TripDetailPage.xaml b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/TripDetailPage.xaml index 04c27bc..9674d36 100644 --- a/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/TripDetailPage.xaml +++ b/Workshop/Module3-ConnectedApps/Source/End/Microsoft.Labs.SightsToSee/Microsoft.Labs.SightsToSee/Views/TripDetailPage.xaml @@ -175,23 +175,6 @@ - - diff --git a/Workshop/Module3-ConnectedApps/Source/Setup/scripts/snippets/ConnectedApps.vsi b/Workshop/Module3-ConnectedApps/Source/Setup/scripts/snippets/ConnectedApps.vsi index 65a92ad..5e50eb3 100644 Binary files a/Workshop/Module3-ConnectedApps/Source/Setup/scripts/snippets/ConnectedApps.vsi and b/Workshop/Module3-ConnectedApps/Source/Setup/scripts/snippets/ConnectedApps.vsi differ