Skip to content

Commit

Permalink
Add View support (#11)
Browse files Browse the repository at this point in the history
* Add View support

Also adds lifetimes to make sure nothing (except for a new Record) outlives a Session or any of its ancestors.

* Use pwsh shell
  • Loading branch information
heaths authored Aug 17, 2022
1 parent 838187b commit db72ac0
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 41 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ jobs:
with:
command: test
args: --all
- uses: actions-rs/cargo@v1
- name: Release
run: gh release create ${{ github.ref_name }} --generate-notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish
uses: actions-rs/cargo@v1
with:
command: publish
env:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[package]
name = "msica"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MIT"
description = "Rust for Windows Installer Custom Actions"
Expand Down
17 changes: 17 additions & 0 deletions examples/deferred/deferred.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,22 @@
<InstallExecuteSequence>
<Custom Action="DeferredExampleCustomAction" After="InstallInitialize" />
</InstallExecuteSequence>

<CustomTable Id="DeferredExample">
<Column Id="Cardinal" Type="int" Category="Integer" Width="2" PrimaryKey="yes" />
<Column Id="Ordinal" Type="string" Category="LowerCase" Localizable="yes" />
<Row>
<Data Column="Cardinal">1</Data>
<Data Column="Ordinal">first</Data>
</Row>
<Row>
<Data Column="Cardinal">2</Data>
<Data Column="Ordinal">second</Data>
</Row>
<Row>
<Data Column="Cardinal">3</Data>
<Data Column="Ordinal">third</Data>
</Row>
</CustomTable>
</Fragment>
</Wix>
22 changes: 17 additions & 5 deletions examples/deferred/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ const ERROR_SUCCESS: u32 = 0;
pub extern "C" fn DeferredExampleCustomAction(h: MSIHANDLE) -> u32 {
let session = Session::from(h);

// Simulate reading data from a custom table.
for i in 0..5 {
session.do_deferred_action("DeferredExampleCustomActionDeferred", &i.to_string())
let database = session.database();
let mut view = database
.open_view("SELECT `Cardinal`, `Ordinal` FROM `DeferredExample` ORDER BY `Cardinal`");
view.execute(None);
for record in view.into_iter() {
let data = format!(
"{}\t{}",
record.integer_data(1).unwrap(),
record.string_data(2)
);
session.do_deferred_action("DeferredExampleCustomActionDeferred", &data);
}
ERROR_SUCCESS
}
Expand All @@ -22,9 +30,13 @@ pub extern "C" fn DeferredExampleCustomActionDeferred(h: MSIHANDLE) -> u32 {
// Process the custom action data passed by the immediate custom action.
// This data is always made available in a property named "CustomActionData".
let data = session.property("CustomActionData");
let fields: Vec<&str> = data.split('\t').collect();
let record = Record::with_fields(
Some("Running deferred custom action [1]"),
vec![Field::StringData(data)],
Some("Running the [2] ([1]) deferred custom action"),
vec![
Field::StringData(fields[0].to_string()),
Field::StringData(fields[1].to_string()),
],
);
session.message(MessageType::Info, &record);
ERROR_SUCCESS
Expand Down
42 changes: 38 additions & 4 deletions src/database.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
// Copyright 2022 Heath Stewart.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

use super::{MSIHANDLE, PMSIHANDLE};
use super::ffi;
use super::{Record, View, MSIHANDLE, PMSIHANDLE};
use std::ffi::CString;

/// The database for the current install session.
pub struct Database {
h: PMSIHANDLE,
pub struct Database<'a> {
h: PMSIHANDLE<'a>,
}

impl From<MSIHANDLE> for Database {
impl<'a> Database<'a> {
/// Returns a [`View`] object that represents the query specified by a
/// [SQL string](https://docs.microsoft.com/windows/win32/msi/sql-syntax).
pub fn open_view(&'a self, sql: &str) -> View<'a> {
unsafe {
let mut h = MSIHANDLE::null();
let sql = CString::new(sql).unwrap();

// TODO: Return Result<View<'a>, ?>.
ffi::MsiDatabaseOpenView(*self.h, sql.as_ptr(), &mut h);

View::from(h)
}
}

/// Returns a [`Record`] object containing the table name in field 0 and the column names
/// (comprising the primary keys) in succeeding fields corresponding to their column numbers.
///
/// The field count of the record is the count of primary key columns.
pub fn primary_keys(&'a self, table: &str) -> Record<'a> {
unsafe {
let mut h = MSIHANDLE::null();
let table = CString::new(table).unwrap();

// TODO: Return Result<View<'a>, ?>.
ffi::MsiDatabaseGetPrimaryKeys(*self.h, table.as_ptr(), &mut h);

Record::from(h)
}
}
}

impl<'a> From<MSIHANDLE> for Database<'a> {
fn from(h: MSIHANDLE) -> Self {
Database { h: h.to_owned() }
}
Expand Down
21 changes: 21 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub const ERROR_INSTALL_FAILURE: u32 = 1603;
pub const ERROR_FUNCTION_NOT_CALLED: u32 = 1626;

pub(crate) const ERROR_MORE_DATA: u32 = 234;
pub(crate) const MSI_NULL_INTEGER: i32 = -0x8000_0000;

// cspell:ignore pcch
#[link(name = "msi")]
Expand All @@ -22,6 +23,20 @@ extern "C" {

pub fn MsiCreateRecord(cParams: u32) -> MSIHANDLE;

#[link_name = "MsiDatabaseGetPrimaryKeysA"]
pub fn MsiDatabaseGetPrimaryKeys(
hDatabase: MSIHANDLE,
szTableName: LPCSTR,
hRecord: &mut MSIHANDLE,
) -> u32;

#[link_name = "MsiDatabaseOpenViewA"]
pub fn MsiDatabaseOpenView(
hDatabase: MSIHANDLE,
szQuery: LPCSTR,
phView: &mut MSIHANDLE,
) -> u32;

#[link_name = "MsiDoActionA"]
pub fn MsiDoAction(hInstall: MSIHANDLE, szAction: LPCSTR) -> u32;

Expand Down Expand Up @@ -76,6 +91,12 @@ extern "C" {

#[link_name = "MsiSetPropertyA"]
pub fn MsiSetProperty(hInstall: MSIHANDLE, szName: LPCSTR, szValue: LPCSTR) -> u32;

pub fn MsiViewClose(hView: MSIHANDLE) -> u32;

pub fn MsiViewExecute(hView: MSIHANDLE, hRecord: MSIHANDLE) -> u32;

pub fn MsiViewFetch(hView: MSIHANDLE, phRecord: &mut MSIHANDLE) -> u32;
}

#[derive(Copy, Clone)]
Expand Down
57 changes: 44 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod database;
mod ffi;
mod record;
mod session;
mod view;

pub use database::Database;
pub use ffi::{
Expand All @@ -22,8 +23,9 @@ pub use ffi::{
};
pub use record::{Field, Record};
pub use session::Session;
pub use view::{View, ViewIterator};

use std::{fmt::Debug, ops::Deref};
use std::{fmt::Debug, marker::PhantomData, ops::Deref};

/// Message types that can be processed by a custom action.
#[repr(u32)]
Expand Down Expand Up @@ -84,7 +86,7 @@ pub enum RunMode {
/// println!("last error: {}", error.format_text());
/// }
/// ```
pub fn last_error_record() -> Option<Record> {
pub fn last_error_record<'a>() -> Option<Record<'a>> {
unsafe {
match ffi::MsiGetLastErrorRecord() {
h if !h.is_null() => Some(h.into()),
Expand All @@ -94,7 +96,7 @@ pub fn last_error_record() -> Option<Record> {
}

/// A Windows Installer handle. This handle is not automatically closed.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq)]
#[repr(transparent)]
pub struct MSIHANDLE(u32);

Expand All @@ -103,8 +105,12 @@ impl MSIHANDLE {
MSIHANDLE(0)
}

fn to_owned(&self) -> PMSIHANDLE {
PMSIHANDLE(*self)
fn to_owned<'a>(&self) -> PMSIHANDLE<'a> {
PMSIHANDLE {
h: *self,
_owned: true,
_phantom: PhantomData,
}
}

fn is_null(&self) -> bool {
Expand Down Expand Up @@ -133,27 +139,52 @@ impl Deref for MSIHANDLE {
}

/// A Windows Installer handle. This handle is automatically closed when dropped.
#[repr(transparent)]
struct PMSIHANDLE(MSIHANDLE);
struct PMSIHANDLE<'a> {
h: MSIHANDLE,
_owned: bool,
_phantom: PhantomData<&'a ()>,
}

impl<'a> Clone for PMSIHANDLE<'a> {
fn clone(&self) -> Self {
PMSIHANDLE {
h: self.h,
_owned: false,
_phantom: PhantomData,
}
}
}

impl Debug for PMSIHANDLE {
impl<'a> Debug for PMSIHANDLE<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MSIHANDLE ({})", *self.0)
write!(f, "MSIHANDLE ({})", *self.h)
}
}

impl Drop for PMSIHANDLE {
impl<'a> Drop for PMSIHANDLE<'a> {
fn drop(&mut self) {
unsafe {
ffi::MsiCloseHandle(**self);
if self._owned {
ffi::MsiCloseHandle(**self);
}
}
}
}

impl Deref for PMSIHANDLE {
impl<'a> Deref for PMSIHANDLE<'a> {
type Target = MSIHANDLE;

fn deref(&self) -> &Self::Target {
&self.0
&self.h
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn is_null() {
assert!(MSIHANDLE::null().is_null());
}
}
43 changes: 32 additions & 11 deletions src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ impl From<&str> for Field {
}

/// A collection of [`Field`] containing strings, integers, and byte streams.
pub struct Record {
h: PMSIHANDLE,
#[derive(Clone)]
pub struct Record<'a> {
h: PMSIHANDLE<'a>,
}

impl Record {
impl<'a> Record<'a> {
/// Creates an empty [`Record`] with capacity for the count of fields specified.
///
/// Field indices are 1-based.
Expand Down Expand Up @@ -213,10 +214,15 @@ impl Record {
/// Some("this is [1] [2]"),
/// vec![Field::IntegerData(1), Field::StringData("example".to_owned())],
/// );
/// assert_eq!(record.integer_data(1), 1);
/// assert_eq!(record.integer_data(1), Some(1));
/// ```
pub fn integer_data(&self, field: u32) -> i32 {
unsafe { ffi::MsiRecordGetInteger(*self.h, field) }
pub fn integer_data(&self, field: u32) -> Option<i32> {
unsafe {
match ffi::MsiRecordGetInteger(*self.h, field) {
i if i == ffi::MSI_NULL_INTEGER => None,
i => Some(i),
}
}
}

/// Sets an integer field in a [`Record`].
Expand All @@ -230,14 +236,22 @@ impl Record {
///
/// let mut record = Record::new(1);
/// record.set_integer_data(1, 42);
/// assert_eq!(record.integer_data(1), 42);
/// assert_eq!(record.integer_data(1), Some(42));
/// ```
pub fn set_integer_data(&self, field: u32, value: i32) {
unsafe {
ffi::MsiRecordSetInteger(*self.h, field, value);
}
}

/// Reads bytes from a record field that contains stream data.
///
/// Field indices are 1-based.
#[allow(unused_variables)]
pub fn stream_data(&self, field: u32) -> Vec<u8> {
todo!()
}

/// Gets whether a field is null in a [`Record`].
///
/// Field indices are 1-based.
Expand All @@ -255,21 +269,21 @@ impl Record {
}
}

impl Deref for Record {
impl<'a> Deref for Record<'a> {
type Target = MSIHANDLE;

fn deref(&self) -> &Self::Target {
&*self.h
}
}

impl From<MSIHANDLE> for Record {
impl<'a> From<MSIHANDLE> for Record<'a> {
fn from(h: MSIHANDLE) -> Self {
Record { h: h.to_owned() }
}
}

impl From<&str> for Record {
impl<'a> From<&str> for Record<'a> {
fn from(s: &str) -> Self {
unsafe {
let h = ffi::MsiCreateRecord(0u32);
Expand All @@ -282,7 +296,7 @@ impl From<&str> for Record {
}
}

impl From<String> for Record {
impl<'a> From<String> for Record<'a> {
fn from(s: String) -> Self {
unsafe {
let h = ffi::MsiCreateRecord(0u32);
Expand Down Expand Up @@ -318,5 +332,12 @@ mod tests {

record.set_string_data(1, None);
assert!(record.is_null(1));
assert_eq!(record.string_data(1), "");
}

#[test]
fn integer_data_from_string() {
let record = Record::with_fields(None, vec![Field::StringData("test".to_owned())]);
assert_eq!(record.integer_data(1), None);
}
}
Loading

0 comments on commit db72ac0

Please sign in to comment.