/// <summary> /// Update the level's metadata on disk. /// Does not change the level's timestamp. /// </summary> /// <param name="level"></param> public static void UpdateWorldMetadata(LevelMetadata level) { try { string bucket = Utils.FolderNameFromFlags(level.Genres); string fullPath = BokuGame.Settings.MediaPath + bucket + level.WorldId.ToString() + @".Xml"; Xml.XmlWorldData xml = XmlWorldData.Load(fullPath, XnaStorageHelper.Instance); if (xml != null) { level.ToXml(xml); bool isDownload = (level.Genres & Genres.Downloads) != 0; // Manage the stream ourselves so avoid level timestamp being changed. Stream stream = Storage4.OpenWrite(fullPath); xml.Save(stream, isDownload); Storage4.Close(stream); } } catch (Exception e) { Debug.WriteLine(e.Message); } }
private bool IsAlreadyDownloaded(LevelMetadata level) { string filename = BokuGame.Settings.MediaPath + BokuGame.DownloadsPath + level.WorldId.ToString() + @".Xml"; if (Storage4.FileExists(filename, StorageSource.UserSpace)) { XmlWorldData xml = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xml != null) { LevelMetadata local = LevelMetadata.CreateFromXml(xml); return( local.WorldId == level.WorldId && local.Creator == level.Creator && local.LastWriteTime >= level.LastWriteTime); } } return(false); }
public static LevelMetadata LoadMetadataByGenre(Guid worldId, Genres genres) { string bucket = BokuGame.MyWorldsPath; if (genres != 0) { bucket = Utils.FolderNameFromFlags(genres); } string fullPath = BokuGame.Settings.MediaPath + bucket + worldId.ToString() + @".Xml"; Xml.XmlWorldData xml = XmlWorldData.Load(fullPath, XnaStorageHelper.Instance); if (xml != null) { LevelMetadata data = new LevelMetadata(); data.FromXml(xml); //minor hackery - seems previous versions of kodu will sometimes leave the genres set to 0 //even though they should be updated for the bucket the level is in. Then at load time, a run-time //genre is set. This code will maintain that behavior for levels loaded through this helper if (bucket == BokuGame.DownloadsPath) { //ensure downloads always have the downloads flag data.Genres |= Genres.Downloads; } else if (bucket == BokuGame.BuiltInWorldsPath) { //ensure built in worlds always have the built in flag data.Genres |= Genres.BuiltInWorlds; } else if (bucket == BokuGame.MyWorldsPath) { data.Genres |= Genres.MyWorlds; } return(data); } return(null); }
public static BokuShared.Wire.WorldPacket ReadWorldPacketFromDisk(string worldFullPathAndName, string bucket) { BokuShared.Wire.WorldPacket packet = null; Stream file = null; try { string localLevelPath = BokuGame.Settings.MediaPath + bucket; string worldFilename = Path.GetFileName(worldFullPathAndName); // Read contents of world xml to retrieve the names of the dependent // files we need to upload Xml.XmlWorldData xmlWorldData = XmlWorldData.Load(localLevelPath + worldFilename, XnaStorageHelper.Instance); if (xmlWorldData == null) { return(null); } // Clear virtual genre bits in case they got saved (server clears them too). xmlWorldData.genres &= ~(int)Genres.Virtual; packet = new BokuShared.Wire.WorldPacket(); packet.Info.WorldId = packet.Data.WorldId = xmlWorldData.id; packet.Info.Name = xmlWorldData.name; packet.Info.Description = xmlWorldData.description; packet.Info.Creator = xmlWorldData.creator; packet.Info.IdHash = ""; packet.Info.Genres = xmlWorldData.genres; string imageFileName = xmlWorldData.GetImageFilenameWithoutExtension(); // VirtualMap file = Storage4.OpenRead(BokuGame.Settings.MediaPath + xmlWorldData.xmlTerrainData2.virtualMapFile, StorageSource.All); packet.Data.VirtualMapBytes = new byte[file.Length]; file.Read(packet.Data.VirtualMapBytes, 0, (int)file.Length); Storage4.Close(file); // Stuff xml file = Storage4.OpenRead(BokuGame.Settings.MediaPath + xmlWorldData.stuffFilename, StorageSource.All); packet.Data.StuffXmlBytes = new byte[file.Length]; file.Read(packet.Data.StuffXmlBytes, 0, (int)file.Length); Storage4.Close(file); // Optional: don't worry if we don't have a thumbnail image. try { file = null; file = Storage4.TextureFileOpenRead(localLevelPath + imageFileName); if (file != null) { packet.Info.ThumbnailBytes = new byte[file.Length]; file.Read(packet.Info.ThumbnailBytes, 0, (int)file.Length); Storage4.Close(file); } } catch { } // Try To load Snapshot image. try { file = null; file = Storage4.TextureFileOpenRead(localLevelPath + imageFileName, Storage4.TextureFileType.jpg); if (file != null) { packet.Info.ScreenshotBytes = new byte[file.Length]; file.Read(packet.Info.ScreenshotBytes, 0, (int)file.Length); Storage4.Close(file); } } catch { } // We've successfully read all required files. We may now upload them to the server. file = Storage4.OpenRead(localLevelPath + worldFilename, StorageSource.All); packet.Data.WorldXmlBytes = new byte[file.Length]; file.Read(packet.Data.WorldXmlBytes, 0, (int)file.Length); Storage4.Close(file); Instrumentation.RecordEvent(Instrumentation.EventId.LevelUploaded, xmlWorldData.name); } catch { if (file != null) { Storage4.Close(file); } packet = null; } return(packet); }
/// <summary> /// Delete a level from the local system. Returns false if not yet initialized. /// </summary> /// <param name="worldId"></param> /// <param name="callback"></param> /// <param name="param"></param> /// <returns></returns> public bool StartDeletingLevel( Guid worldId, Genres bucket, BokuAsyncCallback callback, object param) { bool deleted = false; bucket &= Genres.SharableBins; // Verify exactly one bucket is specified Debug.Assert(bucket != 0); Debug.Assert((int)bucket == int.MinValue || MyMath.IsPowerOfTwo((int)bucket)); string worldFilename = null; string stuffFilename = null; string thumbFilename = null; LevelMetadata record = null; string stuffPath = String.Empty; string worldPath = String.Empty; if (0 != (bucket & Genres.MyWorlds)) { stuffPath = BokuGame.MyWorldsStuffPath; worldPath = BokuGame.MyWorldsPath; } else if (0 != (bucket & Genres.Downloads)) { stuffPath = BokuGame.DownloadsStuffPath; worldPath = BokuGame.DownloadsPath; } lock (Synch) { for (int i = 0; i < allLevels.Count; ++i) { record = allLevels[i]; if (record.WorldId == worldId && (record.Genres & bucket) == bucket) { worldFilename = Path.Combine(BokuGame.Settings.MediaPath, worldPath + worldId.ToString() + @".Xml"); stuffFilename = Path.Combine(BokuGame.Settings.MediaPath, stuffPath + worldId.ToString() + @".Xml"); thumbFilename = Path.Combine(BokuGame.Settings.MediaPath, worldPath + worldId.ToString()); // Need to get the terrain file before we delete the main file. BUT the terrain should be // deleted after, otherwise the usage test will find the main file and always thing that // the terrain file is in use. string terrainFilename = null; try { // Only delete terrain file if no longer referenced. XmlWorldData xmlWorldData = XmlWorldData.Load(worldFilename, XnaStorageHelper.Instance); terrainFilename = xmlWorldData.xmlTerrainData2.virtualMapFile; } catch { } // Note : Delete() handles non-existent files just fine. Storage4.Delete(worldFilename); Storage4.Delete(stuffFilename); Storage4.Delete(thumbFilename + @".dds"); Storage4.Delete(thumbFilename + @".jpg"); Storage4.Delete(thumbFilename + @".png"); // Only deletes terrain file if no other world is using it. (including autosaves) DeleteTerrainFile(terrainFilename); LevelMetadata level = allLevels[i]; allLevels.RemoveAt(i); LevelRemoved_Synched(level); deleted = true; break; } } } AsyncResult result = new AsyncResult(); result.Success = deleted; result.Param = param; result.Seconds = 0; if (callback != null) { callback(result); } return(deleted); }
/// <summary> /// Reads a directory, building the list of level metadata available for browsing. /// </summary> /// <param name="dataSource"></param> /// <returns></returns> private void ReadDataSource(string folder, Genres tag, StorageSource sources) { string path = Path.Combine(BokuGame.Settings.MediaPath, BokuGame.LevelsPath, folder); string[] files = null; try { #if NETFX_CORE files = Storage4.GetFiles(path, @"*.Xml", sources); #else files = Storage4.GetFiles(path, @"*.Xml", sources, SearchOption.TopDirectoryOnly); #endif } catch { } if (files != null) { // Filter out AutoSave.Xml List <string> filteredFiles = new List <string>(); for (int i = 0; i < files.Length; ++i) { if (files[i].ToUpper().Contains("AUTOSAVE")) { continue; } filteredFiles.Add(files[i]); } files = filteredFiles.ToArray(); // Load level metadata records for (int i = 0; running && i < files.Length; i++) { try { string filename = Path.GetFileName(files[i]); string fullPath = Path.Combine(path, filename); XmlWorldData xml = XmlWorldData.Load(fullPath, XnaStorageHelper.Instance, (int)sources); if (xml == null) { continue; } LevelMetadata level = new LevelMetadata(); level.FromXml(xml); level.Genres |= tag; LevelBrowserState state = new LevelBrowserState(); state.level = level; level.BrowserState = state; // Force the creator name of built-ins to "Microsoft" if ((level.Genres & Genres.BuiltInWorlds) != 0) { level.Creator = "Kodu Team"; } AddLevel(level); } catch (Exception e) { Debug.WriteLine(e.Message); } } } }
} // end of Shared c'tor /// <summary> /// Creates and populates a grid with file elements based on the input path and filter. /// </summary> /// <param name="path">Path where files reside.</param> /// <param name="filter">Filter for valid file names.</param> /// <returns>The created grid. Grids are created in the InActive state. Will return null if no files found.</returns> public UIGrid CreateGrid(String path, String filter) { // Create a list of files in the path. String[] files = null; try { #if !XBOX360 files = Storage.GetFiles(path, filter, SearchOption.TopDirectoryOnly); #else files = Storage.GetFiles(path, filter); #endif } catch (DirectoryNotFoundException) { // The directory will be empty, so no need to get files again // we do this as a convenience and its really not needed. } UIGrid grid = null; if (files != null && files.Length > 0) { List <string> filteredFiles = new List <string>(); // Filter AutoSave.Xml for (int i = 0; i < files.Length; ++i) { if (files[i].ToUpper().Contains("AUTOSAVE")) { continue; } filteredFiles.Add(files[i]); } files = filteredFiles.ToArray(); } if (files != null && files.Length > 0) { // Create and populate grid. grid = new UIGrid(parent.OnSelect, parent.OnCancel, new Point(1, files.Length), "App.LoadLevelMenu"); // Set up the blob for info common to all tiles. UIGridWorldTile.ParamBlob blob = new UIGridWorldTile.ParamBlob(); blob.width = 8.5f; blob.height = 1.25f; blob.edgeSize = 0.125f; blob.selectedColor = selectedColor; blob.unselectedColor = unselectedColor; blob.textColor = Color.White; blob.dropShadowColor = Color.Black; blob.useDropShadow = true; blob.invertDropShadow = false; blob.normalMapName = @"QuarterRoundNormalMap"; int index = 0; for (int i = 0; i < files.Length; i++) { DateTime dateTime = Storage.GetLastWriteTime(files[i]); String filename = files[i].Substring(path.Length); String fullPath = path + filename; XmlWorldData xmlWorldData = XmlWorldData.Load(fullPath); Texture thumb = null; try { string thumbFilename = xmlWorldData.GetThumbFilenameWithoutExtension(); thumb = Storage.TextureLoad(path + thumbFilename); } catch { } UIGridWorldTile tile = new UIGridWorldTile( blob, xmlWorldData.id, fullPath, xmlWorldData.name, xmlWorldData.description, xmlWorldData.creator, dateTime, xmlWorldData.rating, thumb ); grid.Add(tile, 0, index++); } grid.Spacing = new Vector2(0.0f, 0.05f); // The first number doesn't really matter since we're doing a 1d column. grid.Scrolling = true; grid.SlopOffset = true; } return(grid); } // end of CreateGrid()
private void ScrubTerrainFiles() { // Build a list of all terrain files in user storage. // For each world in local storage (all three bins) // get the XmlWorldData file // from the XmlWorldData file, get the terrain filename // remove that filename from the list // // Any terrain filenames still on the list should be deleted // since they're no longer referenced by any files. string[] terrainFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.TerrainPath, @"*.raw", StorageSource.UserSpace); // If nothing to scrub, just return. if (terrainFiles == null) { return; } string[] undoFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.UnDoPath, @"*.Xml", StorageSource.UserSpace); string[] myWorldsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.MyWorldsPath, @"*.Xml", StorageSource.UserSpace); string[] starterWorldsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.BuiltInWorldsPath, @"*.Xml", StorageSource.TitleSpace); string[] downloadsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.DownloadsPath, @"*.Xml", StorageSource.UserSpace); /// Undo/Resume files. We might have to fall back on these if the user deletes the /// world they are editing and then back back to it. if (undoFiles != null) { for (int i = 0; i < undoFiles.Length; ++i) { string filename = undoFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null) { continue; } if (xmlWorldData.xmlTerrainData2 != null) { string terrainName = Path.Combine(Storage4.UserLocation, BokuGame.Settings.MediaPath, xmlWorldData.xmlTerrainData2.virtualMapFile); for (int j = 0; j < terrainFiles.Length; ++j) { if (terrainName == terrainFiles[j]) { // Remove this file. terrainFiles[j] = null; break; } } } } } // MyWorlds if (myWorldsFiles != null) { for (int i = 0; i < myWorldsFiles.Length; ++i) { string filename = myWorldsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null) { continue; } if (xmlWorldData.xmlTerrainData2 != null) { string terrainName = Path.Combine(Storage4.UserLocation, BokuGame.Settings.MediaPath, xmlWorldData.xmlTerrainData2.virtualMapFile); for (int j = 0; j < terrainFiles.Length; ++j) { if (terrainName == terrainFiles[j]) { // Remove this file. terrainFiles[j] = null; break; } } } } } // BuiltInWorlds if (starterWorldsFiles != null) { for (int i = 0; i < starterWorldsFiles.Length; ++i) { try { string filename = starterWorldsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null) { continue; } if (xmlWorldData.xmlTerrainData2 != null) { string terrainName = Path.Combine(Storage4.UserLocation, BokuGame.Settings.MediaPath, xmlWorldData.xmlTerrainData2.virtualMapFile); for (int j = 0; j < terrainFiles.Length; ++j) { if (terrainName == terrainFiles[j]) { // Remove this file. terrainFiles[j] = null; break; } } } } catch { } } } // Downloads if (downloadsFiles != null) { for (int i = 0; i < downloadsFiles.Length; ++i) { string filename = downloadsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null) { continue; } if (xmlWorldData.xmlTerrainData2 != null) { string terrainName = Path.Combine(Storage4.UserLocation, BokuGame.Settings.MediaPath, xmlWorldData.xmlTerrainData2.virtualMapFile); for (int j = 0; j < terrainFiles.Length; ++j) { if (terrainName == terrainFiles[j]) { // Remove this file. terrainFiles[j] = null; break; } } } } } int deleteCount = 0; // Now, anything that's left in the list should be fair game for deletion. for (int i = 0; i < terrainFiles.Length; i++) { if (terrainFiles[i] != null) { if (Storage4.FileExists(terrainFiles[i], StorageSource.UserSpace)) { if (Storage4.Delete(terrainFiles[i])) { deleteCount += 1; } } } } //System.Diagnostics.Debug.WriteLine(String.Format("Scrubbed {0} terrain files", deleteCount)); } // end of ScrubTerrainFiles()
} // end of ScrubTerrainFiles() /// <summary> /// Deletes the given terrain file but only after verifying that no world is using it. /// It any world is found that is using the file then it is not deleted. /// /// Note this still isn't perfect since when a level is deleted, the terrain file may still be left /// in the undo stack and hence not deleted. No biggy. It's better than deleting too much and it's /// much quicker than the above ScrubTerrainFiles(). /// </summary> /// <param name="terrainFile"></param> void DeleteTerrainFile(string terrainFile) { string[] undoFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.UnDoPath, @"*.Xml", StorageSource.UserSpace); string[] myWorldsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.MyWorldsPath, @"*.Xml", StorageSource.UserSpace); string[] starterWorldsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.BuiltInWorldsPath, @"*.Xml", StorageSource.TitleSpace); string[] downloadsFiles = Storage4.GetFiles(BokuGame.Settings.MediaPath + BokuGame.DownloadsPath, @"*.Xml", StorageSource.UserSpace); /// Undo/Resume files. We might have to fall back on these if the user deletes the /// world they are editing and then back back to it. if (undoFiles != null) { for (int i = 0; i < undoFiles.Length; ++i) { string filename = undoFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null || xmlWorldData.xmlTerrainData2 == null) { continue; } if (xmlWorldData.xmlTerrainData2.virtualMapFile == terrainFile) { // Found it, don't delete. return; } } } // MyWorlds if (myWorldsFiles != null) { for (int i = 0; i < myWorldsFiles.Length; ++i) { string filename = myWorldsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null || xmlWorldData.xmlTerrainData2 == null) { continue; } if (xmlWorldData.xmlTerrainData2.virtualMapFile == terrainFile) { // Found it, don't delete. return; } } } // BuiltInWorlds if (starterWorldsFiles != null) { for (int i = 0; i < starterWorldsFiles.Length; ++i) { try { string filename = starterWorldsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null || xmlWorldData.xmlTerrainData2 == null) { continue; } if (xmlWorldData.xmlTerrainData2.virtualMapFile == terrainFile) { // Found it, don't delete. return; } } catch { } } } // Downloads if (downloadsFiles != null) { for (int i = 0; i < downloadsFiles.Length; ++i) { string filename = downloadsFiles[i]; XmlWorldData xmlWorldData = XmlWorldData.Load(filename, XnaStorageHelper.Instance); if (xmlWorldData == null || xmlWorldData.xmlTerrainData2 == null) { continue; } if (xmlWorldData.xmlTerrainData2.virtualMapFile == terrainFile) { // Found it, don't delete. return; } } } // Nothing was found using this terrain file so we can delete it. try { terrainFile = Path.Combine(BokuGame.Settings.MediaPath, terrainFile); Storage4.Delete(terrainFile); } catch (Exception e) { if (e != null) { } } } // end of DeleteTerrainFile()