/// <summary> /// Process returns a readonly list of now playing media items, filtered by optional parameters /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get NowPlayingService instance NowPlayingService nowPlayingService = (NowPlayingService)ServiceManager.GetInstance("nowplaying"); // Ensure service is running if (nowPlayingService == null) { processor.WriteJson(new NowPlayingResponse("NowPlayingService is not running!", null)); return; } // Store list of now playing objects IList<NowPlaying> nowPlaying = nowPlayingService.Playing; // Filter by user name if (uri.Parameters.ContainsKey("user")) { nowPlaying = nowPlaying.Where(x => x.User.UserName == uri.Parameters["user"]).ToList(); } // Filter by client name if (uri.Parameters.ContainsKey("client")) { nowPlaying = nowPlaying.Where(x => x.User.CurrentSession.ClientName == uri.Parameters["client"]).ToList(); } // Return list of now playing items processor.WriteJson(new NowPlayingResponse(null, nowPlaying)); return; }
/// <summary> /// Process returns a new session key for this user upon successful login /// <summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { logger.IfInfo(String.Format("Authenticated user, generated new session: [user: {0}, key: {1}]", user.UserName, user.SessionId)); processor.WriteJson(new LoginResponse(null, user.SessionId)); return; }
/// <summary> /// Process returns a file stream containing album art /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for the MusicBrainz ID string musicBrainzId = null; uri.Parameters.TryGetValue("musicBrainzId", out musicBrainzId); if (musicBrainzId == null) { processor.WriteErrorHeader(); return; } // Find the music brainz id string path = ArtPathForMusicBrainzId(musicBrainzId); FileInfo info = new FileInfo(path); if (info.Exists) { DateTime? lastModified = info.LastWriteTimeUtc; FileStream stream = new FileStream(path, FileMode.Open); processor.WriteFile(stream, 0, stream.Length, HttpHeader.MimeTypeForExtension(".jpg"), null, true, lastModified); // Close the file so we don't get sharing violations on future accesses stream.Close(); } else { // If all else fails, error processor.WriteErrorHeader(); } }
/// <summary> /// Process returns a readonly list of now playing media items, filtered by optional parameters /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get NowPlayingService instance NowPlayingService nowPlayingService = (NowPlayingService)ServiceManager.GetInstance("nowplaying"); // Ensure service is running if (nowPlayingService == null) { processor.WriteJson(new NowPlayingResponse("NowPlayingService is not running!", null)); return; } // Store list of now playing objects IList <NowPlaying> nowPlaying = nowPlayingService.Playing; // Filter by user name if (uri.Parameters.ContainsKey("user")) { nowPlaying = nowPlaying.Where(x => x.User.UserName == uri.Parameters["user"]).ToList(); } // Filter by client name if (uri.Parameters.ContainsKey("client")) { nowPlaying = nowPlaying.Where(x => x.User.CurrentSession.ClientName == uri.Parameters["client"]).ToList(); } // Return list of now playing items processor.WriteJson(new NowPlayingResponse(null, nowPlaying)); return; }
public void LocalPath_IfTheWrappedUriIsRelative_ShouldReturnTheRelativePath() { const string path = "Directory"; var uriWrapper = new UriWrapper(new Uri(path, UriKind.Relative)); Assert.AreEqual("/" + path, uriWrapper.LocalPath); }
/// <summary> /// Process logs the error, creates a JSON response, and send it back to the user on bad API call /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user, string error) { logger.Error(error); ErrorResponse response = new ErrorResponse(error); processor.WriteJson(response); }
/// <summary> /// Process returns a JSON response list of folders /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Shortcut to favorites repository IFavoriteRepository favoriteRepository = Injection.Kernel.Get <IFavoriteRepository>(); // Lists of favorites and items associated IList <IItem> items = new List <IItem>(); IList <Favorite> favorites = new List <Favorite>(); // If no action specified, read favorites if (uri.Action == null || uri.Action == "read") { // Get this users's favorites favorites = favoriteRepository.FavoritesForUserId((int)user.UserId); // Get the items associated with their favorites items = favoriteRepository.ItemsForFavorites(favorites); // Send response processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // Verify ID present for remaining actions if (uri.Id == null) { processor.WriteJson(new FavoritesResponse("ID required for modifying favorites", null, null)); return; } // create - add favorites if (uri.Action == "create") { favoriteRepository.AddFavorite((int)user.UserId, (int)uri.Id, null); processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // delete - remove favorites if (uri.Action == "delete") { // Grab favorite to delete, verify its ownership Favorite fav = favoriteRepository.FavoriteForId((int)uri.Id); if (fav.FavoriteUserId != user.UserId) { processor.WriteJson(new FavoritesResponse("Cannot delete another user's favorite", null, null)); return; } // Remove favorite favoriteRepository.DeleteFavorite((int)uri.Id); processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // Invalid action processor.WriteJson(new FavoritesResponse("Invalid action specified: " + uri.Action, null, null)); return; }
/// <summary> /// Process returns a JSON response list of folders /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Shortcut to favorites repository IFavoriteRepository favoriteRepository = Injection.Kernel.Get<IFavoriteRepository>(); // Lists of favorites and items associated IList<IItem> items = new List<IItem>(); IList<Favorite> favorites = new List<Favorite>(); // If no action specified, read favorites if (uri.Action == null || uri.Action == "read") { // Get this users's favorites favorites = favoriteRepository.FavoritesForUserId((int)user.UserId); // Get the items associated with their favorites items = favoriteRepository.ItemsForFavorites(favorites); // Send response processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // Verify ID present for remaining actions if (uri.Id == null) { processor.WriteJson(new FavoritesResponse("ID required for modifying favorites", null, null)); return; } // create - add favorites if (uri.Action == "create") { favoriteRepository.AddFavorite((int)user.UserId, (int)uri.Id, null); processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // delete - remove favorites if (uri.Action == "delete") { // Grab favorite to delete, verify its ownership Favorite fav = favoriteRepository.FavoriteForId((int)uri.Id); if (fav.FavoriteUserId != user.UserId) { processor.WriteJson(new FavoritesResponse("Cannot delete another user's favorite", null, null)); return; } // Remove favorite favoriteRepository.DeleteFavorite((int)uri.Id); processor.WriteJson(new FavoritesResponse(null, items, favorites)); return; } // Invalid action processor.WriteJson(new FavoritesResponse("Invalid action specified: " + uri.Action, null, null)); return; }
public void Port_IfTheSchemeIsWellknown_ShouldReturnTheDefaultPort() { var uriWrapper = new UriWrapper(new Uri("http://localhost", UriKind.Absolute)); // ReSharper disable PossibleInvalidOperationException Assert.AreEqual(80, uriWrapper.Port.Value); // ReSharper restore PossibleInvalidOperationException }
/// <summary> /// Process performs a HLS transcode on a media item /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Verify ID received if (uri.Id == null) { processor.WriteJson(new TranscodeHlsResponse("Missing required parameter 'id'")); return; } try { // Get the media item associated with this id ItemType itemType = Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId((int)uri.Id); IMediaItem item = null; if (itemType == ItemType.Song) { item = Injection.Kernel.Get<ISongRepository>().SongForId((int)uri.Id); logger.IfInfo("HLS transcoding for songs not currently supported"); // CURRENTLY DO NOT SUPPORT HLS STREAMING FOR SONGS return; } else if (itemType == ItemType.Video) { item = Injection.Kernel.Get<IVideoRepository>().VideoForId((int)uri.Id); logger.IfInfo("Preparing video stream: " + item.FileName); } // Return an error if none exists if ((item == null) || (!File.Exists(item.FilePath()))) { processor.WriteJson(new TranscodeHlsResponse("No media item exists with ID: " + (int)uri.Id)); return; } // Generate the playlist file string response = null; string[] transQualities = uri.Parameters.ContainsKey("transQuality") ? uri.Parameters["transQuality"].Split(',') : new string[] {"Medium"}; if (transQualities.Length == 1) { // This is a single playlist response = this.GeneratePlaylist(item, transQualities[0], uri); } else { // This is a multi playlist response = this.GenerateMultiPlaylist(item, transQualities, uri); } processor.WriteText(response, "application/x-mpegURL"); logger.IfInfo("Successfully HLS transcoded file!"); } catch (Exception e) { logger.Error(e); } }
public void GetLeftPart_IfTheWrappedUriIsRelativeAndThePartParameterIsQuery_ShouldReturnThePathAndQuery() { var uriWrapper = new UriWrapper(new Uri("Directory/Sub-Directory/File.txt?Key=Value#Our-fragment", UriKind.Relative)); Assert.AreEqual("/Directory/Sub-Directory/File.txt?Key=Value", uriWrapper.GetLeftPart(UriPartial.Query)); uriWrapper = new UriWrapper(new Uri(string.Empty, UriKind.Relative)); Assert.AreEqual("/", uriWrapper.GetLeftPart(UriPartial.Query)); }
public void GetComponents_Test() { var uriWrapper = new UriWrapper(new Uri("Directory", UriKind.Relative)); Assert.AreEqual("/Directory", uriWrapper.GetComponents(UriComponents.AbsoluteUri, UriFormat.Unescaped)); Assert.AreEqual("/Directory", uriWrapper.GetComponents(UriComponents.AbsoluteUri | UriComponents.Host, UriFormat.Unescaped)); Assert.IsNull(uriWrapper.GetComponents(UriComponents.Host, UriFormat.Unescaped)); Assert.IsNull(uriWrapper.GetComponents(UriComponents.Port, UriFormat.Unescaped)); }
/// <summary> /// Process performs a HLS transcode on a media item /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Verify ID received if (uri.Id == null) { processor.WriteJson(new TranscodeHlsResponse("Missing required parameter 'id'")); return; } try { // Get the media item associated with this id ItemType itemType = Injection.Kernel.Get <IItemRepository>().ItemTypeForItemId((int)uri.Id); IMediaItem item = null; if (itemType == ItemType.Song) { item = Injection.Kernel.Get <ISongRepository>().SongForId((int)uri.Id); logger.IfInfo("HLS transcoding for songs not currently supported"); // CURRENTLY DO NOT SUPPORT HLS STREAMING FOR SONGS return; } else if (itemType == ItemType.Video) { item = Injection.Kernel.Get <IVideoRepository>().VideoForId((int)uri.Id); logger.IfInfo("Preparing video stream: " + item.FileName); } // Return an error if none exists if ((item == null) || (!File.Exists(item.FilePath()))) { processor.WriteJson(new TranscodeHlsResponse("No media item exists with ID: " + (int)uri.Id)); return; } // Generate the playlist file string response = null; string[] transQualities = uri.Parameters.ContainsKey("transQuality") ? uri.Parameters["transQuality"].Split(',') : new string[] { "Medium" }; if (transQualities.Length == 1) { // This is a single playlist response = this.GeneratePlaylist(item, transQualities[0], uri); } else { // This is a multi playlist response = this.GenerateMultiPlaylist(item, transQualities, uri); } processor.WriteText(response, "application/x-mpegURL"); logger.IfInfo("Successfully HLS transcoded file!"); } catch (Exception e) { logger.Error(e); } }
/// <summary> /// Process returns a JSON response list of genres /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate return lists of folders, songs, videos IList <Genre> listOfGenres = new List <Genre>(); IList <Folder> listOfFolders = new List <Folder>(); IList <Artist> listOfArtists = new List <Artist>(); IList <Album> listOfAlbums = new List <Album>(); IList <Song> listOfSongs = new List <Song>(); PairList <string, int> sectionPositions = new PairList <string, int>(); // ID specified if (uri.Id != null) { // Default: artists string type = "artists"; if (uri.Parameters.ContainsKey("type")) { type = uri.Parameters["type"]; } // Get single genre, add it for output Genre genre = Injection.Kernel.Get <IGenreRepository>().GenreForId((int)uri.Id); listOfGenres.Add(genre); switch (type) { case "folders": listOfFolders = genre.ListOfFolders(); break; case "albums": listOfAlbums = genre.ListOfAlbums(); break; case "songs": listOfSongs = genre.ListOfSongs(); break; case "artists": default: listOfArtists = genre.ListOfArtists(); break; } } else { // No id parameter listOfGenres = Injection.Kernel.Get <IGenreRepository>().AllGenres(); sectionPositions = Utility.SectionPositionsFromSortedList(new List <IGroupingItem>(listOfGenres.Select(c => (IGroupingItem)c))); } // Return all results processor.WriteJson(new GenresResponse(null, listOfGenres, listOfFolders, listOfArtists, listOfAlbums, listOfSongs, sectionPositions)); return; }
/// <summary> /// Process returns a copy of the media database, and can be used to return SQL deltas to update /// the local copy of the media database /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // If ID set, return all queries >= this id if (uri.Id != null) { processor.WriteJson(new DatabaseResponse(null, Injection.Kernel.Get <IDatabase>().QueryLogsSinceId((int)uri.Id))); return; } // No id parameter, so send down the whole backup database long databaseLastQueryId = -1; string databaseFileName = DatabaseBackup.Backup(out databaseLastQueryId); // Verify database filename present if ((object)databaseFileName == null) { processor.WriteErrorHeader(); return; } try { // Read in entire database file Stream stream = new FileStream(ServerUtility.RootPath() + databaseFileName, FileMode.Open, FileAccess.Read); long length = stream.Length; int startOffset = 0; // Handle the Range header to start from later in the file if connection interrupted if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; string start = range.Split(new char[] { '-', '=' })[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } // We send the last query id as a custom header IDictionary <string, string> customHeader = new Dictionary <string, string>(); customHeader["WaveBox-LastQueryId"] = databaseLastQueryId.ToString(); // Send the database file processor.WriteFile(stream, startOffset, length, "application/octet-stream", customHeader, true, new FileInfo(ServerUtility.RootPath() + databaseFileName).LastWriteTimeUtc); stream.Close(); } catch { // Send JSON on error processor.WriteJson(new DatabaseResponse("Could not open backup database " + databaseFileName, null)); } return; }
/// <summary> /// Process returns a JSON response list of genres /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate return lists of folders, songs, videos IList<Genre> listOfGenres = new List<Genre>(); IList<Folder> listOfFolders = new List<Folder>(); IList<Artist> listOfArtists = new List<Artist>(); IList<Album> listOfAlbums = new List<Album>(); IList<Song> listOfSongs = new List<Song>(); PairList<string, int> sectionPositions = new PairList<string, int>(); // ID specified if (uri.Id != null) { // Default: artists string type = "artists"; if (uri.Parameters.ContainsKey("type")) { type = uri.Parameters["type"]; } // Get single genre, add it for output Genre genre = Injection.Kernel.Get<IGenreRepository>().GenreForId((int)uri.Id); listOfGenres.Add(genre); switch (type) { case "folders": listOfFolders = genre.ListOfFolders(); break; case "albums": listOfAlbums = genre.ListOfAlbums(); break; case "songs": listOfSongs = genre.ListOfSongs(); break; case "artists": default: listOfArtists = genre.ListOfArtists(); break; } } else { // No id parameter listOfGenres = Injection.Kernel.Get<IGenreRepository>().AllGenres(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(listOfGenres.Select(c => (IGroupingItem)c))); } // Return all results processor.WriteJson(new GenresResponse(null, listOfGenres, listOfFolders, listOfArtists, listOfAlbums, listOfSongs, sectionPositions)); return; }
/// <summary> /// Process returns a copy of the media database, and can be used to return SQL deltas to update /// the local copy of the media database /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // If ID set, return all queries >= this id if (uri.Id != null) { processor.WriteJson(new DatabaseResponse(null, Injection.Kernel.Get<IDatabase>().QueryLogsSinceId((int)uri.Id))); return; } // No id parameter, so send down the whole backup database long databaseLastQueryId = -1; string databaseFileName = DatabaseBackup.Backup(out databaseLastQueryId); // Verify database filename present if ((object)databaseFileName == null) { processor.WriteErrorHeader(); return; } try { // Read in entire database file Stream stream = new FileStream(ServerUtility.RootPath() + databaseFileName, FileMode.Open, FileAccess.Read); long length = stream.Length; int startOffset = 0; // Handle the Range header to start from later in the file if connection interrupted if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; string start = range.Split(new char[]{'-', '='})[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } // We send the last query id as a custom header IDictionary<string, string> customHeader = new Dictionary<string, string>(); customHeader["WaveBox-LastQueryId"] = databaseLastQueryId.ToString(); // Send the database file processor.WriteFile(stream, startOffset, length, "application/octet-stream", customHeader, true, new FileInfo(ServerUtility.RootPath() + databaseFileName).LastWriteTimeUtc); stream.Close(); } catch { // Send JSON on error processor.WriteJson(new DatabaseResponse("Could not open backup database " + databaseFileName, null)); } return; }
/// <summary> /// Process logs this user out and destroys their current session /// <summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Destroy session if (user.DeleteSession(user.SessionId)) { logger.IfInfo(String.Format("Logged out user, destroyed session: [user: {0}, key: {1}]", user.UserName, user.SessionId)); processor.WriteJson(new LogoutResponse(null, user.SessionId)); return; } processor.WriteJson(new LogoutResponse("Failed to destroy user session", user.SessionId)); return; }
/// <summary> /// Generate playlist for a single item /// </summary> private string GeneratePlaylist(IMediaItem item, string transQuality, UriWrapper uri) { // If duration not set, null! if ((object)item.Duration == null) { return(null); } // Set default parameters from URL string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; // Begin creating M3U playlist StringBuilder builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXT-X-TARGETDURATION:10"); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); int offset = 0; for (int i = (int)item.Duration; i > 0; i -= 10) { // Calculate the length of this slice int seconds = i < 10 ? i : 10; // Add the default line builder.AppendLine("#EXTINF:" + seconds + ","); builder.Append("transcode?s=" + s + "&id=" + id + "&offsetSeconds=" + offset + "&transQuality=" + transQuality + "&lengthSeconds=" + seconds + "&transType=MPEGTS&isDirect=true"); // Add the optional parameters if ((object)width != null) { builder.Append("&width=" + width); } if ((object)height != null) { builder.Append("&height=" + height); } builder.AppendLine(); offset += seconds; } // Finalize file builder.AppendLine("#EXT-X-ENDLIST"); return(builder.ToString()); }
/// <summary> /// Process returns a JSON response list of folders /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate return lists of folders, songs, videos IList <Folder> listOfFolders = new List <Folder>(); IList <Song> listOfSongs = new List <Song>(); IList <Video> listOfVideos = new List <Video>(); Folder containingFolder = null; bool recursive = false; PairList <string, int> sectionPositions = new PairList <string, int>(); // If ID present, return that folder if (uri.Id != null) { // Return the folder for this id containingFolder = Injection.Kernel.Get <IFolderRepository>().FolderForId((int)uri.Id); listOfFolders = containingFolder.ListOfSubFolders(); if (uri.Parameters.ContainsKey("recursiveMedia") && uri.Parameters["recursiveMedia"].IsTrue()) { recursive = true; } // Get it, son. listOfSongs = containingFolder.ListOfSongs(recursive); listOfVideos = containingFolder.ListOfVideos(recursive); // Return all results processor.WriteJson(new FoldersResponse(null, containingFolder, listOfFolders, listOfSongs, listOfVideos, sectionPositions)); return; } // No id parameter if (uri.Parameters.ContainsKey("mediaFolders") && uri.Parameters["mediaFolders"].IsTrue()) { // They asked for the media folders listOfFolders = Injection.Kernel.Get <IFolderRepository>().MediaFolders(); } else { // They didn't ask for media folders, so send top level folders listOfFolders = Injection.Kernel.Get <IFolderRepository>().TopLevelFolders(); sectionPositions = Utility.SectionPositionsFromSortedList(new List <IGroupingItem>(listOfFolders.Select(c => (IGroupingItem)c))); } // Return all results processor.WriteJson(new FoldersResponse(null, containingFolder, listOfFolders, listOfSongs, listOfVideos, sectionPositions)); return; }
/// <summary> /// Process returns a JSON response list of folders /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate return lists of folders, songs, videos IList<Folder> listOfFolders = new List<Folder>(); IList<Song> listOfSongs = new List<Song>(); IList<Video> listOfVideos = new List<Video>(); Folder containingFolder = null; bool recursive = false; PairList<string, int> sectionPositions = new PairList<string, int>(); // If ID present, return that folder if (uri.Id != null) { // Return the folder for this id containingFolder = Injection.Kernel.Get<IFolderRepository>().FolderForId((int)uri.Id); listOfFolders = containingFolder.ListOfSubFolders(); if (uri.Parameters.ContainsKey("recursiveMedia") && uri.Parameters["recursiveMedia"].IsTrue()) { recursive = true; } // Get it, son. listOfSongs = containingFolder.ListOfSongs(recursive); listOfVideos = containingFolder.ListOfVideos(recursive); // Return all results processor.WriteJson(new FoldersResponse(null, containingFolder, listOfFolders, listOfSongs, listOfVideos, sectionPositions)); return; } // No id parameter if (uri.Parameters.ContainsKey("mediaFolders") && uri.Parameters["mediaFolders"].IsTrue()) { // They asked for the media folders listOfFolders = Injection.Kernel.Get<IFolderRepository>().MediaFolders(); } else { // They didn't ask for media folders, so send top level folders listOfFolders = Injection.Kernel.Get<IFolderRepository>().TopLevelFolders(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(listOfFolders.Select(c => (IGroupingItem)c))); } // Return all results processor.WriteJson(new FoldersResponse(null, containingFolder, listOfFolders, listOfSongs, listOfVideos, sectionPositions)); return; }
private IList <int> ParseIndexes(UriWrapper uri) { // Try to get the itemIds IList <int> itemIds = new List <int>(); if (uri.Parameters.ContainsKey("indexes")) { string[] itemIdStrings = uri.Parameters["indexes"].Split(','); foreach (string itemIdString in itemIdStrings) { int itemId; if (Int32.TryParse(itemIdString, out itemId)) { itemIds.Add(itemId); } } } return(itemIds); }
/// <summary> /// Overload for IApiHandler interface /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { this.Process(uri, processor, user, "Invalid API call"); }
/// <summary> /// Process returns a list of videos from WaveBox /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Return list of videos IList<Video> videos = new List<Video>(); // Check if ID present if (uri.Id != null) { // Add video by ID to the list videos.Add(Injection.Kernel.Get<IVideoRepository>().VideoForId((int)uri.Id)); } // Check for a request for range of videos else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new VideosResponse("Parameter 'range' requires a valid, comma-separated character tuple", null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new VideosResponse("Parameter 'range' requires characters which are single alphanumeric values", null)); return; } // Grab range of videos videos = Injection.Kernel.Get<IVideoRepository>().RangeVideos(start, end); } // Check for a request to limit/paginate videos, like SQL // Note: can be combined with range or all videos if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a valid integer start index", null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a non-negative integer start index", null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a valid integer duration", null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a non-negative integer duration", null)); return; } } // Check if results list already populated by range if (videos.Count > 0) { // No duration? Return just specified number of videos if (duration == Int32.MinValue) { videos = videos.Skip(0).Take(index).ToList(); } else { // Else, return videos starting at index, up to count duration videos = videos.Skip(index).Take(duration).ToList(); } } else { // If no videos in list, grab directly using model method videos = Injection.Kernel.Get<IVideoRepository>().LimitVideos(index, duration); } } // Finally, if no videos already in list, send the whole list if (videos.Count == 0) { videos = Injection.Kernel.Get<IVideoRepository>().AllVideos(); } // Send it! processor.WriteJson(new VideosResponse(null, videos)); return; }
/// <summary> /// Process returns a JSON response list of playlists /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate return lists of playlists, media items in them IList<Playlist> listOfPlaylists = new List<Playlist>(); IList<IMediaItem> listOfMediaItems = new List<IMediaItem>(); PairList<string, int> sectionPositions = new PairList<string, int>(); // The playlist to perform actions Playlist playlist; // Check for playlist creation if (uri.Action == "create") { // Try to get the name string name = null; if (uri.Parameters.ContainsKey("name")) { name = HttpUtility.UrlDecode(uri.Parameters["name"]); } // Verify non-null name if (name == null) { processor.WriteJson(new PlaylistsResponse("Parameter 'name' required for playlist creation", null, null, null)); return; } // Verify name not already in use playlist = Injection.Kernel.Get<IPlaylistRepository>().PlaylistForName(name); if (playlist.ItemId != null) { processor.WriteJson(new PlaylistsResponse("Playlist name '" + name + "' already in use", null, null, null)); return; } // Looks like this name is unused, so create the playlist playlist.PlaylistName = name; playlist.CreatePlaylist(); // Try to get the itemIds to add them to the playlist if necessary IList<int> itemIds = this.ParseItemIds(uri); if (itemIds.Count > 0) { playlist.AddMediaItems(itemIds); } listOfMediaItems = playlist.ListOfMediaItems(); listOfPlaylists.Add(playlist); // Return newly created playlist processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // If not creating playlist, and no ID, return all playlists if (uri.Id == null) { listOfPlaylists = Injection.Kernel.Get<IPlaylistRepository>().AllPlaylists(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(listOfPlaylists.Select(c => (IGroupingItem)c))); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // If ID, return the playlist for this ID playlist = Injection.Kernel.Get<IPlaylistRepository>().PlaylistForId((int)uri.Id); if (playlist.PlaylistId == null) { processor.WriteJson(new PlaylistsResponse("Playlist does not exist", null, null, null)); return; } // read - default action, list all of the items in a playlist if (uri.Action == "read") { listOfPlaylists.Add(playlist); listOfMediaItems = playlist.ListOfMediaItems(); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // add - add items to a playlist if (uri.Action == "add") { // Try to get the itemIds to add them to the playlist if necessary IList<int> itemIds = this.ParseItemIds(uri); if (itemIds.Count == 0) { processor.WriteJson(new PlaylistsResponse("No item IDs found in URL", null, null, null)); return; } // Iterate item IDs for (int i = 0; i < itemIds.Count; i++) { // Store ID int itemId = itemIds[i]; // List of songs IList<IMediaItem> songs = null; // Iterate each item type in the list, adding items switch (Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId(itemId)) { case ItemType.Folder: // get all the media items underneath this folder and add them // Use Select instead of ConvertAll: http://stackoverflow.com/questions/1571819/difference-between-select-and-convertall-in-c-sharp songs = Injection.Kernel.Get<IFolderRepository>().FolderForId(itemId).ListOfSongs(true).Select(x => (IMediaItem)x).ToList(); playlist.AddMediaItems(songs); break; case ItemType.Artist: songs = Injection.Kernel.Get<IArtistRepository>().ArtistForId(itemId).ListOfSongs().Select(x => (IMediaItem)x).ToList(); playlist.AddMediaItems(songs); break; case ItemType.Album: songs = Injection.Kernel.Get<IAlbumRepository>().AlbumForId(itemId).ListOfSongs().Select(x => (IMediaItem)x).ToList(); playlist.AddMediaItems(songs); break; case ItemType.Song: playlist.AddMediaItem(Injection.Kernel.Get<ISongRepository>().SongForId(itemId)); break; case ItemType.Video: playlist.AddMediaItem(Injection.Kernel.Get<IVideoRepository>().VideoForId(itemId)); break; default: processor.WriteJson(new PlaylistsResponse("Invalid item type at index: " + i, null, null, null)); return; } } // Grab everything just put in the playlist listOfPlaylists.Add(playlist); listOfMediaItems = playlist.ListOfMediaItems(); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // delete - delete a playlist if (uri.Action == "delete") { playlist.DeletePlaylist(); listOfPlaylists.Add(playlist); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // insert - insert item in playlist at specified index if (uri.Action == "insert") { IList<int> insertItemIds = this.ParseItemIds(uri); IList<int> insertIndexes = this.ParseIndexes(uri); if (insertItemIds.Count == 0 || insertItemIds.Count != insertIndexes.Count) { processor.WriteJson(new PlaylistsResponse("Incorrect number of items and indices supplied for action 'insert'", null, null, null)); return; } // Add media items and specified indices for (int i = 0; i < insertItemIds.Count; i++) { int itemId = insertItemIds[i]; int index = insertIndexes[i]; playlist.InsertMediaItem(itemId, index); } // Return playlist with media items listOfPlaylists.Add(playlist); listOfMediaItems = playlist.ListOfMediaItems(); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // move - move an item in the playlist if (uri.Action == "move") { IList<int> moveIndexes = this.ParseIndexes(uri); if (moveIndexes.Count == 0 || moveIndexes.Count % 2 != 0) { processor.WriteJson(new PlaylistsResponse("Incorrect number of indices supplied for action 'move'", null, null, null)); return; } // Move media items in playlist for (int i = 0; i < moveIndexes.Count; i += 2) { int fromIndex = moveIndexes[i]; int toIndex = moveIndexes[i+1]; playlist.MoveMediaItem(fromIndex, toIndex); } listOfPlaylists.Add(playlist); listOfMediaItems = playlist.ListOfMediaItems(); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // remove - remove items from playlist if (uri.Action == "remove") { IList<int> removeIndexes = this.ParseIndexes(uri); if (removeIndexes.Count == 0) { processor.WriteJson(new PlaylistsResponse("No indices supplied for action 'remove'", null, null, null)); return; } playlist.RemoveMediaItemAtIndexes(removeIndexes); listOfPlaylists.Add(playlist); listOfMediaItems = playlist.ListOfMediaItems(); processor.WriteJson(new PlaylistsResponse(null, listOfPlaylists, listOfMediaItems, sectionPositions)); return; } // Finally, invalid action supplied processor.WriteJson(new PlaylistsResponse("Invalid action: " + uri.Action, listOfPlaylists, listOfMediaItems, sectionPositions)); return; }
/// <summary> /// Process a Last.fm API request /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Create Last.fm object for this user Lastfm lfm = new Lastfm(user); // Pull URL parameters for Last.fm integration string eve = null; uri.Parameters.TryGetValue("event", out eve); if (uri.Action == null || uri.Action == "auth") { // If not authenticated, pass back authorization URL if (!lfm.SessionAuthenticated) { processor.WriteJson(new ScrobbleResponse(null, lfm.AuthUrl)); } else { // Else, already authenticated processor.WriteJson(new ScrobbleResponse("LFMAlreadyAuthenticated")); } return; } // If Last.fm is not authenticated, provide an authorization URL if (!lfm.SessionAuthenticated) { logger.IfInfo("You must authenticate before you can scrobble."); processor.WriteJson(new ScrobbleResponse("LFMNotAuthenticated", lfm.AuthUrl)); return; } // Create list of scrobble data IList <LfmScrobbleData> scrobbles = new List <LfmScrobbleData>(); // Get Last.fm API enumerations LfmScrobbleType scrobbleType = Lastfm.ScrobbleTypeForString(uri.Action); // On invalid scrobble type, return error JSON if (scrobbleType == LfmScrobbleType.INVALID) { processor.WriteJson(new ScrobbleResponse("LFMInvalidScrobbleType")); return; } // On now playing scrobble type if (scrobbleType == LfmScrobbleType.NOWPLAYING) { // Ensure ID specified for scrobble if (uri.Id == null) { processor.WriteJson(new ScrobbleResponse("LFMNoIdSpecifiedForNowPlaying")); return; } // Add successful scrobble to list, submit scrobbles.Add(new LfmScrobbleData((int)uri.Id, null)); lfm.Scrobble(scrobbles, scrobbleType); } // Else, unknown scrobble event else { // On null event, return error JSON if (eve == null) { processor.WriteJson(new ScrobbleResponse("LFMNoEventSpecifiedForScrobble")); return; } // Ensure input is a comma-separated pair string[] input = eve.Split(','); if ((input.Length % 2) != 0) { processor.WriteJson(new ScrobbleResponse("LFMInvalidInput")); return; } // Add scrobbles from input data pairs int i = 0; while (i < input.Length) { scrobbles.Add(new LfmScrobbleData(int.Parse(input[i]), long.Parse(input[i + 1]))); i = i + 2; } } // Scrobble all plays string result = lfm.Scrobble(scrobbles, scrobbleType); dynamic resp = null; // No response, service must be offline if (result == null) { processor.WriteJson(new ScrobbleResponse("LFMServiceOffline")); return; } // If result is not null, store deserialize and store it try { resp = JsonConvert.DeserializeObject(result); } catch (Exception e) { logger.Error(e); } // Check for nowplaying or scrobbles fields if ((resp.nowplaying != null) || (resp.scrobbles != null)) { // Write blank scrobble response processor.WriteJson(new ScrobbleResponse()); return; } // Write error JSON if it exists else if (resp.error != null) { processor.WriteJson(new ScrobbleResponse(string.Format("LFM{0}: {1}", resp.error, resp.message))); return; } }
/// <summary> /// Process generates a JSON list of songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Return list of songs IList<Song> listOfSongs = new List<Song>(); PairList<string, int> sectionPositions = new PairList<string, int>(); // Check for valid ID if (uri.Id != null) { // Add song by ID to the list listOfSongs.Add(Injection.Kernel.Get<ISongRepository>().SongForId((int)uri.Id)); } // Check for a request for range of songs else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new SongsResponse("Parameter 'range' requires a valid, comma-separated character tuple", null, null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new SongsResponse("Parameter 'range' requires characters which are single alphanumeric values", null, null)); return; } // Grab range of songs listOfSongs = Injection.Kernel.Get<ISongRepository>().RangeSongs(start, end); } // Check for a request to limit/paginate songs, like SQL // Note: can be combined with range or all songs if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { processor.WriteJson(new SongsResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new SongsResponse("Parameter 'limit' requires a valid integer start index", null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new SongsResponse("Parameter 'limit' requires a non-negative integer start index", null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new SongsResponse("Parameter 'limit' requires a valid integer duration", null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new SongsResponse("Parameter 'limit' requires a non-negative integer duration", null, null)); return; } } // Check if results list already populated by range if (listOfSongs.Count > 0) { // No duration? Return just specified number of songs if (duration == Int32.MinValue) { listOfSongs = listOfSongs.Skip(0).Take(index).ToList(); } else { // Else, return songs starting at index, up to count duration listOfSongs = listOfSongs.Skip(index).Take(duration).ToList(); } } else { // If no songs in list, grab directly using model method listOfSongs = Injection.Kernel.Get<ISongRepository>().LimitSongs(index, duration); } } // Finally, if no songs already in list, send the whole list if (listOfSongs.Count == 0) { listOfSongs = Injection.Kernel.Get<ISongRepository>().AllSongs(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(listOfSongs.Select(c => (IGroupingItem)c))); } // Send it! processor.WriteJson(new SongsResponse(null, listOfSongs, sectionPositions)); }
/// <summary> /// Process allows the modification of users and their properties /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Return list of users to be passed as JSON IList<User> listOfUsers = new List<User>(); // Parse common parameters // Username string username = null; if (uri.Parameters.ContainsKey("username")) { username = uri.Parameters["username"]; } // Password string password = null; if (uri.Parameters.ContainsKey("password")) { password = uri.Parameters["password"]; } // Role Role role = Role.User; int roleInt = 0; if (uri.Parameters.ContainsKey("role") && Int32.TryParse(uri.Parameters["role"], out roleInt)) { // Validate role if (Enum.IsDefined(typeof(Role), roleInt)) { role = (Role)roleInt; } } // See if we need to make a test user if (uri.Parameters.ContainsKey("testUser") && uri.Parameters["testUser"].IsTrue()) { bool success = false; int durationSeconds = 0; if (uri.Parameters.ContainsKey("durationSeconds")) { success = Int32.TryParse(uri.Parameters["durationSeconds"], out durationSeconds); } // Create a test user and reply with the account info User testUser = Injection.Kernel.Get<IUserRepository>().CreateTestUser(success ? (int?)durationSeconds : null); if (testUser == null) { processor.WriteJson(new UsersResponse("Couldn't create user", listOfUsers)); return; } listOfUsers.Add(testUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Check for no action, or read action if (uri.Action == null || uri.Action == "read") { // On valid key, return a specific user, and their attributes if (uri.Id != null) { User oneUser = Injection.Kernel.Get<IUserRepository>().UserForId((int)uri.Id); listOfUsers.Add(oneUser); } else { // Else, return all users listOfUsers = Injection.Kernel.Get<IUserRepository>().AllUsers(); } processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Perform actions // killSession - remove a session by rowId if (uri.Action == "killSession") { // Try to pull rowId from parameters for session management int rowId = 0; if (!uri.Parameters.ContainsKey("rowId")) { processor.WriteJson(new UsersResponse("Missing parameter 'rowId' for action 'killSession'", null)); return; } // Try to parse rowId integer if (!Int32.TryParse(uri.Parameters["rowId"], out rowId)) { processor.WriteJson(new UsersResponse("Invalid integer for 'rowId' for action 'killSession'", null)); return; } // After all checks pass, delete this session, return user associated with it var session = Injection.Kernel.Get<ISessionRepository>().SessionForRowId(rowId); if (session != null) { session.Delete(); listOfUsers.Add(Injection.Kernel.Get<IUserRepository>().UserForId(Convert.ToInt32(session.UserId))); } processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // create - create a new user if (uri.Action == "create") { // Check for required username and password parameters if (username == null || password == null) { processor.WriteJson(new UsersResponse("Parameters 'username' and 'password' required for action 'create'", null)); return; } // Attempt to create the user User newUser = Injection.Kernel.Get<IUserRepository>().CreateUser(username, password, role, null); if (newUser == null) { processor.WriteJson(new UsersResponse("Action 'create' failed to create new user", null)); return; } // Verify user didn't already exist if (newUser.UserId == null) { processor.WriteJson(new UsersResponse("User '" + username + "' already exists!", null)); return; } // Return newly created user logger.IfInfo(String.Format("Successfully created new user [id: {0}, username: {1}]", newUser.UserId, newUser.UserName)); listOfUsers.Add(newUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Verify ID present if (uri.Id == null) { processor.WriteJson(new UsersResponse("Missing parameter ID", null)); return; } // delete - remove a user if (uri.Action == "delete") { // Attempt to fetch and delete user User deleteUser = Injection.Kernel.Get<IUserRepository>().UserForId((int)uri.Id); if (deleteUser.UserName == null || !deleteUser.Delete()) { processor.WriteJson(new UsersResponse("Action 'delete' failed to delete user", null)); return; } // Return deleted user logger.IfInfo(String.Format("Successfully deleted user [id: {0}, username: {1}]", deleteUser.UserId, deleteUser.UserName)); listOfUsers.Add(deleteUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // update - update a user's username, password, or role if (uri.Action == "update") { // Attempt to get user User updateUser = Injection.Kernel.Get<IUserRepository>().UserForId((int)uri.Id); if (updateUser.UserName == null) { processor.WriteJson(new UsersResponse("Invalid user ID for action 'update'", null)); return; } // If user isn't an admin, verify that they are attempting to update themselves if (!user.HasPermission(Role.Admin) && user.UserId != updateUser.UserId) { processor.WriteJson(new UsersResponse("Permission denied", null)); return; } // Change username if (username != null) { if (!updateUser.UpdateUsername(username)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change username", null)); return; } } // Change password if (password != null) { if (!updateUser.UpdatePassword(password)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change password", null)); return; } } // If user is admin, a role parameter is set, and the role is not their current one, change role if (user.HasPermission(Role.Admin) && uri.Parameters.ContainsKey("role") && (role != updateUser.Role)) { if (!updateUser.UpdateRole(role)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change role", null)); return; } } // Return updated user logger.IfInfo(String.Format("Successfully updated user [id: {0}, username: {1}]", updateUser.UserId, updateUser.UserName)); listOfUsers.Add(updateUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Invalid action processor.WriteJson(new UsersResponse("Invalid action specified", null)); return; }
public void Port_IfTheWrappedUriIsRelative_ShouldReturnNull() { var uriWrapper = new UriWrapper(new Uri("Directory", UriKind.Relative)); Assert.IsNull(uriWrapper.Port); }
/// <summary> /// Process returns an AlbumArtistsResponse containing a list of artists, albums, and songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Lists of artists, albums, songs to be returned via handler IList <AlbumArtist> albumArtists = new List <AlbumArtist>(); IList <Album> albums = new List <Album>(); IList <Song> songs = new List <Song>(); Dictionary <string, int> counts = new Dictionary <string, int>(); PairList <string, int> sectionPositions = new PairList <string, int>(); // Optional Last.fm info string lastfmInfo = null; // Check if an ID was passed if (uri.Id != null) { // Add artist by ID to the list AlbumArtist a = Injection.Kernel.Get <IAlbumArtistRepository>().AlbumArtistForId((int)uri.Id); albumArtists.Add(a); // Add artist's albums to response albums = a.ListOfAlbums(); counts.Add("albums", albums.Count); // If requested, add artist's songs to response if (uri.Parameters.ContainsKey("includeSongs") && uri.Parameters["includeSongs"].IsTrue()) { songs = a.ListOfSongs(); counts.Add("songs", songs.Count); } else { counts.Add("songs", a.ListOfSongs().Count); } // If requested, add artist's Last.fm info to response if (uri.Parameters.ContainsKey("lastfmInfo") && uri.Parameters["lastfmInfo"].IsTrue()) { logger.IfInfo("Querying Last.fm for artist: " + a.AlbumArtistName); try { lastfmInfo = Lastfm.GetAlbumArtistInfo(a); logger.IfInfo("Last.fm query complete!"); } catch (Exception e) { logger.Error("Last.fm query failed!"); logger.Error(e); } } // Get favorites count if available if (a.AlbumArtistId != null) { int favoriteCount = Injection.Kernel.Get <IFavoriteRepository>().FavoritesForAlbumArtistId(a.AlbumArtistId, user.UserId).Count; counts.Add("favorites", favoriteCount); } else { counts.Add("favorites", 0); } } // Check for a request for range of artists else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'range' requires a valid, comma-separated character tuple", null, null, null, null, null, null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'range' requires characters which are single alphanumeric values", null, null, null, null, null, null)); return; } // Grab range of artists albumArtists = Injection.Kernel.Get <IAlbumArtistRepository>().RangeAlbumArtists(start, end); } // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null, null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'limit' requires a valid integer start index", null, null, null, null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null, null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'limit' requires a valid integer duration", null, null, null, null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new AlbumArtistsResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null, null, null, null)); return; } } // Check if results list already populated by range if (albumArtists.Count > 0) { // No duration? Return just specified number of artists if (duration == Int32.MinValue) { albumArtists = albumArtists.Skip(0).Take(index).ToList(); } else { // Else, return artists starting at index, up to count duration albumArtists = albumArtists.Skip(index).Take(duration).ToList(); } } else { // If no artists in list, grab directly using model method albumArtists = Injection.Kernel.Get <IAlbumArtistRepository>().LimitAlbumArtists(index, duration); } } // Finally, if no artists already in list, send the whole list if (albumArtists.Count == 0) { albumArtists = Injection.Kernel.Get <IAlbumArtistRepository>().AllAlbumArtists(); sectionPositions = Utility.SectionPositionsFromSortedList(new List <IGroupingItem>(albumArtists.Select(c => (IGroupingItem)c))); } // Send it! processor.WriteJson(new AlbumArtistsResponse(null, albumArtists, albums, songs, counts, lastfmInfo, sectionPositions)); return; }
/// <summary> /// Process is used to return a JSON object containing a variety of information about the host system /// which is running the WaveBox server /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { try { // Allocate an array of various statistics about the running process IDictionary<string, object> status = new Dictionary<string, object>(); // Gather data about WaveBox process global::System.Diagnostics.Process proc = global::System.Diagnostics.Process.GetCurrentProcess(); // Get current UNIX time long unixTime = DateTime.UtcNow.ToUnixTime(); // Get current query log ID long queryLogId = Injection.Kernel.Get<IDatabase>().LastQueryLogId; // Get process ID status["pid"] = proc.Id; // Get uptime of WaveBox instance status["uptime"] = unixTime - WaveBoxService.StartTime.ToUnixTime(); // Get last update time in UNIX format for status status["updated"] = unixTime; // Get hostname of machine status["hostname"] = System.Environment.MachineName; // Get WaveBox version status["version"] = WaveBoxService.BuildVersion; // Get build date status["buildDate"] = WaveBoxService.BuildDate.ToString("MMMM dd, yyyy"); // Get host platform status["platform"] = WaveBoxService.OS.ToDescription(); // Get current CPU usage status["cpuPercent"] = CpuUsage(); // Get current memory usage in MB status["memoryMb"] = (float)proc.WorkingSet64 / 1024f / 1024f; // Get peak memory usage in MB status["peakMemoryMb"] = (float)proc.PeakWorkingSet64 / 1024f / 1024f; // Get list of media types WaveBox can index and serve (removing "Unknown") status["mediaTypes"] = Enum.GetNames(typeof(FileType)).Where(x => x != "Unknown").ToList(); // Get list of transcoders available status["transcoders"] = Enum.GetNames(typeof(TranscodeType)).ToList(); // Get list of services status["services"] = ServiceManager.GetServices(); // Get last query log ID status["lastQueryLogId"] = queryLogId; // Call for extended status, which uses some database intensive calls if (uri.Parameters.ContainsKey("extended")) { if (uri.Parameters["extended"].IsTrue()) { // Check if any destructive queries have been performed since the last cache if ((statusCache.LastQueryId == null) || (queryLogId > statusCache.LastQueryId)) { // Update to the latest query log ID statusCache.LastQueryId = queryLogId; logger.IfInfo("Gathering extended status metrics from database"); // Get count of artists statusCache.Cache["artistCount"] = Injection.Kernel.Get<IArtistRepository>().CountArtists(); // Get count of album artists statusCache.Cache["albumArtistCount"] = Injection.Kernel.Get<IAlbumArtistRepository>().CountAlbumArtists(); // Get count of albums statusCache.Cache["albumCount"] = Injection.Kernel.Get<IAlbumRepository>().CountAlbums(); // Get count of songs statusCache.Cache["songCount"] = Injection.Kernel.Get<ISongRepository>().CountSongs(); // Get count of videos statusCache.Cache["videoCount"] = Injection.Kernel.Get<IVideoRepository>().CountVideos(); // Get total file size of songs (bytes) statusCache.Cache["songFileSize"] = Injection.Kernel.Get<ISongRepository>().TotalSongSize(); // Get total file size of videos (bytes) statusCache.Cache["videoFileSize"] = Injection.Kernel.Get<IVideoRepository>().TotalVideoSize(); // Get total song duration statusCache.Cache["songDuration"] = Injection.Kernel.Get<ISongRepository>().TotalSongDuration(); // Get total video duration statusCache.Cache["videoDuration"] = Injection.Kernel.Get<IVideoRepository>().TotalVideoDuration(); logger.IfInfo("Metric gathering complete, cached results!"); } // Append cached status dictionary to status status = status.Concat(statusCache.Cache).ToDictionary(x => x.Key, x => x.Value); } } // Return all status processor.WriteJson(new StatusResponse(null, status)); return; } catch (Exception e) { logger.Error(e); } // Return error processor.WriteJson(new StatusResponse("Could not retrieve server status", null)); return; }
/// <summary> /// Process produces a direct file stream of the requested media file /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Verify ID received if (uri.Id == null) { processor.WriteJson(new StreamResponse("Missing required parameter 'id'")); return; } logger.IfInfo("Starting file streaming sequence"); // Try to get seconds float seconds = 0f; if (uri.Parameters.ContainsKey("seconds")) { float.TryParse(uri.Parameters["seconds"], out seconds); } try { // Get the media item associated with this id ItemType itemType = Injection.Kernel.Get <IItemRepository>().ItemTypeForItemId((int)uri.Id); IMediaItem item = null; if (itemType == ItemType.Song) { item = Injection.Kernel.Get <ISongRepository>().SongForId((int)uri.Id); logger.IfInfo("Preparing audio stream: " + item.FileName); } else if (itemType == ItemType.Video) { item = Injection.Kernel.Get <IVideoRepository>().VideoForId((int)uri.Id); logger.IfInfo("Preparing video stream: " + item.FileName); } // Return an error if none exists if ((item == null) || (!File.Exists(item.FilePath()))) { processor.WriteJson(new StreamResponse("No media item exists with ID: " + (int)uri.Id)); return; } // Prepare file stream Stream stream = item.File(); long length = stream.Length; int startOffset = 0; long? limitToSize = null; if (seconds > 0) { // Guess the file position based on the seconds requested // this is just rough now, but will be improved to take into account the header size float percent = seconds / (float)item.Duration; if (percent < 1f) { startOffset = (int)(item.FileSize * percent); logger.IfInfo("seconds: " + seconds + " Resuming from " + startOffset); } } else if (processor.HttpHeaders.ContainsKey("Range")) { // Handle the Range header to start from later in the file string range = (string)processor.HttpHeaders["Range"]; var split = range.Split(new char[] { '-', '=' }); string start = split[1]; string end = split.Length > 2 ? split[2] : null; logger.IfInfo("Range header: " + range + " Resuming from " + start); startOffset = Convert.ToInt32(start); if (!ReferenceEquals(end, null) && end != String.Empty) { limitToSize = (Convert.ToInt64(end) + 1) - startOffset; } } // Send the file processor.WriteFile(stream, startOffset, length, item.FileType.MimeType(), null, true, new FileInfo(item.FilePath()).LastWriteTimeUtc, limitToSize); stream.Close(); logger.IfInfo("Successfully streamed file!"); } catch (Exception e) { logger.Error(e); } }
/// <summary> /// Process allows the modification of users and their properties /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Return list of users to be passed as JSON IList <User> listOfUsers = new List <User>(); // Parse common parameters // Username string username = null; if (uri.Parameters.ContainsKey("username")) { username = uri.Parameters["username"]; } // Password string password = null; if (uri.Parameters.ContainsKey("password")) { password = uri.Parameters["password"]; } // Role Role role = Role.User; int roleInt = 0; if (uri.Parameters.ContainsKey("role") && Int32.TryParse(uri.Parameters["role"], out roleInt)) { // Validate role if (Enum.IsDefined(typeof(Role), roleInt)) { role = (Role)roleInt; } } // See if we need to make a test user if (uri.Parameters.ContainsKey("testUser") && uri.Parameters["testUser"].IsTrue()) { bool success = false; int durationSeconds = 0; if (uri.Parameters.ContainsKey("durationSeconds")) { success = Int32.TryParse(uri.Parameters["durationSeconds"], out durationSeconds); } // Create a test user and reply with the account info User testUser = Injection.Kernel.Get <IUserRepository>().CreateTestUser(success ? (int?)durationSeconds : null); if (testUser == null) { processor.WriteJson(new UsersResponse("Couldn't create user", listOfUsers)); return; } listOfUsers.Add(testUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Check for no action, or read action if (uri.Action == null || uri.Action == "read") { // On valid key, return a specific user, and their attributes if (uri.Id != null) { User oneUser = Injection.Kernel.Get <IUserRepository>().UserForId((int)uri.Id); listOfUsers.Add(oneUser); } else { // Else, return all users listOfUsers = Injection.Kernel.Get <IUserRepository>().AllUsers(); } processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Perform actions // killSession - remove a session by rowId if (uri.Action == "killSession") { // Try to pull rowId from parameters for session management int rowId = 0; if (!uri.Parameters.ContainsKey("rowId")) { processor.WriteJson(new UsersResponse("Missing parameter 'rowId' for action 'killSession'", null)); return; } // Try to parse rowId integer if (!Int32.TryParse(uri.Parameters["rowId"], out rowId)) { processor.WriteJson(new UsersResponse("Invalid integer for 'rowId' for action 'killSession'", null)); return; } // After all checks pass, delete this session, return user associated with it var session = Injection.Kernel.Get <ISessionRepository>().SessionForRowId(rowId); if (session != null) { session.Delete(); listOfUsers.Add(Injection.Kernel.Get <IUserRepository>().UserForId(Convert.ToInt32(session.UserId))); } processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // create - create a new user if (uri.Action == "create") { // Check for required username and password parameters if (username == null || password == null) { processor.WriteJson(new UsersResponse("Parameters 'username' and 'password' required for action 'create'", null)); return; } // Attempt to create the user User newUser = Injection.Kernel.Get <IUserRepository>().CreateUser(username, password, role, null); if (newUser == null) { processor.WriteJson(new UsersResponse("Action 'create' failed to create new user", null)); return; } // Verify user didn't already exist if (newUser.UserId == null) { processor.WriteJson(new UsersResponse("User '" + username + "' already exists!", null)); return; } // Return newly created user logger.IfInfo(String.Format("Successfully created new user [id: {0}, username: {1}]", newUser.UserId, newUser.UserName)); listOfUsers.Add(newUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Verify ID present if (uri.Id == null) { processor.WriteJson(new UsersResponse("Missing parameter ID", null)); return; } // delete - remove a user if (uri.Action == "delete") { // Attempt to fetch and delete user User deleteUser = Injection.Kernel.Get <IUserRepository>().UserForId((int)uri.Id); if (deleteUser.UserName == null || !deleteUser.Delete()) { processor.WriteJson(new UsersResponse("Action 'delete' failed to delete user", null)); return; } // Return deleted user logger.IfInfo(String.Format("Successfully deleted user [id: {0}, username: {1}]", deleteUser.UserId, deleteUser.UserName)); listOfUsers.Add(deleteUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // update - update a user's username, password, or role if (uri.Action == "update") { // Attempt to get user User updateUser = Injection.Kernel.Get <IUserRepository>().UserForId((int)uri.Id); if (updateUser.UserName == null) { processor.WriteJson(new UsersResponse("Invalid user ID for action 'update'", null)); return; } // If user isn't an admin, verify that they are attempting to update themselves if (!user.HasPermission(Role.Admin) && user.UserId != updateUser.UserId) { processor.WriteJson(new UsersResponse("Permission denied", null)); return; } // Change username if (username != null) { if (!updateUser.UpdateUsername(username)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change username", null)); return; } } // Change password if (password != null) { if (!updateUser.UpdatePassword(password)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change password", null)); return; } } // If user is admin, a role parameter is set, and the role is not their current one, change role if (user.HasPermission(Role.Admin) && uri.Parameters.ContainsKey("role") && (role != updateUser.Role)) { if (!updateUser.UpdateRole(role)) { processor.WriteJson(new UsersResponse("Action 'update' failed to change role", null)); return; } } // Return updated user logger.IfInfo(String.Format("Successfully updated user [id: {0}, username: {1}]", updateUser.UserId, updateUser.UserName)); listOfUsers.Add(updateUser); processor.WriteJson(new UsersResponse(null, listOfUsers)); return; } // Invalid action processor.WriteJson(new UsersResponse("Invalid action specified", null)); return; }
/// <summary> /// Generates multiple item playlist /// <summary> private string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, UriWrapper uri) { // Ensure duration is set if ((object)item.Duration == null) { return null; } // Grab URI parameters string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; // Create new string, write M3U header StringBuilder builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); // Iterate all transcode qualities foreach (string qualityString in transQualities) { // Get the quality, default to medium uint quality = (uint)TranscodeQuality.Medium; TranscodeQuality qualityEnum; uint qualityValue; // First try and parse a word enum value if (Enum.TryParse<TranscodeQuality>(qualityString, true, out qualityEnum)) { quality = (uint)qualityEnum; } // Otherwise look for a number to use as bitrate else if (UInt32.TryParse(qualityString, out qualityValue)) { quality = qualityValue; } uint bitrate = VideoTranscoder.DefaultBitrateForQuality(quality); // Append information about this transcode to the playlist builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + (bitrate * 1000)); builder.Append("transcodehls?s=" + s + "&id=" + id + "&transQuality=" + bitrate); // Add the optional parameters if ((object)width != null) { builder.Append("&width=" + width); } if ((object)height != null) { builder.Append("&height=" + height); } builder.AppendLine(); } // Return the completed string return builder.ToString(); }
/// <summary> /// Process produces a direct file stream of the requested media file /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Verify ID received if (uri.Id == null) { processor.WriteJson(new StreamResponse("Missing required parameter 'id'")); return; } logger.IfInfo("Starting file streaming sequence"); // Try to get seconds float seconds = 0f; if (uri.Parameters.ContainsKey("seconds")) { float.TryParse(uri.Parameters["seconds"], out seconds); } try { // Get the media item associated with this id ItemType itemType = Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId((int)uri.Id); IMediaItem item = null; if (itemType == ItemType.Song) { item = Injection.Kernel.Get<ISongRepository>().SongForId((int)uri.Id); logger.IfInfo("Preparing audio stream: " + item.FileName); } else if (itemType == ItemType.Video) { item = Injection.Kernel.Get<IVideoRepository>().VideoForId((int)uri.Id); logger.IfInfo("Preparing video stream: " + item.FileName); } // Return an error if none exists if ((item == null) || (!File.Exists(item.FilePath()))) { processor.WriteJson(new StreamResponse("No media item exists with ID: " + (int)uri.Id)); return; } // Prepare file stream Stream stream = item.File(); long length = stream.Length; int startOffset = 0; long? limitToSize = null; if (seconds > 0) { // Guess the file position based on the seconds requested // this is just rough now, but will be improved to take into account the header size float percent = seconds / (float)item.Duration; if (percent < 1f) { startOffset = (int)(item.FileSize * percent); logger.IfInfo("seconds: " + seconds + " Resuming from " + startOffset); } } else if (processor.HttpHeaders.ContainsKey("Range")) { // Handle the Range header to start from later in the file string range = (string)processor.HttpHeaders["Range"]; var split = range.Split(new char[]{'-', '='}); string start = split[1]; string end = split.Length > 2 ? split[2] : null; logger.IfInfo("Range header: " + range + " Resuming from " + start); startOffset = Convert.ToInt32(start); if (!ReferenceEquals(end, null) && end != String.Empty) { limitToSize = (Convert.ToInt64(end) + 1) - startOffset; } } // Send the file processor.WriteFile(stream, startOffset, length, item.FileType.MimeType(), null, true, new FileInfo(item.FilePath()).LastWriteTimeUtc, limitToSize); stream.Close(); logger.IfInfo("Successfully streamed file!"); } catch (Exception e) { logger.Error(e); } }
/// <summary> /// Process returns a page from the WaveBox web interface /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Store root path, return index by default string path = webRoot; // Check for and apply theme path if (Injection.Kernel.Get <IServerSettings>().Theme == null) { logger.Error("No theme set in WaveBox configuration, cannot serve Web UI"); // File not found processor.WriteErrorHeader(); return; } // Append theme path to web root path += Injection.Kernel.Get <IServerSettings>().Theme; // Validate theme path if (!Directory.Exists(path)) { logger.Error("Invalid theme '" + Injection.Kernel.Get <IServerSettings>().Theme + "' set in WaveBox configuration, cannot serve Web UI"); // File not found processor.WriteErrorHeader(); return; } if (uri.UriParts.Count == 0) { // No path, so return the home page path += Path.DirectorySeparatorChar + "index.html"; // Ensure theme contains an index if (!File.Exists(path)) { logger.Error("Theme '" + Injection.Kernel.Get <IServerSettings>().Theme + "' missing required file index.html"); // File not found processor.WriteErrorHeader(); return; } } else { // Iterate UriParts to send web pages for (int i = 0; i < uri.UriParts.Count; i++) { string pathPart = uri.UriParts[i]; if (pathPart.Length > 0 && pathPart[0] == '.') { // Do not return hidden files/folders processor.WriteErrorHeader(); return; } else { path += Path.DirectorySeparatorChar + uri.UriParts[i]; } } } // Make sure the file exists if (!File.Exists(path)) { logger.IfInfo("File does not exist: " + path); // File not found processor.WriteErrorHeader(); return; } // Serve up files inside html directory FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read); int startOffset = 0; // Handle the Range header to start from later in the file if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; string start = range.Split(new char[] { '-', '=' })[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } long length = file.Length - startOffset; processor.WriteFile(file, startOffset, length, HttpHeader.MimeTypeForExtension(Path.GetExtension(path)), null, true, new FileInfo(path).LastWriteTimeUtc); file.Close(); }
/// <summary> /// Process returns a file stream containing album art /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for the itemId if (uri.Id == null) { processor.WriteErrorHeader(); return; } // Check for blur (value between 0 and 100) double blurSigma = 0; if (uri.Parameters.ContainsKey("blur")) { int blur = 0; Int32.TryParse(uri.Parameters["blur"], out blur); if (blur < 0) { blur = 0; } else if (blur > 100) { blur = 100; } blurSigma = (double)blur / 10.0; } // Grab art stream Art art = Injection.Kernel.Get<IArtRepository>().ArtForId((int)uri.Id); Stream stream = CreateStream(art); // If the stream could not be produced, return error if ((object)stream == null) { processor.WriteErrorHeader(); return; } // If art size requested... if (uri.Parameters.ContainsKey("size")) { int size = Int32.MaxValue; Int32.TryParse(uri.Parameters["size"], out size); // Parse size if valid if (size != Int32.MaxValue) { bool imageMagickFailed = false; if (ServerUtility.DetectOS() != ServerUtility.OS.Windows) { // First try ImageMagick try { Byte[] data = ResizeImageMagick(stream, size, blurSigma); stream = new MemoryStream(data, false); } catch { imageMagickFailed = true; } } // If ImageMagick dll isn't loaded, or this is Windows, if (imageMagickFailed || ServerUtility.DetectOS() == ServerUtility.OS.Windows) { // Resize image, put it in memory stream Image resized = ResizeImageGDI(new Bitmap(stream), new Size(size, size)); stream = new MemoryStream(); resized.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg); } } } DateTime? lastModified = null; if (!ReferenceEquals(art.LastModified, null)) { lastModified = ((long)art.LastModified).ToDateTime(); } processor.WriteFile(stream, 0, stream.Length, HttpHeader.MimeTypeForExtension(".jpg"), null, true, lastModified); // Close the file so we don't get sharing violations on future accesses stream.Close(); }
/// <summary> /// Process returns a serialized list of albums and songs in JSON format /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // List of songs and albums to be returned via handler IList <Song> songs = new List <Song>(); IList <Album> albums = new List <Album>(); PairList <string, int> sectionPositions = new PairList <string, int>(); // Check if an ID was passed if (uri.Id != null) { // Add album by ID to the list Album a = Injection.Kernel.Get <IAlbumRepository>().AlbumForId((int)uri.Id); albums.Add(a); // Add album's songs to response songs = a.ListOfSongs(); } // Check for a request for range of songs else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new AlbumsResponse("Parameter 'range' requires a valid, comma-separated character tuple", null, null, null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new AlbumsResponse("Parameter 'range' requires characters which are single alphanumeric values", null, null, null)); return; } // Grab range of albums albums = Injection.Kernel.Get <IAlbumRepository>().RangeAlbums(start, end); } // Check for a request to limit/paginate songs, like SQL // Note: can be combined with range or all albums if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a valid integer start index", null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a valid integer duration", null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null)); return; } } // Check if results list already populated by range if (albums.Count > 0) { // No duration? Return just specified number of albums if (duration == Int32.MinValue) { albums = albums.Skip(0).Take(index).ToList(); } else { // Else, return albums starting at index, up to count duration albums = albums.Skip(index).Take(duration).ToList(); } } else { // If no albums in list, grab directly using model method albums = Injection.Kernel.Get <IAlbumRepository>().LimitAlbums(index, duration); } } // Finally, if no albums already in list, send the whole list if (albums.Count == 0) { albums = Injection.Kernel.Get <IAlbumRepository>().AllAlbums(); sectionPositions = Utility.SectionPositionsFromSortedList(new List <IGroupingItem>(albums.Select(c => (IGroupingItem)c))); } // Send it! processor.WriteJson(new AlbumsResponse(null, albums, songs, sectionPositions)); }
/// <summary> /// Process performs a search for a query with specified types /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Lists to return as results IList<Artist> artists = new List<Artist>(); IList<AlbumArtist> albumArtists = new List<AlbumArtist>(); IList<Album> albums = new List<Album>(); IList<Song> songs = new List<Song>(); IList<Video> videos = new List<Video>(); // If no query is provided, error if (!uri.Parameters.ContainsKey("query")) { processor.WriteJson(new SearchResponse("No search query provided", artists, albumArtists, albums, songs, videos)); return; } // URL decode to strip any URL-encoded characters string query = HttpUtility.UrlDecode(uri.Parameters["query"]); // Ensure query is not blank if (query.Length < 1) { processor.WriteJson(new SearchResponse("Query cannot be empty", artists, albumArtists, albums, songs, videos)); return; } // Check for query field string field = null; if (uri.Parameters.ContainsKey("field")) { // Use input field for query field = HttpUtility.UrlDecode(uri.Parameters["field"]); } // Check for exact match parameter bool exact = false; if (uri.Parameters.ContainsKey("exact") && uri.Parameters["exact"].IsTrue()) { exact = true; } // If a query type is provided... if (uri.Parameters.ContainsKey("type")) { // Iterate all comma-separated values in query type foreach (string type in uri.Parameters["type"].Split(',')) { // Return results, populating lists depending on parameters specified switch (type) { case "artists": artists = Injection.Kernel.Get<IArtistRepository>().SearchArtists(field, query, exact); break; case "albumartists": albumArtists = Injection.Kernel.Get<IAlbumArtistRepository>().SearchAlbumArtists(field, query, exact); break; case "albums": albums = Injection.Kernel.Get<IAlbumRepository>().SearchAlbums(field, query, exact); break; case "songs": songs = Injection.Kernel.Get<ISongRepository>().SearchSongs(field, query, exact); break; case "videos": videos = Injection.Kernel.Get<IVideoRepository>().SearchVideos(field, query, exact); break; default: artists = Injection.Kernel.Get<IArtistRepository>().SearchArtists(field, query, exact); albums = Injection.Kernel.Get<IAlbumRepository>().SearchAlbums(field, query, exact); songs = Injection.Kernel.Get<ISongRepository>().SearchSongs(field, query, exact); videos = Injection.Kernel.Get<IVideoRepository>().SearchVideos(field, query, exact); break; } } } else { // For no type, provide all types of data artists = Injection.Kernel.Get<IArtistRepository>().SearchArtists(field, query, exact); albumArtists = Injection.Kernel.Get<IAlbumArtistRepository>().SearchAlbumArtists(field, query, exact); albums = Injection.Kernel.Get<IAlbumRepository>().SearchAlbums(field, query, exact); songs = Injection.Kernel.Get<ISongRepository>().SearchSongs(field, query, exact); videos = Injection.Kernel.Get<IVideoRepository>().SearchVideos(field, query, exact); } // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists if (uri.Parameters.ContainsKey("limit")) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a valid integer start index", null, null, null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a valid integer duration", null, null, null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null, null, null)); return; } } // No duration? Return just specified number of each item if (duration == Int32.MinValue) { artists = artists.Skip(0).Take(index).ToList(); albumArtists = albumArtists.Skip(0).Take(index).ToList(); albums = albums.Skip(0).Take(index).ToList(); songs = songs.Skip(0).Take(index).ToList(); videos = videos.Skip(0).Take(index).ToList(); } else { // Else return items starting at index, and taking duration artists = artists.Skip(index).Take(duration).ToList(); albumArtists = albumArtists.Skip(index).Take(duration).ToList(); albums = albums.Skip(index).Take(duration).ToList(); songs = songs.Skip(index).Take(duration).ToList(); videos = videos.Skip(index).Take(duration).ToList(); } } // Return all results processor.WriteJson(new SearchResponse(null, artists, albumArtists, albums, songs, videos)); return; }
/// <summary> /// Generates multiple item playlist /// <summary> private string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, UriWrapper uri) { // Ensure duration is set if ((object)item.Duration == null) { return(null); } // Grab URI parameters string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; // Create new string, write M3U header StringBuilder builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); // Iterate all transcode qualities foreach (string qualityString in transQualities) { // Get the quality, default to medium uint quality = (uint)TranscodeQuality.Medium; TranscodeQuality qualityEnum; uint qualityValue; // First try and parse a word enum value if (Enum.TryParse <TranscodeQuality>(qualityString, true, out qualityEnum)) { quality = (uint)qualityEnum; } // Otherwise look for a number to use as bitrate else if (UInt32.TryParse(qualityString, out qualityValue)) { quality = qualityValue; } uint bitrate = VideoTranscoder.DefaultBitrateForQuality(quality); // Append information about this transcode to the playlist builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + (bitrate * 1000)); builder.Append("transcodehls?s=" + s + "&id=" + id + "&transQuality=" + bitrate); // Add the optional parameters if ((object)width != null) { builder.Append("&width=" + width); } if ((object)height != null) { builder.Append("&height=" + height); } builder.AppendLine(); } // Return the completed string return(builder.ToString()); }
/// <summary> /// Process returns an ArtistsResponse containing a list of artists, albums, and songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Lists of artists, albums, songs to be returned via handler IList<Artist> artists = new List<Artist>(); IList<Album> albums = new List<Album>(); IList<Song> songs = new List<Song>(); Dictionary<string, int> counts = new Dictionary<string, int>(); PairList<string, int> sectionPositions = new PairList<string, int>(); // Optional Last.fm info string lastfmInfo = null; // Check if an ID was passed if (uri.Id != null) { // Add artist by ID to the list Artist a = Injection.Kernel.Get<IArtistRepository>().ArtistForId((int)uri.Id); if (a.ArtistId == null) { processor.WriteJson(new ArtistsResponse("Artist id not valid", null, null, null, null, null, null)); return; } artists.Add(a); // Add artist's albums to response albums = a.ListOfAlbums(); counts.Add("albums", albums.Count); // If requested, add artist's songs to response if (uri.Parameters.ContainsKey("includeSongs") && uri.Parameters["includeSongs"].IsTrue()) { songs = a.ListOfSongs(); counts.Add("songs", songs.Count); } else { counts.Add("songs", a.ListOfSongs().Count); } // If requested, add artist's Last.fm info to response if (uri.Parameters.ContainsKey("lastfmInfo") && uri.Parameters["lastfmInfo"].IsTrue()) { logger.IfInfo("Querying Last.fm for artist: " + a.ArtistName); try { lastfmInfo = Lastfm.GetArtistInfo(a); logger.IfInfo("Last.fm query complete!"); } catch (Exception e) { logger.Error("Last.fm query failed!"); logger.Error(e); } } // Get favorites count int numFavorites = Injection.Kernel.Get<IFavoriteRepository>().FavoritesForArtistId(a.ArtistId, user.UserId).Count; counts.Add("favorites", numFavorites); } // Check for a request for range of artists else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new ArtistsResponse("Parameter 'range' requires a valid, comma-separated character tuple", null, null, null, null, null, null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new ArtistsResponse("Parameter 'range' requires characters which are single alphanumeric values", null, null, null, null, null, null)); return; } // Grab range of artists artists = Injection.Kernel.Get<IArtistRepository>().RangeArtists(start, end); } // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists if (uri.Parameters.ContainsKey("limit") && !uri.Parameters.ContainsKey("id")) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { processor.WriteJson(new ArtistsResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null, null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new ArtistsResponse("Parameter 'limit' requires a valid integer start index", null, null, null, null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new ArtistsResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null, null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new ArtistsResponse("Parameter 'limit' requires a valid integer duration", null, null, null, null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new ArtistsResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null, null, null, null)); return; } } // Check if results list already populated by range if (artists.Count > 0) { // No duration? Return just specified number of artists if (duration == Int32.MinValue) { artists = artists.Skip(0).Take(index).ToList(); } else { // Else, return artists starting at index, up to count duration artists = artists.Skip(index).Take(duration).ToList(); } } else { // If no artists in list, grab directly using model method artists = Injection.Kernel.Get<IArtistRepository>().LimitArtists(index, duration); } } // Finally, if no artists already in list and no ID attribute, send the whole list if (artists.Count == 0 && uri.Id == null) { artists = Injection.Kernel.Get<IArtistRepository>().AllArtists(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(artists.Select(c => (IGroupingItem)c))); } // Send it! processor.WriteJson(new ArtistsResponse(null, artists, albums, songs, counts, lastfmInfo, sectionPositions)); }
/// <summary> /// Process returns a page from the WaveBox web interface /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Store root path, return index by default string path = webRoot; // Check for and apply theme path if (Injection.Kernel.Get<IServerSettings>().Theme == null) { logger.Error("No theme set in WaveBox configuration, cannot serve Web UI"); // File not found processor.WriteErrorHeader(); return; } // Append theme path to web root path += Injection.Kernel.Get<IServerSettings>().Theme; // Validate theme path if (!Directory.Exists(path)) { logger.Error("Invalid theme '" + Injection.Kernel.Get<IServerSettings>().Theme + "' set in WaveBox configuration, cannot serve Web UI"); // File not found processor.WriteErrorHeader(); return; } if (uri.UriParts.Count == 0) { // No path, so return the home page path += Path.DirectorySeparatorChar + "index.html"; // Ensure theme contains an index if (!File.Exists(path)) { logger.Error("Theme '" + Injection.Kernel.Get<IServerSettings>().Theme + "' missing required file index.html"); // File not found processor.WriteErrorHeader(); return; } } else { // Iterate UriParts to send web pages for (int i = 0; i < uri.UriParts.Count; i++) { string pathPart = uri.UriParts[i]; if (pathPart.Length > 0 && pathPart[0] == '.') { // Do not return hidden files/folders processor.WriteErrorHeader(); return; } else { path += Path.DirectorySeparatorChar + uri.UriParts[i]; } } } // Make sure the file exists if (!File.Exists(path)) { logger.IfInfo("File does not exist: " + path); // File not found processor.WriteErrorHeader(); return; } // Serve up files inside html directory FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read); int startOffset = 0; // Handle the Range header to start from later in the file if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; string start = range.Split(new char[]{'-', '='})[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } long length = file.Length - startOffset; processor.WriteFile(file, startOffset, length, HttpHeader.MimeTypeForExtension(Path.GetExtension(path)), null, true, new FileInfo(path).LastWriteTimeUtc); file.Close(); }
/// <summary> /// Process returns a file stream containing album art /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for the itemId if (uri.Id == null) { processor.WriteErrorHeader(); return; } // Check for blur (value between 0 and 100) double blurSigma = 0; if (uri.Parameters.ContainsKey("blur")) { int blur = 0; Int32.TryParse(uri.Parameters["blur"], out blur); if (blur < 0) { blur = 0; } else if (blur > 100) { blur = 100; } blurSigma = (double)blur / 10.0; } // Grab art stream Art art = Injection.Kernel.Get <IArtRepository>().ArtForId((int)uri.Id); Stream stream = CreateStream(art); // If the stream could not be produced, return error if ((object)stream == null) { processor.WriteErrorHeader(); return; } // If art size requested... if (uri.Parameters.ContainsKey("size")) { int size = Int32.MaxValue; Int32.TryParse(uri.Parameters["size"], out size); // Parse size if valid if (size != Int32.MaxValue) { bool imageMagickFailed = false; if (ServerUtility.DetectOS() != ServerUtility.OS.Windows) { // First try ImageMagick try { Byte[] data = ResizeImageMagick(stream, size, blurSigma); stream = new MemoryStream(data, false); } catch { imageMagickFailed = true; } } // If ImageMagick dll isn't loaded, or this is Windows, if (imageMagickFailed || ServerUtility.DetectOS() == ServerUtility.OS.Windows) { // Resize image, put it in memory stream Image resized = ResizeImageGDI(new Bitmap(stream), new Size(size, size)); stream = new MemoryStream(); resized.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg); } } } DateTime?lastModified = null; if (!ReferenceEquals(art.LastModified, null)) { lastModified = ((long)art.LastModified).ToDateTime(); } processor.WriteFile(stream, 0, stream.Length, HttpHeader.MimeTypeForExtension(".jpg"), null, true, lastModified); // Close the file so we don't get sharing violations on future accesses stream.Close(); }
/// <summary> /// Generate playlist for a single item /// </summary> private string GeneratePlaylist(IMediaItem item, string transQuality, UriWrapper uri) { // If duration not set, null! if ((object)item.Duration == null) { return null; } // Set default parameters from URL string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; // Begin creating M3U playlist StringBuilder builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXT-X-TARGETDURATION:10"); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); int offset = 0; for (int i = (int)item.Duration; i > 0; i -= 10) { // Calculate the length of this slice int seconds = i < 10 ? i : 10; // Add the default line builder.AppendLine("#EXTINF:" + seconds + ","); builder.Append("transcode?s=" + s + "&id=" + id + "&offsetSeconds=" + offset + "&transQuality=" + transQuality + "&lengthSeconds=" + seconds + "&transType=MPEGTS&isDirect=true"); // Add the optional parameters if ((object)width != null) { builder.Append("&width=" + width); } if ((object)height != null) { builder.Append("&height=" + height); } builder.AppendLine(); offset += seconds; } // Finalize file builder.AppendLine("#EXT-X-ENDLIST"); return builder.ToString(); }
/// <summary> /// Process handles the initialization of the file transcoding sequence /// <summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get TranscodeService instance TranscodeService transcodeService = (TranscodeService)ServiceManager.GetInstance("transcode"); // Ensure transcode service is ready if ((object)transcodeService == null) { processor.WriteJson(new TranscodeResponse("TranscodeService is not running!")); return; } // Create transcoder ITranscoder transcoder = null; // Get seconds offset float seconds = 0f; if (uri.Parameters.ContainsKey("seconds")) { float.TryParse(uri.Parameters["seconds"], out seconds); } // Verify ID received if (uri.Id == null) { processor.WriteJson(new TranscodeResponse("Missing required parameter 'id'")); return; } try { // Set up default transcoding parameters ItemType itemType = Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId((int)uri.Id); IMediaItem item = null; TranscodeType transType = TranscodeType.MP3; bool isDirect = false; Stream stream = null; int startOffset = 0; long? limitToSize = null; long length = 0; bool estimateContentLength = false; // Optionally estimate content length if (uri.Parameters.ContainsKey("estimateContentLength")) { estimateContentLength = uri.Parameters["estimateContentLength"].IsTrue(); } // Get the media item associated with this id if (itemType == ItemType.Song) { item = Injection.Kernel.Get<ISongRepository>().SongForId((int)uri.Id); logger.IfInfo("Preparing audio transcode: " + item.FileName); // Default to MP3 transcoding transType = TranscodeType.MP3; } else if (itemType == ItemType.Video) { item = Injection.Kernel.Get<IVideoRepository>().VideoForId((int)uri.Id); logger.IfInfo("Preparing video transcode: " + item.FileName); // Default to h.264 transcoding transType = TranscodeType.X264; } // Return an error if no item exists if ((item == null) || (!File.Exists(item.FilePath()))) { processor.WriteJson(new TranscodeResponse("No media item exists with ID: " + (int)uri.Id)); return; } // Optionally add isDirect parameter if (uri.Parameters.ContainsKey("isDirect")) { isDirect = uri.Parameters["isDirect"].IsTrue(); } if (seconds > 0) { // Guess the file position based on the seconds requested // this is wrong now, but will be improved to take into account the header size and transcode quality // or even better, we should be able to just pass the offset seconds to ffmpeg float percent = seconds / (float)item.Duration; if (percent < 1f) { startOffset = (int)(item.FileSize * percent); logger.IfInfo("seconds: " + seconds + " Resuming from " + startOffset); } } else if (processor.HttpHeaders.ContainsKey("Range")) { // Handle the Range header to start from later in the file string range = (string)processor.HttpHeaders["Range"]; var split = range.Split(new char[]{'-', '='}); string start = split[1]; string end = split.Length > 2 ? split[2] : null; logger.IfInfo("Range header: " + range + " Resuming from " + start + " end: " + end); if (isDirect) { // TODO: Actually implement this lol // This is a direct transfer with no file buffer, so treat a Range request as if it // were start offset, unless an offsetSeconds was specified if (uri.Parameters.ContainsKey("offsetSeconds")) { } else { } } else { // This is a file request so use the range header to specify where in the file to return startOffset = Convert.ToInt32(start); if (!ReferenceEquals(end, null) && end.Length > 0) { limitToSize = (Convert.ToInt64(end) + 1) - startOffset; } } } // Get the transcoding type if specified if (uri.Parameters.ContainsKey("transType")) { // Parse transcoding type TranscodeType transTypeTemp; if (Enum.TryParse<TranscodeType>(uri.Parameters["transType"], true, out transTypeTemp)) { // Verify this is a valid transcode type for this item type if (transTypeTemp.IsValidForItemType(item.ItemType)) { // It is, so use it transType = transTypeTemp; } } } // Get the quality, default to medium uint quality = (uint)TranscodeQuality.Medium; if (uri.Parameters.ContainsKey("transQuality")) { string qualityString = uri.Parameters["transQuality"]; TranscodeQuality qualityEnum; uint qualityValue; // First try and parse a word enum value if (Enum.TryParse<TranscodeQuality>(qualityString, true, out qualityEnum)) { quality = (uint)qualityEnum; } // Otherwise look for a number to use as bitrate else if (UInt32.TryParse(qualityString, out qualityValue)) { quality = qualityValue; } } // Create the transcoder if (item.ItemType == ItemType.Song) { // Begin transcoding song transcoder = transcodeService.TranscodeSong(item, transType, (uint)quality, isDirect, 0, (uint)item.Duration); } else { // Video transcoding is just a bit more complicated. // Check to see if the width, height, and maintainAspect options were used uint? width = null; if (uri.Parameters.ContainsKey("width")) { uint widthTemp; width = UInt32.TryParse(uri.Parameters["width"], out widthTemp) ? (uint?)widthTemp : null; } uint? height = 0; if (uri.Parameters.ContainsKey("height")) { uint heightTemp; height = UInt32.TryParse(uri.Parameters["height"], out heightTemp) ? (uint?)heightTemp : null; } bool maintainAspect = true; if (uri.Parameters.ContainsKey("maintainAspect")) { if (!Boolean.TryParse(uri.Parameters["maintainAspect"], out maintainAspect)) { maintainAspect = true; } } // Check for offset seconds and length seconds parameters uint offsetSeconds = 0; if (uri.Parameters.ContainsKey("offsetSeconds")) { UInt32.TryParse(uri.Parameters["offsetSeconds"], out offsetSeconds); } uint lengthSeconds = 0; if (uri.Parameters.ContainsKey("lengthSeconds")) { UInt32.TryParse(uri.Parameters["lengthSeconds"], out lengthSeconds); } // Either stream the rest of the file, or the duration specified lengthSeconds = lengthSeconds == 0 ? (uint)item.Duration - offsetSeconds : lengthSeconds; // Begin video transcoding transcoder = transcodeService.TranscodeVideo(item, transType, quality, isDirect, width, height, maintainAspect, offsetSeconds, lengthSeconds); } // If a transcoder was generated... if ((object)transcoder != null) { length = (long)transcoder.EstimatedOutputSize; // Wait up 5 seconds for file or basestream to appear for (int i = 0; i < 20; i++) { if (transcoder.IsDirect) { logger.IfInfo("Checking if base stream exists"); if ((object)transcoder.TranscodeProcess != null && (object)transcoder.TranscodeProcess.StandardOutput.BaseStream != null) { // The base stream exists, so the transcoding process has started logger.IfInfo("Base stream exists, starting transfer"); stream = transcoder.TranscodeProcess.StandardOutput.BaseStream; break; } } else { logger.IfInfo("Checking if file exists (" + transcoder.OutputPath + ")"); if (File.Exists(transcoder.OutputPath)) { // The file exists, so the transcoding process has started stream = new FileStream(transcoder.OutputPath, FileMode.Open, FileAccess.Read); break; } } Thread.Sleep(250); } } // Send the file if either there is no transcoder and the original file exists OR // it's a direct transcoder and the base stream exists OR // it's a file transcoder and the transcoded file exists if ((object)transcoder == null && File.Exists(item.FilePath()) || (transcoder.IsDirect && (object)stream != null) || (!transcoder.IsDirect && File.Exists(transcoder.OutputPath))) { logger.IfInfo("Sending direct stream"); string mimeType = (object)transcoder == null ? item.FileType.MimeType() : transcoder.MimeType; processor.Transcoder = transcoder; if (uri.Parameters.ContainsKey("offsetSeconds")) { logger.IfInfo("Writing file at offsetSeconds " + uri.Parameters["offsetSeconds"]); } DateTime lastModified = transcoder.IsDirect ? DateTime.UtcNow : new FileInfo(transcoder.OutputPath).LastWriteTimeUtc; // Direct write file processor.WriteFile(stream, startOffset, length, mimeType, null, estimateContentLength, lastModified, limitToSize); stream.Close(); logger.IfInfo("Successfully sent direct stream"); if (uri.Parameters.ContainsKey("offsetSeconds")) { logger.IfInfo("DONE writing file at offsetSeconds " + uri.Parameters["offsetSeconds"]); } } else { processor.WriteErrorHeader(); } // Spin off a thread to consume the transcoder in 30 seconds. Thread consume = new Thread(() => transcodeService.ConsumedTranscode(transcoder)); consume.Start(); } catch (Exception e) { logger.Error(e); } }
private IList<int> ParseItemIds(UriWrapper uri) { // Try to get the itemIds IList<int> itemIds = new List<int>(); if (uri.Parameters.ContainsKey("itemIds")) { string[] itemIdStrings = uri.Parameters["itemIds"].Split(','); foreach (string itemIdString in itemIdStrings) { int itemId; if (Int32.TryParse(itemIdString, out itemId)) { itemIds.Add(itemId); } } } return itemIds; }
/// <summary> /// Process performs a search for a query with specified types /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Lists to return as results IList <Artist> artists = new List <Artist>(); IList <AlbumArtist> albumArtists = new List <AlbumArtist>(); IList <Album> albums = new List <Album>(); IList <Song> songs = new List <Song>(); IList <Video> videos = new List <Video>(); // If no query is provided, error if (!uri.Parameters.ContainsKey("query")) { processor.WriteJson(new SearchResponse("No search query provided", artists, albumArtists, albums, songs, videos)); return; } // URL decode to strip any URL-encoded characters string query = HttpUtility.UrlDecode(uri.Parameters["query"]); // Ensure query is not blank if (query.Length < 1) { processor.WriteJson(new SearchResponse("Query cannot be empty", artists, albumArtists, albums, songs, videos)); return; } // Check for query field string field = null; if (uri.Parameters.ContainsKey("field")) { // Use input field for query field = HttpUtility.UrlDecode(uri.Parameters["field"]); } // Check for exact match parameter bool exact = false; if (uri.Parameters.ContainsKey("exact") && uri.Parameters["exact"].IsTrue()) { exact = true; } // If a query type is provided... if (uri.Parameters.ContainsKey("type")) { // Iterate all comma-separated values in query type foreach (string type in uri.Parameters["type"].Split(',')) { // Return results, populating lists depending on parameters specified switch (type) { case "artists": artists = Injection.Kernel.Get <IArtistRepository>().SearchArtists(field, query, exact); break; case "albumartists": albumArtists = Injection.Kernel.Get <IAlbumArtistRepository>().SearchAlbumArtists(field, query, exact); break; case "albums": albums = Injection.Kernel.Get <IAlbumRepository>().SearchAlbums(field, query, exact); break; case "songs": songs = Injection.Kernel.Get <ISongRepository>().SearchSongs(field, query, exact); break; case "videos": videos = Injection.Kernel.Get <IVideoRepository>().SearchVideos(field, query, exact); break; default: artists = Injection.Kernel.Get <IArtistRepository>().SearchArtists(field, query, exact); albums = Injection.Kernel.Get <IAlbumRepository>().SearchAlbums(field, query, exact); songs = Injection.Kernel.Get <ISongRepository>().SearchSongs(field, query, exact); videos = Injection.Kernel.Get <IVideoRepository>().SearchVideos(field, query, exact); break; } } } else { // For no type, provide all types of data artists = Injection.Kernel.Get <IArtistRepository>().SearchArtists(field, query, exact); albumArtists = Injection.Kernel.Get <IAlbumArtistRepository>().SearchAlbumArtists(field, query, exact); albums = Injection.Kernel.Get <IAlbumRepository>().SearchAlbums(field, query, exact); songs = Injection.Kernel.Get <ISongRepository>().SearchSongs(field, query, exact); videos = Injection.Kernel.Get <IVideoRepository>().SearchVideos(field, query, exact); } // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists if (uri.Parameters.ContainsKey("limit")) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a valid integer start index", null, null, null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a valid integer duration", null, null, null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new SearchResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null, null, null)); return; } } // No duration? Return just specified number of each item if (duration == Int32.MinValue) { artists = artists.Skip(0).Take(index).ToList(); albumArtists = albumArtists.Skip(0).Take(index).ToList(); albums = albums.Skip(0).Take(index).ToList(); songs = songs.Skip(0).Take(index).ToList(); videos = videos.Skip(0).Take(index).ToList(); } else { // Else return items starting at index, and taking duration artists = artists.Skip(index).Take(duration).ToList(); albumArtists = albumArtists.Skip(index).Take(duration).ToList(); albums = albums.Skip(index).Take(duration).ToList(); songs = songs.Skip(index).Take(duration).ToList(); videos = videos.Skip(index).Take(duration).ToList(); } } // Return all results processor.WriteJson(new SearchResponse(null, artists, albumArtists, albums, songs, videos)); return; }
/// <summary> /// Process is used to return a JSON object containing a variety of information about the host system /// which is running the WaveBox server /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { try { // Allocate an array of various statistics about the running process IDictionary <string, object> status = new Dictionary <string, object>(); // Gather data about WaveBox process global::System.Diagnostics.Process proc = global::System.Diagnostics.Process.GetCurrentProcess(); // Get current UNIX time long unixTime = DateTime.UtcNow.ToUnixTime(); // Get current query log ID long queryLogId = Injection.Kernel.Get <IDatabase>().LastQueryLogId; // Get process ID status["pid"] = proc.Id; // Get uptime of WaveBox instance status["uptime"] = unixTime - WaveBoxService.StartTime.ToUnixTime(); // Get last update time in UNIX format for status status["updated"] = unixTime; // Get hostname of machine status["hostname"] = System.Environment.MachineName; // Get WaveBox version status["version"] = WaveBoxService.BuildVersion; // Get build date status["buildDate"] = WaveBoxService.BuildDate.ToString("MMMM dd, yyyy"); // Get host platform status["platform"] = WaveBoxService.OS.ToDescription(); // Get current CPU usage status["cpuPercent"] = CpuUsage(); // Get current memory usage in MB status["memoryMb"] = (float)proc.WorkingSet64 / 1024f / 1024f; // Get peak memory usage in MB status["peakMemoryMb"] = (float)proc.PeakWorkingSet64 / 1024f / 1024f; // Get list of media types WaveBox can index and serve (removing "Unknown") status["mediaTypes"] = Enum.GetNames(typeof(FileType)).Where(x => x != "Unknown").ToList(); // Get list of transcoders available status["transcoders"] = Enum.GetNames(typeof(TranscodeType)).ToList(); // Get list of services status["services"] = ServiceManager.GetServices(); // Get last query log ID status["lastQueryLogId"] = queryLogId; // Call for extended status, which uses some database intensive calls if (uri.Parameters.ContainsKey("extended")) { if (uri.Parameters["extended"].IsTrue()) { // Check if any destructive queries have been performed since the last cache if ((statusCache.LastQueryId == null) || (queryLogId > statusCache.LastQueryId)) { // Update to the latest query log ID statusCache.LastQueryId = queryLogId; logger.IfInfo("Gathering extended status metrics from database"); // Get count of artists statusCache.Cache["artistCount"] = Injection.Kernel.Get <IArtistRepository>().CountArtists(); // Get count of album artists statusCache.Cache["albumArtistCount"] = Injection.Kernel.Get <IAlbumArtistRepository>().CountAlbumArtists(); // Get count of albums statusCache.Cache["albumCount"] = Injection.Kernel.Get <IAlbumRepository>().CountAlbums(); // Get count of songs statusCache.Cache["songCount"] = Injection.Kernel.Get <ISongRepository>().CountSongs(); // Get count of videos statusCache.Cache["videoCount"] = Injection.Kernel.Get <IVideoRepository>().CountVideos(); // Get total file size of songs (bytes) statusCache.Cache["songFileSize"] = Injection.Kernel.Get <ISongRepository>().TotalSongSize(); // Get total file size of videos (bytes) statusCache.Cache["videoFileSize"] = Injection.Kernel.Get <IVideoRepository>().TotalVideoSize(); // Get total song duration statusCache.Cache["songDuration"] = Injection.Kernel.Get <ISongRepository>().TotalSongDuration(); // Get total video duration statusCache.Cache["videoDuration"] = Injection.Kernel.Get <IVideoRepository>().TotalVideoDuration(); logger.IfInfo("Metric gathering complete, cached results!"); } // Append cached status dictionary to status status = status.Concat(statusCache.Cache).ToDictionary(x => x.Key, x => x.Value); } } // Return all status processor.WriteJson(new StatusResponse(null, status)); return; } catch (Exception e) { logger.Error(e); } // Return error processor.WriteJson(new StatusResponse("Could not retrieve server status", null)); return; }
// Process API calls based on the class's HTTP URL private void ApiProcess() { // The API request wrapper UriWrapper uri = new UriWrapper(this.HttpUrl, this.HttpMethod); // The user who is accessing the API User apiUser = null; // The handler being accessed IApiHandler api = null; // No API request found? Serve web UI if (!uri.IsApiCall) { api = Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler("web"); api.Process(uri, this, apiUser); return; } // Get client IP address string ip = ((IPEndPoint)this.Socket.Client.RemoteEndPoint).Address.ToString(); // Check for valid API action ("web" and "error" are technically valid, but can't be used in this way) if (uri.ApiAction == null || uri.ApiAction == "web" || uri.ApiAction == "error") { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } // Check for session cookie authentication, unless this is a login request string sessionId = null; if (uri.ApiAction != "login") { sessionId = this.GetSessionCookie(); apiUser = Injection.Kernel.Get <IApiAuthenticate>().AuthenticateSession(sessionId); } // If no cookie, try parameter authentication if (apiUser == null) { apiUser = Injection.Kernel.Get <IApiAuthenticate>().AuthenticateUri(uri); // If user still null, failed authentication, so serve error if (apiUser == null) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Authentication failed"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } } // apiUser.SessionId will be generated on new login, so that takes precedence for new session cookie apiUser.SessionId = apiUser.SessionId ?? sessionId; this.SetSessionCookie(apiUser.SessionId); // Store user's current session object apiUser.CurrentSession = Injection.Kernel.Get <ISessionRepository>().SessionForSessionId(apiUser.SessionId); // Retrieve the requested API handler by its action IApiHandler apiHandler = Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler(uri.ApiAction); // Check for valid API action if (apiHandler == null) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } // Log API call logger.IfInfo(String.Format("[{0}/{1}@{2}] API: {3} {4}", apiUser.UserName, apiUser.CurrentSession.ClientName, ip, this.HttpMethod, this.HttpUrl)); // Check if user has appropriate permissions for this action on this API handler if (!apiHandler.CheckPermission(apiUser, uri.Action)) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get <IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Permission denied"); return; } // Finally, process and return results apiHandler.Process(uri, this, apiUser); }
/// <summary> /// Process returns a list of videos from WaveBox /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Return list of videos IList <Video> videos = new List <Video>(); // Check if ID present if (uri.Id != null) { // Add video by ID to the list videos.Add(Injection.Kernel.Get <IVideoRepository>().VideoForId((int)uri.Id)); } // Check for a request for range of videos else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new VideosResponse("Parameter 'range' requires a valid, comma-separated character tuple", null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new VideosResponse("Parameter 'range' requires characters which are single alphanumeric values", null)); return; } // Grab range of videos videos = Injection.Kernel.Get <IVideoRepository>().RangeVideos(start, end); } // Check for a request to limit/paginate videos, like SQL // Note: can be combined with range or all videos if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a valid integer start index", null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a non-negative integer start index", null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a valid integer duration", null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new VideosResponse("Parameter 'limit' requires a non-negative integer duration", null)); return; } } // Check if results list already populated by range if (videos.Count > 0) { // No duration? Return just specified number of videos if (duration == Int32.MinValue) { videos = videos.Skip(0).Take(index).ToList(); } else { // Else, return videos starting at index, up to count duration videos = videos.Skip(index).Take(duration).ToList(); } } else { // If no videos in list, grab directly using model method videos = Injection.Kernel.Get <IVideoRepository>().LimitVideos(index, duration); } } // Finally, if no videos already in list, send the whole list if (videos.Count == 0) { videos = Injection.Kernel.Get <IVideoRepository>().AllVideos(); } // Send it! processor.WriteJson(new VideosResponse(null, videos)); return; }