UWP Shared File Storage

Wenn Apps in MicroApps aufgeteilt werden stellt sich die Frage nach gemeinsamen Ressourcen. Ein ganz einfacher Anwendungsfall sind Dateien im LocalStorage. Da Windows 10 UWP Apps in einer Sandbox betreibt sind Zugriffe auf das Dateisystem weitestgehend reglementiert. Wer also ApplicationData.Current.LocalFolder nutzt, kann nur auf die eigene Dateien zugreifen.

Das Architektur Konzept von Micrapps benötigt aber Zugriff auf Dateien. Dies ist in Windows 10 seit einer Weile mit Cached Folders möglich, solange der Publisher der Apps ident ist.

Das Manifest der App enthält die Deklaration des Folders.

   1:  <Extensions>
   2:      <Extension Category="windows.publisherCacheFolders">
   3:        <PublisherCacheFolders >
   4:          <Folder Name="ppedvFolder"/>
   5:        </PublisherCacheFolders>
   6:     </Extension>
   7:  </Extensions>

Der Speicherort auf der Festplatte folgt dem Schema

C:\Users\USERXXX\AppData\Local\Publishers\89gf582k2a27c\ppedvFolder

Somit kann man per Dateiexplorer jederzeit darauf zugreifen. Der Code in der App kann das natürlich auch. Die Demo App enthält einen Button, der eine neue Datei anlegt. Diese werden durchnummeriert. Dabei kommt das neue Sprachfeature String Interpolation mit dem $ Zeichen genutzt.

Der Speicherort wird als Cachefolder angelegt und dann mit den StorageFolder und StorageItems aus der WinRT API gearbeitet. Das sind im Hintergrund dann auch COM Objekte mit manchmal eigenartigen Exceptions.

   1:      Private Async Sub Button_ClickAsync(sender As Object, e As RoutedEventArgs)
   2:          Dim SharedFolder = Windows.Storage.ApplicationData.
Current.GetPublisherCacheFolder("ppedvFolder")
   3:          Dim files As IReadOnlyList(Of StorageFile) = 
Await SharedFolder.GetFilesAsync()
   4:          Dim newDatei As String
   5:          If files.Count > 0 Then
   6:              Dim last = files.OrderBy(Function(x) x.Name).Last.Name
   7:              Dim id = CInt(last.Substring(5, 11)) + 1
   8:              newDatei = $"datei{id:00000000000}.txt"
   9:          Else
  10:              newDatei = "datei0000000000.txt"
  11:          End If
  12:          Dim datei = Await SharedFolder.CreateFileAsync(
newDatei, CreationCollisionOption.OpenIfExists)
  13:          Await FileIO.WriteTextAsync(datei, Date.Now)
  14:      End Sub

Nun wird der Prototyp für APP #2 gebaut, der auf neu angelegte Dateien reagieren kann. Dies ist im Beispiel in der gleichen App umgesetzt. Basierend auf einem FileQuery Objekt, kann auf Änderungen ein Event registriert werden. Die FileQuery kann als Parameter Dateierweiterung und Pfad übernehmen. Erst wenn GetFilesAsync aufgerufen wird, wird auch das Event ausgelöst.

   1:  Private filequery As StorageFileQueryResult
   2:  Dim files As IReadOnlyList(Of StorageFile)

3: Private Async Function MainPage_LoadedAsync(

sender As Object, e As RoutedEventArgs) As Task Handles Me.Loaded

   4:          filequery = Windows.Storage.ApplicationData.Current.
GetPublisherCacheFolder("ppedvFolder").CreateFileQuery()
   5:          AddHandler filequery.ContentsChanged, AddressOf neueDatei
   6:          files = Await filequery.GetFilesAsync()
   7:  End Function

Man sollte nun denken, das in der Variable Files eine Liste der Dateien enthalten ist. Das stimmt, ist aber nicht 100% zuverlässig. Es ist eben ein API Call auf höherer Ebene.

Noch seltsamer wird es, wenn man sich das ausgelöste Ereignis ansieht. Der Code wird wahrscheinlich nicht im UI Thread landen. Deswegen benötigt man eine Thread Synchronisation per Dispatcher. In diesem Beispiel stammt die Funktion aus dem Nuget Paket Microsoft.Toolkit.Uwp. Es wird einer Lambda Funktion ein List Control mit einem neuen Eintrag gefüllt. Dann stellt man zur Laufzeit fest, das dieses Event mehrfach ausgelöst wird. Es gibt also mehrere Einträge pro neuer Datei in der Liste. Man würde glauben das die Parameter Informationen übergeben, die auf die Änderungen hinweisen. Allerdings ist args leer und sender enthält nichts im Ansatz nützliches. Deswegen wurde hier einfach nach Dateinamen sortiert und die letzte als neu definiert.

   1:  Private Async Sub neueDatei(sender As IStorageQueryResultBase, args As Object)
   2:          files = Await filequery.GetFilesAsync() 'kann sekunden dauern
   3:          Dim last = files.OrderBy(Function(x) x.Name).Last.Name
   4:          DispatcherHelper.ExecuteOnUIThreadAsync(
Sub() liste1.Items.Add(New TextBlock() With {.Text = last}))
   5:  End Sub

Diese paar Zeilen Code werfen eine Reihe von Problemen auf. Geht man davon aus, das das speichern der Datei länger dauert, weil diese aus dem Internet geladen wird, so weis man nicht wann diese komplett ist. Die Dokumentation gibt nichts dazu her. Eine These basierend auf den Tests ist, das neueDatei wie folgt aufgerufen wird

  • Datei wird angelegt
  • Datei wird geschrieben
  • Datei wird geschlossen

Wenn das stimmt, könnte man einen Counter mitlaufen lassen. Explizit gewarnt sei vor GetBasicPropertiesAsync() das zwar die Eigenschaften liefert, aber auch wenn die Datei schon wieder gelöscht ist.

Kommentare sind geschlossen