/// <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> /// Initializes a new instance of the <see cref="WaveBox.ApiHandler.Handlers.Lastfm"/> class. /// </summary> /// <param name='userId'> /// User identifier. /// </param> public Lastfm(User theUser) { user = theUser; sessionKey = user.LastfmSession; // If the session key is prepended by 'token:', then the user has already generated a request token. // we should now try to get a session using that token. If this fails, that simply means the user // has not granted us access yet. If there is no session key at all, then we should request a token // and do nothing else. if (sessionKey == null) { this.CreateAuthUrl(); logger.IfInfo(this.AuthUrl); } else if (sessionKey.Substring(0, 6) == "token:") { string token = sessionKey.Substring(6); this.GetSessionKeyAndUpdateUser(token); } else { sessionAuthenticated = true; } }
/// <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; }
/// <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 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 Process(UriWrapper uri, IHttpProcessor processor, User user) { // Read out settings if (uri.Action == "read" || uri.Action == null) { // If no parameter provided, return settings processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get<IServerSettings>().SettingsModel)); return; } // Check for required JSON parameter if (!uri.Parameters.ContainsKey("json")) { processor.WriteJson(new SettingsResponse("Missing required parameter 'json'", null)); return; } // Update settings if (uri.Action == "update") { // Take in settings in the JSON format (same as it is stored on disk), // pass it on to the Settings class for processing string json = HttpUtility.UrlDecode(uri.Parameters["json"]); // Attempt to write settings bool success = false; try { success = Injection.Kernel.Get<IServerSettings>().WriteSettings(json); Injection.Kernel.Get<IServerSettings>().Reload(); } catch (JsonException) { // Failure if invalid JSON provided processor.WriteJson(new SettingsResponse("Invalid JSON", null)); return; } // If settings fail to write, report error if (!success) { processor.WriteJson(new SettingsResponse("Settings could not be changed", null)); return; } // If settings wrote successfully, return success processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get<IServerSettings>().SettingsModel)); return; } // Invalid action processor.WriteJson(new SettingsResponse("Invalid action", 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; }
// Standard permissions public bool CheckPermission(User user, string action) { switch (action) { // Write case "update": return user.HasPermission(Role.User); // Read case "read": default: return user.HasPermission(Role.Test); } }
/// <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; }
// Standard permissions public bool CheckPermission(User user, string action) { switch (action) { // Write case "add": case "create": case "delete": case "insert": case "move": case "remove": return user.HasPermission(Role.User); // Read case "read": default: return user.HasPermission(Role.Test); } }
// Standard permissions public bool CheckPermission(User user, string action) { switch (action) { // Admin case "create": case "delete": case "killSession": return user.HasPermission(Role.Admin); // Write // update - so user can update their own username/password, but not role case "update": return user.HasPermission(Role.User); // Read case "read": default: return user.HasPermission(Role.Test); } }
/// <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 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); } }
/// <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; }
// API handler is read-only, so no permissions checks needed public bool CheckPermission(User user, string action) { return true; }
// Only standard user and up may report stats public bool CheckPermission(User user, string action) { return user.HasPermission(Role.User); }
public static int CompareUsersByName(User x, User y) { return StringComparer.OrdinalIgnoreCase.Compare(x.UserName, y.UserName); }
/// <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> /// Overload for IApiHandler interface /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { this.Process(uri, processor, user, "Invalid API call"); }
/// <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 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> /// 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 records play stats for artists, albums, songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Ensure an event is present if (!uri.Parameters.ContainsKey("event")) { processor.WriteJson(new StatsResponse("Please specify an event parameter with comma separated list of events")); return; } // Split events into id, stat type, UNIX timestamp triples string[] events = uri.Parameters["event"].Split(','); // Ensure data sent if (events.Length < 1) { // Report empty data list processor.WriteJson(new StatsResponse("Event data list is empty")); return; } // Ensure events are in triples if (events.Length % 3 != 0) { processor.WriteJson(new StatsResponse("Event data list must be in comma-separated triples of form itemId,statType,timestamp")); return; } // Iterate all events for (int i = 0; i <= events.Length - 3; i += 3) { // Store item ID, stat type, and UNIX timestamp string itemId = events[i]; string statType = events[i+1]; string timeStamp = events[i+2]; // Initialize to null defaults int itemIdInt = -1; StatType statTypeEnum = StatType.Unknown; long timeStampLong = -1; // Perform three checks for valid item ID, stat type, UNIX timestamp bool success = Int32.TryParse(itemId, out itemIdInt); if (success) { success = Enum.TryParse<StatType>(statType, true, out statTypeEnum); } if (success) { success = Int64.TryParse(timeStamp, out timeStampLong); } if (success) { // If all three are successful, generate an item type from the ID ItemType itemType = Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId(itemIdInt); // Case: type is song, stat is playcount if ((itemType == ItemType.Song) && (statTypeEnum == StatType.PLAYED)) { // Also record a play for the artist, album, and folder Song song = Injection.Kernel.Get<ISongRepository>().SongForId(itemIdInt); // Trigger now playing service if available NowPlayingService nowPlaying = (NowPlayingService)ServiceManager.GetInstance("nowplaying"); if (nowPlaying != null) { nowPlaying.Register(user, song, timeStampLong); } if ((object)song.AlbumId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.AlbumId, statTypeEnum, timeStampLong); } if ((object)song.ArtistId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.ArtistId, statTypeEnum, timeStampLong); } if ((object)song.FolderId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.FolderId, statTypeEnum, timeStampLong); } } // Record stats for the generic item Injection.Kernel.Get<IStatRepository>().RecordStat(itemIdInt, statTypeEnum, timeStampLong); } } // After all stat iterations, return a successful response processor.WriteJson(new StatsResponse(null)); }
/// <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; }
public bool Register(User user, IMediaItem m, long? timestamp = null) { // Begin building object NowPlaying nowPlaying = new NowPlaying(); // Store user object nowPlaying.User = user; // Check if client sent a timestamp (if not, use current time) if (timestamp == null) { timestamp = DateTime.UtcNow.ToUnixTime(); } // Capture play time to set up automatic unregister on playback end nowPlaying.StartTime = timestamp; nowPlaying.EndTime = timestamp + Convert.ToInt32(m.Duration); // Start a timer, set to elapse and unregister this song exactly when it should finish playback nowPlaying.Timer = new Timer(Convert.ToInt32(m.Duration) * 1000); nowPlaying.Timer.Elapsed += delegate { this.Unregister(user.UserName, user.CurrentSession.ClientName); }; nowPlaying.Timer.Start(); // Capture media item's type Type mediaType = m.GetType(); // Unregister any items with matching user and client this.Unregister(user.UserName, user.CurrentSession.ClientName); // Handling for Song items if (mediaType.IsAssignableFrom(typeof(Song))) { // Box IMediaItem to Song Song s = (Song)m; nowPlaying.MediaItem = s; // Report now playing playing.Add(nowPlaying); logger.IfInfo(String.Format("{0}@{1} Now Playing: {2} - {3} - {4} [{5}]", user.UserName, user.CurrentSession.ClientName, s.ArtistName, s.AlbumName, s.SongName, Convert.ToInt32(s.Duration).ToTimeString() )); } // Handling for Video items else if (mediaType.IsAssignableFrom(typeof(Video))) { // Box IMediaItem to Video Video v = (Video)m; nowPlaying.MediaItem = v; // Report now playing playing.Add(nowPlaying); logger.IfInfo(String.Format("{0}@{1} Now Watching: {2} [{3}]", user.UserName, user.CurrentSession.ClientName, v.FileName, Convert.ToInt32(v.Duration).ToTimeString() )); } else { // Report unsupported media types logger.IfInfo("Media type not supported, skipping now playing registration..."); } return true; }
/// <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)); }