Desktop

The Briefcase Desktop sample demonstrates using the FolderBriefcase class to save and then load two DataTables that may have changes that are waiting to be sent to the server. It also demonstrates using briefcase properties to ensure that the data in the briefcase is valid and if not delete the briefcase. This sample is available in three of the programming languages that Data Abstract for .NET supports; C#, Oxygene and Visual Basic.

The Briefcase can be helpful for situations where there isn't a persistent connection to the server and the user needs to be able to quit your app or otherwise work in an offline fashion. Another use of a it might be to aid in the application start-up process, as you can load the data from disk without needing to rely on the server being available.

Getting Started

The sample is typically located in C:\Users\Public\Public Documents\RemObjects Samples\Data Abstract for .NET\<Language>\Briefcase, though you may have installed the Data Abstract for .NET SDK and it's samples in another location.

To build it, you will of course need Visual Studio, and like all the samples provided with Data Abstract for .NET you will need to be running the Relativity Server with the DASamples Domain and the Simple Schema available. If you are using the Oxygene version of the sample you will also require Elements to be installed.

Running the Sample

  

  

  

The UI for the sample is split into three parts. A toolbar with various commands, a table that displays data retrieved either from the server or from a briefcase, and finally a "console" like view where log data from the sample is displayed.

The toolbar has 4 buttons; "Reload", "Apply", "Reveal in Explorer" and "Invalidate" along with a drop down box which contains the default address used for an instance of Relativity Server running on a local machine; http://localhost:7099/bin. The "Reload" button retrieves the data again from the server and saves it to a briefcase. The "Apply" button sends any changes to the server to be saved and then a fresh briefcase is saved to the filesystem which will remove the highlighting on the rows. The "Reveal in Explorer" button will open an Explorer window in the folder that contains the briefcase file. Finally the "Invalidate" button will set a boolean which will be checked next time the briefcase is saved to disk and display a information dialog.

When the sample is run, it immediately attempts to access an instance of Relativity Server running on the local machine at http://localhost:7099/bin.

The sample checks to see if there is a Briefcase file available and if not then the data is freshly retrieved from the server. If on the other hand a Briefcase is available, then the BRIEFCASE_DATA_VERSION_KEY stored in the briefcase as a property is tested to see if the data is valid. If it isn't valid then fresh data is retrieved from the server and the briefcase replaced with the new data. If the property is valid then the tables are retrieved from the briefcase. You will also see that the date property is also retrieved and logged to the console when the briefcase is being validated.

At this point the UI will be visible with a list of products, the product group they belong to and a minimum and maximum amount available. Unlike the figure above, when the sample is first run no rows will have a yellow highlight, which means that there are no changes compared to the server version of the data. (See below)

  

  

  

If you double click on a in the "Min Amount" column of a row, the field will become editable allowing you to change the value. If you make a change and press Enter, the row will have a yellow highlight to indicate that a change has been made compared to the DataTable retrieved from the server.

If you quit the app at this point, those changes will be saved to the briefcase along with the information that the data is changed compared to the server, so that when you next run the app you will see those very same rows marked as changed. The highlighting will only be removed by either reloading the data from the server or by applying the changes to the server.

If you press the "Reveal in Explorer" button you will see the briefcase folder (Products.briefcase) that was written to disk when quitting the app, or after the data is retrieved from the server. If you open this folder, you will find three files, two with the extension .daBriefcase which are the tables themselves, and a plist file which has the data stored in any briefcase properties. If you double click on one of the .daBriefcase files it will open in the Briefcase Explorer tool and display the contents of the briefcase for examination. The plist file is viewable in any text editor.

Examining the Code

The code for creating the connection to the server, retrieving and modifying the data are covered by other samples. In this section we will be focusing on the code needed to create the briefcase, store the tables & properties in it and load those tables & properties from disk.

App Structure

The sample is built around two classes; DataModule and MainForm.

The DataModule class handles everything related to interacting with the Data Abstract SDK; including retrieving data from an instance of Relativity Server, applying changes to the server and working with the briefcase. All of the code you are interested in is located in this class and will be discussed below.

The MainForm handles everything related to the user interface.

Creating the path to store the Briefcase

 

public String BriefcaseFileName
{
  get
    {
      return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), BRIEFCASE_NAME);
    }
}

 

property BriefcaseFileName: String read Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), BRIEFCASE_NAME);

 

Public ReadOnly Property BriefcaseFileName() As String
  Get
    Return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), BRIEFCASE_NAME)
  End Get
End Property

The briefcase filename is stored as a read only property; BriefcaseFileName. The properties get method combines the path from where the executable is being run, with the string defined for BRIEFCASE_NAME (Products.briefcase). The property is used in both the SaveBriefcase and LoadData methods.

Store the tables in a new briefcase

 

private void PutTablesToBriefcase(Briefcase briefcase)
{
  this.AddToLog("Putting tables to the briefcase...");
  briefcase.AddTable(this.dataSet.Tables["Products"]);
  briefcase.AddTable(this.dataSet.Tables["ProductGroups"]);
}

public void SaveBriefcase()
{
  Briefcase briefcase = new FolderBriefcase(this.BriefcaseFileName);

  if (this.InvalidateBriefcase)
  {
    String v = this.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY);
    if (String.Equals(BRIEFCASE_DATA_VERSION_INVALID, v, StringComparison.OrdinalIgnoreCase))
    {
      this.AddToLog("Briefcase already marked as INVALID.");
    }
    else
    {
      briefcase.Properties[BRIEFCASE_DATA_VERSION_KEY] = BRIEFCASE_DATA_VERSION_INVALID;
      this.AddToLog("Briefcase has been marked as INVALID.");
      briefcase.WriteBriefcase();
    }
  }
  else
  {
    this.PutTablesToBriefcase(briefcase);
    briefcase.Properties[BRIEFCASE_DATA_VERSION_KEY] = BRIEFCASE_DATA_VERSION;
    briefcase.Properties[BRIEFCASE_UPDATE_DATE_KEY] = DateTime.Now.ToString();
    briefcase.WriteBriefcase();
    this.AddToLog("Briefcase has been updated.");
  }
}

 

method DataModule.PutTablesToBriefcase(briefcase: Briefcase);
begin
  self.AddToLog('Putting tables to the briefcase...');
  briefcase.AddTable(self.dataSet.Tables['Products']);
  briefcase.AddTable(self.dataSet.Tables['ProductGroups'])
end;

method DataModule.SaveBriefcase;
begin
  var briefcase: Briefcase := new FolderBriefcase(self.BriefcaseFileName);

  if self.InvalidateBriefcase then
  begin
    var v: String := self.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY);
    if String.Equals(BRIEFCASE_DATA_VERSION_INVALID, v, StringComparison.OrdinalIgnoreCase) then
    begin
      self.AddToLog('Briefcase already marked as INVALID.')
    end
    else
    begin
      briefcase.Properties[BRIEFCASE_DATA_VERSION_KEY] := BRIEFCASE_DATA_VERSION_INVALID;
      self.AddToLog('Briefcase has been marked as INVALID.');
      briefcase.WriteBriefcase()
    end
  end
  else
  begin
    self.PutTablesToBriefcase(briefcase);
    briefcase.Properties[BRIEFCASE_DATA_VERSION_KEY] := BRIEFCASE_DATA_VERSION;
    briefcase.Properties[BRIEFCASE_UPDATE_DATE_KEY] := DateTime.Now.ToString();
    briefcase.WriteBriefcase();
    self.AddToLog('Briefcase has been updated.')
  end
end;

 

Private Sub PutTablesToBriefcase(briefcase As Briefcase)
  Me.AddToLog("Putting tables to the briefcase...")
  briefcase.AddTable(Me.dataSet.Tables("Products"))
  briefcase.AddTable(Me.dataSet.Tables("ProductGroups"))
End Sub

Public Sub SaveBriefcase()
  Dim briefcase As Briefcase = New FolderBriefcase(Me.BriefcaseFileName)

  If Me.InvalidateBriefcase Then
    Dim v As String = Me.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY)
    If String.Equals(BRIEFCASE_DATA_VERSION_INVALID, v, StringComparison.OrdinalIgnoreCase) Then
      Me.AddToLog("Briefcase already marked as INVALID.")
    Else
      briefcase.Properties(BRIEFCASE_DATA_VERSION_KEY) = BRIEFCASE_DATA_VERSION_INVALID
      Me.AddToLog("Briefcase has been marked as INVALID.")
      briefcase.WriteBriefcase()
    End If
  Else
    Me.PutTablesToBriefcase(briefcase)
    briefcase.Properties(BRIEFCASE_DATA_VERSION_KEY) = BRIEFCASE_DATA_VERSION
    briefcase.Properties(BRIEFCASE_UPDATE_DATE_KEY) = DateTime.Now.ToString()
    briefcase.WriteBriefcase()
    Me.AddToLog("Briefcase has been updated.")
  End If
End Sub

In this sample saving the briefcase to disk is handled over two methods. SaveBriefcase handles the creation of a FolderBriefcase object, recording some information as briefcase properties and writing the briefcase to disk, while the PutTablesToBriefcase adds the DataTables we are interested in into the briefcase.

As noted in the Briefcase Overview, briefcases can be stored in one of two formats; either as a single .daBriefcase file which contains all of the tables and properties that you set or as a package (folder) where each table is stored in its own .daBriefcase file and the properties are stored in a .plist file. This sample uses the folder format, but if you wanted to use the other briefcase format you can replace the two instances of FolderBriefcase with FileBriefcase instead and alter the check to see if the briefcase folder exists in LoadData.

If the InvalidateBriefcase property has been set to true then the value currently stored in briefcase for the BRIEFCASE_DATA_VERSION property is retrieved and tested to see if it has already been set to INVALID. If it hasn't, then the property is set to BRIEFCASE_DATA_VERSION_INVALID and the briefcase is immediately written to disk using the WriteBriefcase method. No changes are saved to disk unless WriteBriefcase is called.

Otherwise if InvalidateBriefcase is set to false, we call putTablesToBriefcase which adds two tables "Products" and "ProductGroups" to the briefcase using the AddTable method. If a table with the same name is already there, it will be replaced with this new table. The briefcase is then written to disk using the WriteBriefcase method. As noted, no changes to the briefcase are saved to disk unless WriteBriefcase is called.

Loading the briefcase from disk and retrieving the tables

 

public void LoadData()
{
  this.AddToLog("Initial loading...");
  this.AddToLog("Looking for the local briefcase first...");

  String briefcaseName = this.BriefcaseFileName;
  this.AddToLog(String.Format("Briefcase: {0}", briefcaseName));
  if (!System.IO.Directory.Exists(briefcaseName))
  {
    this.AddToLog("No briefcase found. Downloading fresh data...");
    this.LoadFromServer();
  }
  else
  {
    this.AddToLog("Briefcase has been found.");
    Briefcase briefcase = new FolderBriefcase(briefcaseName);
    // ToDo: Try without that line below, after #60063 will be fixed
    briefcase.ReadBriefcase();
    Boolean valid = this.ValidateBriefcase(briefcase);
    if (valid)
    {
      this.GetTablesFromBriefcase(briefcase);
    }
    else
    {
      this.AddToLog("Clearing briefcase and downloading fresh data...");
      this.LoadFromServer();
    }
  }
  this.AddToLog("Data is Ready.");
}

private void GetTablesFromBriefcase(Briefcase briefcase)
{
  this.AddToLog("Loading tables from the briefcase...");

  this.dataSet.Clear();
  this.dataSet.Tables.Add(briefcase.FindTable("Products"));
  this.dataSet.Tables.Add(briefcase.FindTable("ProductGroups"));
}

private Boolean ValidateBriefcase(Briefcase briefcase)
{
  this.AddToLog("Validating briefcase...");

  String briefcaseVersion = this.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY);
  String appVersion = BRIEFCASE_DATA_VERSION;
  Boolean isValid = String.Equals(briefcaseVersion, appVersion, StringComparison.OrdinalIgnoreCase);
  if (isValid)
  {
    this.AddToLog(String.Format("Briefcase is valid. Version: {0}; Last update: {1}", briefcaseVersion, this.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_UPDATE_DATE_KEY)));
  }
  else
  {
    this.AddToLog(String.Format("Briefcase looks outdated! Application requires version {0}, while briefcase has version {1}", appVersion, briefcaseVersion));
  }

 

method DataModule.LoadData;
begin
  self.AddToLog('Initial loading...');
  self.AddToLog('Looking for the local briefcase first...');
  var briefcaseName: String := self.BriefcaseFileName;
  self.AddToLog(String.Format('Briefcase: {0}', briefcaseName));
  if not System.IO.Directory.Exists(briefcaseName) then
  begin
    self.AddToLog('No briefcase found. Downloading fresh data...');
    self.LoadFromServer()
  end
  else
  begin
    self.AddToLog('Briefcase has been found.');
    var briefcase: Briefcase := new FolderBriefcase(briefcaseName);
    // ToDo: Try without that line below, after #60063 will be fixed
    briefcase.ReadBriefcase();
    var valid: Boolean := self.ValidateBriefcase(briefcase);
    if valid then
    begin
      self.GetTablesFromBriefcase(briefcase)
    end
    else
    begin
      self.AddToLog('Clearing briefcase and downloading fresh data...');
      self.LoadFromServer()
    end
  end;
  self.AddToLog('Data is Ready.')
end;

method DataModule.GetTablesFromBriefcase(briefcase: Briefcase);
begin
  self.AddToLog('Loading tables from the briefcase...');

  self.dataSet.Clear();
  self.dataSet.Tables.Add(briefcase.FindTable('Products'));
  self.dataSet.Tables.Add(briefcase.FindTable('ProductGroups'))
end;

method DataModule.ValidateBriefcase(briefcase: Briefcase): Boolean;
begin
  self.AddToLog('Validating briefcase...');

  var briefcaseVersion: String := self.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY);
  var appVersion: String := BRIEFCASE_DATA_VERSION;
  var isValid: Boolean := String.Equals(briefcaseVersion, appVersion, StringComparison.OrdinalIgnoreCase);
  if isValid then
  begin
    self.AddToLog(String.Format('Briefcase is valid. Version: {0}; Last update: {1}', briefcaseVersion, self.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_UPDATE_DATE_KEY)))
  end
  else
  begin
    self.AddToLog(String.Format('Briefcase looks outdated! Application requires version {0}, while briefcase has version {1}', appVersion, briefcaseVersion))
  end;

  exit isValid
end;

 

Public Sub LoadData()
    Me.AddToLog("Initial loading...")
    Me.AddToLog("Looking for the local briefcase first...")

    Dim briefcaseName As String = Me.BriefcaseFileName
    Me.AddToLog(String.Format("Briefcase: {0}", briefcaseName))
    If Not System.IO.Directory.Exists(briefcaseName) Then
        Me.AddToLog("No briefcase found. Downloading fresh data...")
        Me.LoadFromServer()
    Else
        Me.AddToLog("Briefcase has been found.")
        Dim briefcase As Briefcase = New FolderBriefcase(briefcaseName)
        briefcase.ReadBriefcase()
        Dim valid As Boolean = Me.ValidateBriefcase(briefcase)
        If valid Then
            Me.GetTablesFromBriefcase(briefcase)
        Else
            Me.AddToLog("Clearing briefcase and downloading fresh data...")
            Me.LoadFromServer()
        End If
    End If
    Me.AddToLog("Data is Ready.")
End Sub

Private Sub GetTablesFromBriefcase(briefcase As Briefcase)
    Me.AddToLog("Loading tables from the briefcase...")

    Me.dataSet.Clear()
    Me.dataSet.Tables.Add(briefcase.FindTable("Products"))
    Me.dataSet.Tables.Add(briefcase.FindTable("ProductGroups"))
End Sub

Private Function ValidateBriefcase(briefcase As Briefcase) As Boolean
    Me.AddToLog("Validating briefcase...")

    Dim briefcaseVersion As String = Me.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_DATA_VERSION_KEY)
    Dim appVersion As String = BRIEFCASE_DATA_VERSION
    Dim isValid As Boolean = String.Equals(briefcaseVersion, appVersion, StringComparison.OrdinalIgnoreCase)
    If isValid Then
        Me.AddToLog(String.Format("Briefcase is valid. Version: {0}; Last update: {1}", briefcaseVersion, Me.SafeGetBriefcaseProperty(briefcase, BRIEFCASE_UPDATE_DATE_KEY)))
    Else
        Me.AddToLog(String.Format("Briefcase looks outdated! Application requires version {0}, while briefcase has version {1}", appVersion, briefcaseVersion))
    End If

    Return isValid
End Function

These three methods handle loading the briefcase from disk and retrieving the tables from the briefcase based on the BRIEFCASE_DATA_VERSION property stored inside the briefcase.

The LoadData first tests to see if the briefcase folder exists, if not a fresh set of data is retrieved from the server. Otherwise a briefcase object is created using FolderBriefcase and the ReadBriefcase called to load the data into memory. The fully loaded briefcase is passed to the ValidateBriefcase method which checks the value of the BRIEFCASE_DATA_VERSION_KEY property. The SafeGetBriefcaseProperty attempts to retrieve the property from the Properties dictionary and returns an empty string if the property is not found.

If the briefcase is considered to be valid then the tables are retrieved with a call to getTablesFromBriefcase:. Each table is retrieved from the briefcase using the FindTable method which takes a string that is the name of the table and returns a copy of the table stored in the briefcase as a DataTable. Changes made to that copy will not affect the one stored in the briefcase.