PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/Source Code/SongDatabase/Export and Import/PrimaryFacility.vb

#
Visual Basic | 364 lines | 314 code | 26 blank | 24 comment | 0 complexity | 5a053277055b001bab7b2fbeca4d92ac MD5 | raw file
  1. Imports System.IO
  2. Imports System.IO.Path
  3. Imports System.IO.Compression
  4. Imports PluginSupport
  5. Imports PowerSong.SongDatabase.Items
  6. Namespace ExportImport
  7. ''' <summary>
  8. ''' Represents the import and export functionality of PowerSong databases.
  9. ''' </summary>
  10. Public Class PrimaryFacility
  11. Private FDatabase As Database = Nothing
  12. ''' <summary>
  13. ''' Initializes a new instance of the <see cref="PrimaryFacility" /> class.
  14. ''' </summary>
  15. ''' <param name="sourceDatabase">The source database to use for the export/import.</param>
  16. Public Sub New(ByVal sourceDatabase As Database)
  17. FDatabase = sourceDatabase
  18. End Sub
  19. ''' <summary>
  20. ''' Exports the entire song database into a single file.
  21. ''' </summary>
  22. ''' <param name="fileName">The name of the target archive file.</param>
  23. Public Sub Export(ByVal fileName As String, _
  24. ByVal exportSongs As Boolean, _
  25. ByVal includeCategories As Boolean, _
  26. ByVal exportPlugins As Boolean, _
  27. ByVal exportStyles As Boolean)
  28. Dim DS As Data = FDatabase.Data
  29. ' Create the output stream
  30. Dim File As New BinaryWriter(New MemoryStream)
  31. ' Write all files in this folder to the file
  32. If exportSongs Then
  33. For Each SourceFilename As String In Directory.GetFiles(FDatabase.Location, "*.song")
  34. WriteFileToStream(File, SourceFilename, GetFileName(SourceFilename))
  35. Next
  36. End If
  37. ' TODO: Allow importing of categories as well
  38. If includeCategories Then
  39. ' Create a dataset for exporting
  40. Dim CategoriesDataset As New Data
  41. For Each Row As Data.CategoriesRow In DS.Categories
  42. CategoriesDataset.Categories.ImportRow(Row)
  43. Next
  44. For Each Row As Data.SongsRow In DS.Songs
  45. CategoriesDataset.Songs.ImportRow(Row)
  46. Next
  47. For Each Row As Data.SongCategoriesRow In DS.SongCategories
  48. CategoriesDataset.SongCategories.ImportRow(Row)
  49. Next
  50. ' Write the dataset to the archive
  51. Dim Writer As New StringWriter
  52. CategoriesDataset.WriteXml(Writer)
  53. Writer.Close()
  54. WriteTextToStream(File, Writer.ToString, "SongCategories.xml")
  55. End If
  56. If exportPlugins Then
  57. For Each SourceFilename As String In Directory.GetFiles(FDatabase.Location, "*.plugin")
  58. WriteFileToStream(File, SourceFilename, GetFileName(SourceFilename))
  59. Next
  60. End If
  61. If exportStyles Then
  62. ' Create a dataset for exporting
  63. Dim StylesDataset As New Data
  64. For Each Row As Data.StylesRow In DS.Styles
  65. StylesDataset.Styles.ImportRow(Row)
  66. Next
  67. For Each Row As Data.ProjectletsRow In DS.Projectlets
  68. StylesDataset.Projectlets.ImportRow(Row)
  69. Next
  70. ' Write the dataset to the archive
  71. Dim Writer As New StringWriter
  72. StylesDataset.WriteXml(Writer)
  73. Writer.Close()
  74. WriteTextToStream(File, Writer.ToString, "Styles.xml")
  75. End If
  76. ' Get the data as a sequence of bytes
  77. Dim Data(File.BaseStream.Length) As Byte
  78. File.BaseStream.Position = 0
  79. File.BaseStream.Read(Data, 0, File.BaseStream.Length)
  80. ' Write to a compressed stream
  81. Dim CompressedFile As New GZipStream(New FileStream(fileName, FileMode.Create), CompressionMode.Compress)
  82. CompressedFile.Write(Data, 0, File.BaseStream.Length)
  83. File.Close()
  84. CompressedFile.Close()
  85. End Sub
  86. Public Function Import(ByVal fileStream As Stream, _
  87. ByVal pluginContext As PluginContextBase, _
  88. ByVal overwriteItems As Boolean) As List(Of String)
  89. ' Start a transaction
  90. Dim DS As Data = FDatabase.Data
  91. Dim Messages As New List(Of String)
  92. FDatabase.StartTransaction()
  93. Try
  94. ' Decompress all of the data in the given file into a memory stream
  95. Dim ZipStream As New GZipStream(fileStream, CompressionMode.Decompress)
  96. Dim MemoryStream As New MemoryStream
  97. Dim Buffer(4096) As Byte
  98. Dim BytesRead As Integer = ZipStream.Read(Buffer, 0, 4096)
  99. While BytesRead > 0
  100. MemoryStream.Write(Buffer, 0, BytesRead)
  101. BytesRead = ZipStream.Read(Buffer, 0, 4096)
  102. End While
  103. ' Start reading from the beginning of the memory stream
  104. MemoryStream.Position = 0
  105. Dim File As New BinaryReader(MemoryStream)
  106. ' Read each file in the stream
  107. While File.BaseStream.Position < File.BaseStream.Length
  108. Dim NextFileName As String = File.ReadString
  109. Dim FileLength As Integer = File.ReadInt32
  110. Dim FileContents As Byte() = File.ReadBytes(FileLength)
  111. Dim OutputFileName As String = FDatabase.Location + "\" + NextFileName
  112. ' Work with the current file in the stream
  113. Select Case GetExtension(NextFileName).ToUpper
  114. Case ".SONG"
  115. ImportSong(DS, FileContents, OutputFileName, overwriteItems, Messages)
  116. Case ".PLUGIN"
  117. ImportPlugin(FileContents, pluginContext, OutputFileName, overwriteItems, Messages)
  118. Case ".XML"
  119. Select Case NextFileName.ToUpper
  120. Case "STYLES.XML"
  121. ImportStyles(DS, FileContents, overwriteItems, Messages)
  122. Case "SONGCATEGORIES.XML"
  123. Messages.Add("Song Categories have been included in the selected file but they cannot yet be imported. It is likely that a future version of PowerSong will allow Song Categories to be imported from the specified file.")
  124. Case Else
  125. Messages.Add("Skipped over unknown file: '" + NextFileName + "'.")
  126. End Select
  127. Case Else
  128. Messages.Add("Skipped over unknown file: '" + NextFileName + "'.")
  129. End Select
  130. End While
  131. Finally
  132. FDatabase.EndTransaction()
  133. End Try
  134. ' Perform some checks on the database - this assigns a category to uncategorised songs
  135. FDatabase.Save()
  136. DatabaseUpgrade.CheckSongCategories(FDatabase)
  137. ' Recreate the index
  138. FDatabase.RecreateIndex()
  139. Return Messages
  140. End Function
  141. Private Sub ImportSong(ByVal dataSet As Data, _
  142. ByVal fileContents As Byte(), _
  143. ByVal destinationFileName As String, _
  144. ByVal overwriteExisting As Boolean, _
  145. ByVal messages As List(Of String))
  146. ' Determine whether or not to import/overwrite the song
  147. Dim ImportThisSong As Boolean = True
  148. If IO.File.Exists(destinationFileName) Then
  149. If Not overwriteExisting Then
  150. messages.Add("The song '" + destinationFileName + "' already exists.")
  151. ImportThisSong = False
  152. End If
  153. End If
  154. If ImportThisSong = True Then
  155. Try
  156. ' Write the song
  157. Dim OutputFile As New BinaryWriter(New FileStream(destinationFileName, FileMode.Create))
  158. OutputFile.Write(fileContents)
  159. OutputFile.Close()
  160. ' Add to the database if necessary
  161. Dim Song As SongItem = SongItem.Load(destinationFileName)
  162. Dim ExistingSong As Data.SongsRow = dataSet.Songs.FindBySongID(Song.SongID)
  163. If ExistingSong IsNot Nothing Then dataSet.Songs.RemoveSongsRow(ExistingSong)
  164. Dim SongRow As Data.SongsRow = dataSet.Songs.AddSongsRow(Song.SongID, Song.Title, Nothing)
  165. ' Display information about the song
  166. messages.Add("Added song: '" + Song.Title + "'.")
  167. Catch ex As Exception
  168. messages.Add("Song import error: " + ex.Message)
  169. End Try
  170. End If
  171. End Sub
  172. Private Sub ImportPlugin(ByVal fileContents As Byte(), _
  173. ByVal pluginContext As PluginSupport.PluginContextBase, _
  174. ByVal destinationFileName As String, _
  175. ByVal overwriteExisting As Boolean, _
  176. ByVal messages As List(Of String))
  177. If IO.File.Exists(destinationFileName) Then
  178. messages.Add("The plugin '" + destinationFileName + "' already exists.")
  179. Else
  180. Try
  181. ' Add the plugin to the active database, instantiate it and call its Install method
  182. AddExistingPlugin(destinationFileName, fileContents)
  183. messages.Add("Added plugin '" + GetFileName(destinationFileName) + "'.")
  184. Dim Plugin As IPlugin = FDatabase.InstantiatePlugin(GetFileName(destinationFileName), pluginContext)
  185. Plugin.Install()
  186. messages.Add("Installed plugin '" + GetFileName(destinationFileName) + "'.")
  187. Catch ex As Exception
  188. messages.Add("Plugin import error: " + ex.Message)
  189. End Try
  190. End If
  191. End Sub
  192. Private Sub ImportStyles(ByVal dataSet As Data, _
  193. ByVal fileContents As Byte(), _
  194. ByVal overwriteExisting As Boolean, _
  195. ByVal messages As List(Of String))
  196. Try
  197. ' Get the styles
  198. Dim StyleData As New Data
  199. Dim Reader As New Xml.XmlTextReader(New MemoryStream(fileContents))
  200. StyleData.ReadXml(Reader)
  201. Reader.Close()
  202. ' Import the styles
  203. For Each Row As Data.StylesRow In StyleData.Styles
  204. Try
  205. Dim ExistingStyle As Data.StylesRow() = dataSet.Styles.Select("Name='" + Row.Name + "'")
  206. If ExistingStyle.Length = 0 Then
  207. ' Add the new style
  208. dataSet.Styles.ImportRow(Row)
  209. For Each SubRow As Data.ProjectletsRow In StyleData.Projectlets.Select("StyleID='" + Row.StyleID.ToString + "'")
  210. dataSet.Projectlets.ImportRow(SubRow)
  211. Next
  212. messages.Add("Added style '" + Row.Name + "'.")
  213. ElseIf overwriteExisting Then
  214. ' Overwrite existing style
  215. dataSet.Styles.RemoveStylesRow(ExistingStyle(0))
  216. dataSet.Styles.ImportRow(Row)
  217. For Each SubRow As Data.ProjectletsRow In StyleData.Projectlets.Select("StyleID='" + Row.StyleID.ToString + "'")
  218. dataSet.Projectlets.ImportRow(SubRow)
  219. Next
  220. messages.Add("Replaced style '" + Row.Name + "'.")
  221. Else
  222. messages.Add("The style '" + Row.Name + "' already exists, therefore it has been ignored.")
  223. End If
  224. Catch ex As Exception
  225. messages.Add("Cannot import style '" + Row.Name + "' because it is incompatible.")
  226. End Try
  227. Next
  228. Catch ex As Exception
  229. messages.Add("The styles file contained in the archive is invalid.")
  230. End Try
  231. End Sub
  232. Public Function Import(ByVal fileName As String, _
  233. ByVal pluginContext As PluginContextBase, _
  234. ByVal overwriteExistingSongs As Boolean) As List(Of String)
  235. Dim Stream As New FileStream(fileName, FileMode.Open)
  236. Dim Result As New List(Of String)
  237. Try
  238. Result = Import(Stream, pluginContext, overwriteExistingSongs)
  239. Finally
  240. Stream.Close()
  241. End Try
  242. Return Result
  243. End Function
  244. Private Sub WriteFileToStream(ByVal stream As BinaryWriter, _
  245. ByVal fileName As String, _
  246. ByVal fileNameInStream As String)
  247. stream.Write(fileNameInStream)
  248. Dim File As New BinaryReader(New FileStream(fileName, FileMode.Open))
  249. Dim FileLength As Integer = File.BaseStream.Length
  250. Dim Contents As Byte() = File.ReadBytes(FileLength)
  251. stream.Write(FileLength)
  252. stream.Write(Contents)
  253. File.Close()
  254. End Sub
  255. Private Sub WriteTextToStream(ByVal stream As BinaryWriter, _
  256. ByVal text As String, _
  257. ByVal fileNameInStream As String)
  258. stream.Write(fileNameInStream)
  259. Dim Data As Byte() = System.Text.Encoding.Default.GetBytes(text)
  260. stream.Write(Data.Length)
  261. stream.Write(Data)
  262. End Sub
  263. Private Sub AddExistingPlugin(ByVal destinationFileName As String, ByVal fileContents As Byte())
  264. ' Check extension
  265. If GetExtension(destinationFileName).ToUpper <> ".PLUGIN" Then
  266. Throw New ApplicationException("Plugin must have an extension of .PLUGIN.")
  267. End If
  268. If File.Exists(destinationFileName) Then Throw New ApplicationException("A plugin with the same file name already exists in the database.")
  269. Dim OutputFile As New FileStream(destinationFileName, FileMode.Create)
  270. OutputFile.Write(fileContents, 0, fileContents.Length)
  271. OutputFile.Close()
  272. End Sub
  273. End Class
  274. End Namespace