PageRenderTime 30ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Chavah/Controllers/SongsController.cs

http://chavah.codeplex.com
C# | 882 lines | 773 code | 87 blank | 22 comment | 85 complexity | eff1dedf3db34bd9f712e5059cdd7e18 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Runtime.Serialization;
  5. using System.ServiceModel;
  6. using System.ServiceModel.Web;
  7. using System.Text;
  8. using System.IO;
  9. using System.Timers;
  10. using System.Collections.Concurrent;
  11. using System.Reactive.Linq;
  12. using System.Threading.Tasks;
  13. using System.ServiceModel.Syndication;
  14. using Chavah.Common;
  15. using System.Web.Mvc;
  16. using Chavah.Data;
  17. using Chavah.Services;
  18. using System.Web;
  19. using System.Globalization;
  20. using Raven.Client;
  21. using Raven.Client.Linq;
  22. using Chavah.Models;
  23. using SongLike = Chavah.Models.Like;
  24. namespace Chavah.Controllers
  25. {
  26. public class SongsController : Controller
  27. {
  28. private static readonly Random random = new Random();
  29. private static readonly Lazy<List<SongInfo>> cachedSongs = new Lazy<List<SongInfo>>(() => GetCachedSongsWithLogging(), isThreadSafe: true);
  30. private static readonly ConcurrentDictionary<string, DateTime> visits = new ConcurrentDictionary<string, DateTime>();
  31. private readonly IDocumentStore raven = RavenStore.Db;
  32. public SongsController()
  33. {
  34. }
  35. static SongsController()
  36. {
  37. Task.Factory
  38. .StartNew(UpdateSongPurchaseInfo)
  39. .ContinueWith(_ => ClearOutOldLogs());
  40. }
  41. public JsonResult GetPeopleOnline(int minutes)
  42. {
  43. return Json(new
  44. {
  45. TotalSinceStart = visits.Count,
  46. PeopleOnline = "In the last " + minutes.ToString() + " minutes, " + visits.Values.Count(v => v >= DateTime.Now.Subtract(TimeSpan.FromMinutes(minutes))) + " people have used Chavah."
  47. }, JsonRequestBehavior.AllowGet);
  48. }
  49. public ActionResult ActivityFeed()
  50. {
  51. using (var session = raven.OpenSession())
  52. {
  53. var recentActivities = session
  54. .Query<Activity>()
  55. .OrderByDescending(a => a.DateTime)
  56. .Take(30);
  57. var feedItems = from activity in recentActivities.ToArray()
  58. select new SyndicationItem(
  59. title: activity.Title,
  60. content: activity.Description,
  61. itemAlternateLink: activity.MoreInfoUri);
  62. var feed = new SyndicationFeed("Chavah Messianic Radio", "The latest activity over at Chavah Messianic Radio", new Uri("http://messianicradio.com"), feedItems) { Language = "en-US" };
  63. return new RssActionResult { Feed = feed };
  64. }
  65. }
  66. private static string GetArtistTwitterHandle(string artist)
  67. {
  68. return Match.Value(artist)
  69. .With("Asharyahuw", "@Asharyahuw")
  70. .With("Aviad Cohen", "@aviadcohen")
  71. .With("Barry & Batya Segal", "@VisionForIsrael")
  72. .With("Carlos Perdomo", "@7ElCantante7")
  73. .With("Deborah Kline-Iantorno", "@deborahkline")
  74. .With("Devora Clark", "@devoraclark")
  75. .With("Downpour", "@downpourband1")
  76. .With("Frederique Vervoitte", "@fredouvervoitte")
  77. .With("Ephraim Ben Yoseph", "@EphraimbYoseph")
  78. .With("Giselle", "@GiselleTka")
  79. .With("Greg Silverman", "@GSilverPraise")
  80. .With("Hillel Ben Yochanan", "@Hillel_Yochanan")
  81. .With("Jonathan Lane", "@doyouknowyeshua")
  82. .With("Jonathan Settel", "@JSettel")
  83. .With("Joshua Rosen", "@joshuajrosen")
  84. .With("Lev Shelo", "@corrybell")
  85. .With("Liberated Wailing Wall", "@jfjlww")
  86. .With("Lynne McDowell", "@Lynne_McDowell")
  87. .With("Maurice Sklar", "@mauricesklar")
  88. .With("Maurice Sklar & Hugh Sung", "@mauricesklar")
  89. .With("Magen David", "@MagenDavidMG")
  90. .With("Micha'el Eliyahu BenDavid", "@MalachHaBrit")
  91. .With("Philip Stanley Klein", "@minorkey1")
  92. .With("Ross", "@ROSS_AandS")
  93. .With("Sharon Wilbur", "@sharonwilbur")
  94. .With("The Hebraism Music Project", "@Hebraism")
  95. .With("The Lumbrosos", "@TheLumbrosos")
  96. .With("Will Spires", "@wpspires")
  97. .With("Yerubilee", "@yerubilee")
  98. .With("Zemer Levav", "@zemerlevav")
  99. .DefaultTo(null);
  100. }
  101. public ActionResult GetAllSongs()
  102. {
  103. var result = cachedSongs
  104. .Value
  105. .OrderBy(s => s.Artist)
  106. .ThenBy(s => s.Album)
  107. .ThenBy(s => s.Number)
  108. .Select(s => s.ToDto(SongLikeStatus.None));
  109. return Json(result, JsonRequestBehavior.AllowGet);
  110. }
  111. public ActionResult GetAlbumArt(long songId)
  112. {
  113. var cachedResult = GetCachedContentResultOrNull(TimeSpan.FromDays(30));
  114. if (cachedResult != null)
  115. {
  116. return cachedResult;
  117. }
  118. else
  119. {
  120. var song = cachedSongs.Value.FirstOrDefault(s => s.Id == songId);
  121. if (song == null)
  122. {
  123. throw new HttpException(404, "Couldn't find the song with ID " + songId.ToString());
  124. }
  125. var albumArtFilePath = song.GetAlbumArtFilePath();
  126. var contentType = Match.Value(albumArtFilePath).With(".png", "image/png").DefaultTo("image/jpeg");
  127. return File(albumArtFilePath, contentType);
  128. }
  129. }
  130. private ContentResult GetCachedContentResultOrNull(TimeSpan cacheTime)
  131. {
  132. var modifiedSinceHeader = Request.Headers["If-Modified-Since"];
  133. if (modifiedSinceHeader.Exists())
  134. {
  135. var lastMod = DateTime.ParseExact(modifiedSinceHeader, "r", CultureInfo.InvariantCulture);
  136. var expireTime = lastMod.Add(cacheTime);
  137. if (expireTime > DateTime.Now)
  138. {
  139. Response.StatusCode = 304;
  140. Response.StatusDescription = "Not Modified";
  141. return Content(String.Empty);
  142. }
  143. }
  144. Response.Cache.SetCacheability(HttpCacheability.Public);
  145. Response.Cache.SetLastModified(DateTime.Now);
  146. return null;
  147. }
  148. public ActionResult GetTotalPlays()
  149. {
  150. using (var session = raven.OpenSession())
  151. {
  152. //var result = session.Query<Models.User>().Sum(u => u.TotalPlays);
  153. return Json(0, JsonRequestBehavior.AllowGet);
  154. }
  155. }
  156. public ActionResult GetTrendingSongs(int count)
  157. {
  158. using (var session = raven.OpenSession())
  159. {
  160. var trendingSongs = session
  161. .Query<SongLike>()
  162. .Where(l => l.LikeStatus == true)
  163. .OrderByDescending(l => l.Date)
  164. .Take(count * 2) // Count * 2, so that we can .Distinct on the in-memory stuff and still get back the requested number of elements.
  165. .Select(l => l.SongId)
  166. .AsEnumerable()
  167. .Distinct()
  168. .Take(count)
  169. .Select(id => cachedSongs.Value.First(s => s.Id == id).ToDto(SongLikeStatus.None));
  170. return Json(trendingSongs.ToArray(), JsonRequestBehavior.AllowGet);
  171. }
  172. }
  173. [NoCaching]
  174. public ActionResult GetTopSongs(int count)
  175. {
  176. var topSongCount = 25;
  177. var maxPlace = topSongCount - Math.Min(count, topSongCount);
  178. var randomPlace = random.Next(0, maxPlace);
  179. var result = cachedSongs.Value
  180. .OrderByDescending(s => s.CommunityRank)
  181. .Skip(randomPlace)
  182. .Take(count)
  183. .Select(s => s.ToDto(SongLikeStatus.None));
  184. return Json(result, JsonRequestBehavior.AllowGet);
  185. }
  186. public ActionResult GetSongMatches(string searchText)
  187. {
  188. var stringMatches = new Func<string, string, bool>((s1, s2) => string.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase));
  189. var isHeavenlySeventy = new Func<string, bool>(s => stringMatches(s, "heavenly seventy") || stringMatches(s, "heavenly 70"));
  190. var isBowelyBottom = new Func<string, bool>(s => stringMatches(s, "bowely bottom"));
  191. var isLucasLovelyList = new Func<string, bool>(s => stringMatches(s, "lucas lovely list") || stringMatches(s, "lucas' lovely list"));
  192. var songMatches = Match.Value(searchText)
  193. .With(isHeavenlySeventy, _ => GetHeavenlySeventy())
  194. .With(isBowelyBottom, _ => GetBowelyBottom())
  195. .With(isLucasLovelyList, _ => GetLucasLovelyList())
  196. .With(_ => true, s => GetSongMatchingText(s));
  197. return Json(songMatches.Evaluate(), JsonRequestBehavior.AllowGet);
  198. }
  199. private IEnumerable<Song> GetLucasLovelyList()
  200. {
  201. var lucasLovelyListIds = new long[] { 1179, 359, 573, 1176, 2283, 518, 577, 350, 572, 2513, 667, 2068, 536, 1181, 382, 648, 2279, 324, 2209, 1826, 1913, 2053, 649, 568, 505, 357, 2169, 495, 1824, 335, 483, 97, 723, 1185, 2300, 1282, 1079, 149, 273, 1863, 401, 363, 1969, 241, 162, 513, 1159, 410, 330, 570, 168, 524, 1515, 1945, 633, 2350, 774, 82, 1972, 721, 134, 1283, 1350, 1970, 618, 343, 2341, 444, 2170, 728, 76, 527, 1178, 535, 74, 445, 2302, 553, 1, 277, 1952, 364, 1765, 1976, 346, 1946, 2204, 249, 236, 621, 2100, 1977, 422, 154, 411, 458, 459, 2510 };
  202. return lucasLovelyListIds
  203. .Join(cachedSongs.Value, l => l, s => s.Id, (i, s) => s)
  204. .WhereNotNull()
  205. .Select(s => s.ToDto(SongLikeStatus.None));
  206. }
  207. public IEnumerable<Song> GetSongMatchingText(string searchText)
  208. {
  209. if (searchText.Length <= 2)
  210. {
  211. return Enumerable.Empty<Song>();
  212. }
  213. var allSongs = cachedSongs.Value;
  214. var matchingSongNames = allSongs.Where(s => s.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase));
  215. var matchingArtists = allSongs.Where(s => s.Artist.Contains(searchText, StringComparison.OrdinalIgnoreCase));
  216. var matchingAlbums = allSongs.Where(s => s.Album.Contains(searchText, StringComparison.OrdinalIgnoreCase));
  217. return matchingSongNames
  218. .Concat(matchingArtists)
  219. .Concat(matchingAlbums)
  220. .Take(25)
  221. .Select(s => s.ToDto(SongLikeStatus.None));
  222. }
  223. private IEnumerable<Song> GetBowelyBottom()
  224. {
  225. return cachedSongs.Value
  226. .OrderBy(s => s.CommunityRank)
  227. .Take(70)
  228. .AsEnumerable()
  229. .Select(s => s.ToDto(SongLikeStatus.None));
  230. }
  231. public IEnumerable<Song> GetHeavenlySeventy()
  232. {
  233. return cachedSongs.Value
  234. .OrderByDescending(s => s.CommunityRank)
  235. .Take(70)
  236. .AsEnumerable()
  237. .Select(s => s.ToDto(SongLikeStatus.None));
  238. }
  239. public ActionResult GetRandomLikedSongs(int count)
  240. {
  241. var likedSongs = from like in Dependency.Get<LikesCache>()
  242. .ForClient(GetUserIdFromRequest())
  243. .Where(l => l.LikeStatus == true)
  244. .ToList()
  245. .RandomOrder()
  246. let song = cachedSongs.Value.FirstOrDefault(s => s.Id == like.SongId)
  247. where song != null
  248. select song.ToDto(SongLikeStatus.Like);
  249. return Json(likedSongs.Take(count), JsonRequestBehavior.AllowGet);
  250. }
  251. [NoCaching]
  252. public ActionResult GetSongForClient()
  253. {
  254. try
  255. {
  256. var userId = GetUserIdFromRequest();
  257. OnSongPlayedForClient(userId);
  258. var song = GetSongForClientWithLikeWeights(userId);
  259. return Json(song, JsonRequestBehavior.AllowGet);
  260. }
  261. catch (Exception error)
  262. {
  263. ChavahEntities.LogInNewContext(error.ToString());
  264. return Json(Song.GetErrorSong(error.ToString()), JsonRequestBehavior.AllowGet);
  265. }
  266. }
  267. public ActionResult GetSongById(long songId)
  268. {
  269. var userId = GetUserIdFromRequest();
  270. OnSongPlayedForClient(userId);
  271. using (var entities = new ChavahEntities())
  272. {
  273. var song = entities.SongInfoes.FirstOrDefault(s => s.Id == songId);
  274. if (song != null)
  275. {
  276. var like = Dependency.Get<LikesCache>()
  277. .ForClient(userId)
  278. .FirstOrDefault(l => l.SongId == songId);
  279. var result = song.ToDto(like.ToSongLikeEnum());
  280. return Json(result, JsonRequestBehavior.AllowGet);
  281. }
  282. // This should never happen: a client requets a song ID that doesn't exist.
  283. var errorMessage = "Unable to find song with ID = " + songId.ToString();
  284. entities.Log(errorMessage);
  285. throw new Exception(errorMessage);
  286. }
  287. }
  288. public ActionResult GetRequestedSongId()
  289. {
  290. using (var session = raven.OpenSession())
  291. {
  292. var userId = GetUserIdFromRequest();
  293. var user = session.Load<User>(userId);
  294. if (user != null)
  295. {
  296. var userDislikes = Dependency.Get<LikesCache>().ForClient(userId).Where(l => l.LikeStatus == false);
  297. var halfHourAgo = DateTime.Now.Subtract(TimeSpan.FromMinutes(30));
  298. var songRequest = session
  299. .Query<SongRequest>()
  300. .OrderByDescending(s => s.DateTime)
  301. .Where(s => s.DateTime >= halfHourAgo && s.UserWhoMadeRequestId != user.Id)
  302. .Take(30)
  303. .ToArray()
  304. .Where(s => !s.PlayedForClientIds.Contains(user.Id, StringComparer.OrdinalIgnoreCase))
  305. .Where(s => userDislikes.All(d => d.SongId != s.SongId))
  306. .FirstOrDefault();
  307. if (songRequest != null)
  308. {
  309. songRequest.PlayedForClientIds.Add(user.Id);
  310. session.SaveChanges();
  311. return Json(songRequest.SongId, JsonRequestBehavior.AllowGet);
  312. }
  313. }
  314. return Json(null, JsonRequestBehavior.AllowGet);
  315. }
  316. }
  317. public ActionResult GetIndividualSongRanks(int songId)
  318. {
  319. using (var session = raven.OpenSession())
  320. {
  321. var upVoteCount = session.Query<SongLike>().Count(l => l.SongId == songId && l.LikeStatus == true);
  322. var downVoteCount = session.Query<SongLike>().Count(l => l.SongId == songId && l.LikeStatus == false);
  323. var result = new
  324. {
  325. UpVotes = upVoteCount,
  326. DownVotes = downVoteCount,
  327. SongId = songId
  328. };
  329. return Json(result, JsonRequestBehavior.AllowGet);
  330. }
  331. }
  332. public ActionResult RequestSong(long songId)
  333. {
  334. using (var session = raven.OpenSession())
  335. {
  336. var user = session.Load<User>(this.GetUserIdFromRequest());
  337. var song = cachedSongs.Value.FirstOrDefault(s => s.Id == songId);
  338. if (song != null && user != null)
  339. {
  340. var requestExpiration = DateTime.UtcNow.AddDays(14);
  341. if (!HasRecentPendingSongRequest(songId, session) && !HasManyPendingSongRequestForArtist(song.Artist, session))
  342. {
  343. var songRequest = new SongRequest
  344. {
  345. DateTime = DateTime.Now,
  346. PlayedForClientIds = new List<string> { user.Id },
  347. SongId = songId,
  348. Artist = song.Artist,
  349. UserWhoMadeRequestId = user.Id
  350. };
  351. session.Store(songRequest);
  352. session.AddRavenExpiration(songRequest, requestExpiration);
  353. }
  354. var songArtist = Match.Value(GetArtistTwitterHandle(song.Artist))
  355. .IfNotNull(h => h)
  356. .DefaultTo(song.Artist)
  357. .Evaluate();
  358. var activity = new Activity
  359. {
  360. DateTime = DateTime.Now,
  361. Title = string.Format("{0} - {1} was requested by one of our listeners", song.Artist, song.Name),
  362. Description = string.Format("\"{0}\" by {1} was requested by one of our listeners on Chavah Messianic Radio.", song.Name, songArtist),
  363. MoreInfoUri = song.GetAbsoluteSongUri()
  364. };
  365. session.Store(activity);
  366. session.AddRavenExpiration(activity, requestExpiration);
  367. session.SaveChanges();
  368. }
  369. }
  370. return GetSongById(songId);
  371. }
  372. private bool HasRecentPendingSongRequest(long songId, IDocumentSession session)
  373. {
  374. var recent = DateTime.Now.Subtract(TimeSpan.FromMinutes(30));
  375. return session
  376. .Query<SongRequest>()
  377. .Any(s => s.SongId == songId && s.DateTime >= recent);
  378. }
  379. private bool HasManyPendingSongRequestForArtist(string artist, IDocumentSession session)
  380. {
  381. var recent = DateTime.Now.Subtract(TimeSpan.FromMinutes(60));
  382. var many = 2;
  383. return session
  384. .Query<SongRequest>()
  385. .Count(s => s.Artist == artist && s.DateTime >= recent) >= many;
  386. }
  387. private void OnSongPlayedForClient(string userId)
  388. {
  389. Task.Factory.StartNew(() => RecordSongPlayedForUser(userId));
  390. }
  391. private Song GetSongForClientWithLikeWeights(string userId)
  392. {
  393. // Song weights algorithm described here:
  394. // http://stackoverflow.com/questions/3345788/algorithm-for-picking-thumbed-up-items/3345838#3345838
  395. var allSongs = cachedSongs.Value;
  396. var likeDislikeSongs = Dependency.Get<LikesCache>().ForClient(userId);
  397. var songsWithWeight =
  398. (
  399. from song in allSongs
  400. let likeStatus = GetLikeStatusForSong(song, likeDislikeSongs)
  401. select new
  402. {
  403. Weight = GetSongWeight(song, likeStatus),
  404. Like = likeStatus,
  405. Info = song
  406. }
  407. ).ToArray();
  408. var totalWeights = songsWithWeight.Sum(s => s.Weight);
  409. var randomWeight = RandomDoubleWithMaxValue(totalWeights);
  410. var runningWeight = 0.0;
  411. foreach (var song in songsWithWeight)
  412. {
  413. var newWeight = runningWeight + song.Weight;
  414. if (randomWeight >= runningWeight && randomWeight <= newWeight)
  415. {
  416. return song.Info.ToDto(song.Like);
  417. }
  418. runningWeight = newWeight;
  419. }
  420. var errorMessage = "Unable to find random song. This should never happen. Random weight chosen was " + randomWeight.ToString() + ", max weight was " + totalWeights.ToString();
  421. ChavahEntities.LogInNewContext(errorMessage);
  422. throw new Exception(errorMessage);
  423. }
  424. private string GetUserIdFromRequest()
  425. {
  426. var userId = Match.Value(Request.Cookies["userIdValue"])
  427. .IfNotNull(c => c.Value)
  428. .Evaluate();
  429. if (string.IsNullOrEmpty(userId))
  430. {
  431. throw new InvalidOperationException("Doesn't have a user Id");
  432. }
  433. return userId;
  434. }
  435. private static double GetSongWeight(SongInfo song, SongLikeStatus likeStatus)
  436. {
  437. var likeWeightMultiplier = GetSongLikeWeightMultiplier(likeStatus);
  438. var shabbatWeight = GetShabbatWeight(song, likeStatus);
  439. var popularityWeight = GetPopularityWeight(song);
  440. var proposedFinalWeight = (shabbatWeight + popularityWeight) * likeWeightMultiplier;
  441. return proposedFinalWeight.MinMax(.001, 10);
  442. }
  443. private static double GetSongLikeWeightMultiplier(SongLikeStatus likeStatus)
  444. {
  445. const double normalMultiplier = 1;
  446. const double likeMultiplier = 1.5;
  447. const double dislikeMultiplier = 0.01;
  448. return Match.Value(likeStatus)
  449. .With(SongLikeStatus.Like, likeMultiplier)
  450. .With(SongLikeStatus.Dislike, dislikeMultiplier)
  451. .DefaultTo(normalMultiplier);
  452. }
  453. private static double GetPopularityWeight(SongInfo song)
  454. {
  455. const double veryUnpopularWeight = 0.01;
  456. const double unpopularWeight = 0.07;
  457. const double normalWeight = 1;
  458. const double likedWeight = 1.45;
  459. const double popularWeight = 1.65;
  460. const double veryPopularWeight = 2.05;
  461. const double extremelyPopularWeight = 2.2;
  462. return Match.Value(song.CommunityRank)
  463. .With(v => v < -5, veryUnpopularWeight)
  464. .With((-4).Through(-1), unpopularWeight)
  465. .With(0.Through(9), normalWeight)
  466. .With(10.Through(29), likedWeight)
  467. .With(30.Through(49), popularWeight)
  468. .With(50.Through(100), veryPopularWeight)
  469. .With(v => v >= 101, extremelyPopularWeight)
  470. .DefaultTo(normalWeight);
  471. }
  472. private static double GetShabbatWeight(SongInfo song, SongLikeStatus likeStatus)
  473. {
  474. const int shabbatBoost = 7;
  475. return Match.Value(song)
  476. .With(_ => likeStatus == SongLikeStatus.Dislike || song.CommunityRank < -3, 0)
  477. .With(s => DateTime.Now.IsShabbat() && s.IsShabbatSong, shabbatBoost);
  478. }
  479. private static double RandomDoubleWithMaxValue(double maxValueInclusive)
  480. {
  481. var randomValue = random.NextDouble();
  482. var desiredValue = randomValue * maxValueInclusive;
  483. var desiredValueTrimmed = Math.Min(maxValueInclusive, desiredValue);
  484. return desiredValueTrimmed;
  485. }
  486. private SongLikeStatus GetLikeStatusForSong(SongInfo song, SongLike[] userSongPreferences)
  487. {
  488. var likeDislikeForThisSong = userSongPreferences.FirstOrDefault(l => l.SongId == song.Id);
  489. return likeDislikeForThisSong.ToSongLikeEnum();
  490. }
  491. private void RecordSongPlayedForUser(string userId)
  492. {
  493. visits.AddOrUpdate(userId, DateTime.Now, (_, __) => DateTime.Now);
  494. using (var session = raven.OpenSession())
  495. {
  496. var user = session.Load<User>(userId);
  497. if (user != null)
  498. {
  499. user.TotalPlays += 1;
  500. user.LastVisit = DateTime.Now.Date;
  501. }
  502. session.SaveChanges();
  503. }
  504. }
  505. [HttpPost]
  506. public ActionResult LikeById(long songId)
  507. {
  508. UpdateLikeStatus(this.GetUserIdFromRequest(), songId, SongLikeStatus.Like);
  509. StoreLikeActivity(songId);
  510. return Json(true);
  511. }
  512. [HttpPost]
  513. public ActionResult DislikeById(long songId)
  514. {
  515. UpdateLikeStatus(this.GetUserIdFromRequest(), songId, SongLikeStatus.Dislike);
  516. return Json(true);
  517. }
  518. public ActionResult GetSongByAlbum(string album, string artist)
  519. {
  520. var albumSongs = cachedSongs.Value.Where(s => string.Equals(s.Album, album, StringComparison.OrdinalIgnoreCase));
  521. var song = albumSongs.RandomOrder().FirstOrDefault();
  522. if (song != null)
  523. {
  524. return GetSongById(song.Id);
  525. }
  526. else
  527. {
  528. ChavahEntities.LogInNewContext("Unable to find an album matching name " + album);
  529. return GetSongForClient();
  530. }
  531. }
  532. public ActionResult GetSongByArtist(string artist)
  533. {
  534. var artistSongs = cachedSongs.Value.Where(s => string.Equals(s.Artist, artist, StringComparison.OrdinalIgnoreCase));
  535. var song = artistSongs.RandomOrder().FirstOrDefault();
  536. if (song != null)
  537. {
  538. return GetSongById(song.Id);
  539. }
  540. else
  541. {
  542. ChavahEntities.LogInNewContext("Unable to find a song name starting with " + artist);
  543. return GetSongForClient();
  544. }
  545. }
  546. private static List<SongInfo> GetCachedSongsWithLogging()
  547. {
  548. try
  549. {
  550. return GetCachedSongs();
  551. }
  552. catch (Exception error)
  553. {
  554. using (var entities = new ChavahEntities())
  555. {
  556. entities.Logs.AddObject(new Chavah.Data.Log() { Message = error.ToString(), TimeStamp = DateTime.Now });
  557. entities.SaveChanges();
  558. }
  559. throw;
  560. }
  561. }
  562. static void UpdateSongPurchaseInfo()
  563. {
  564. using (var entities = new ChavahEntities())
  565. {
  566. var knownPurchaseLinks = new Dictionary<string, string>()
  567. {
  568. { "Aviad Cohen", "http://aviadcohen.com/shop.cfm" },
  569. { "Alicia Smith", "http://www.cduniverse.com/productinfo.asp?pid=7345816" },
  570. { "Alyssa Kennedy", "http://www.youtube.com/watch?v=lSSXeuXvYWU" },
  571. { "Avner & Rachel Boskey", "http://www.davidstent.org" },
  572. { "Baruch HaShem Worship Team", "http://baruchhashemsynagogue.org/" },
  573. { "Barry & Batya Segal", "http://www.visionforisrael.com" },
  574. { "Bruce & Lynne Patterson", "http://www.ldpatterson.com" },
  575. { "Bruce Cohen", "http://www.bethelnyc.org/meet_the_rabbi/r'bruce_products.htm" },
  576. { "Carlos Perdomo", "http://www.carlosperdomoministries.com/" },
  577. { "Carolyn Hyde", "http://www.heartofg-d.org" },
  578. { "Christene Jackman", "http://christenejackman.com" },
  579. { "Christopher Mann", "http://www.kadoshmann.com" },
  580. { "Deborah Kline-Iantorno", "http://deborahkline-iantorno.com" },
  581. { "Devora Clark", "http://devoraclark.bandcamp.com/" },
  582. { "Deanne Glenn", "http://www.deannemusic.com/" },
  583. { "Deanne Shallenberger", "http://www.deannemusic.com/" },
  584. { "Downpour", "http://www.thedownpourband.com/" },
  585. { "Elisheva Shomron", "http://www.last.fm/music/Elisheva+Shomron" },
  586. { "Ephraim Ben Yoseph", "http://www.reverbnation.com/ephraimbenyoseph" },
  587. { "Giselle", "http://www.cdbaby.com/cd/giselle33" },
  588. { "Greg Silverman", "http://www.gregsilverman.com/" },
  589. { "Hananyah Naftali", "https://itunes.apple.com/il/album/your-presence/id680795419?i=680795748" },
  590. { "Helen Shapiro", "http://www.mannamusic.co.uk/materialspage/materialspg.htm" },
  591. { "Hillel Ben Yochanan", "http://www.facebook.com/hillel70#!/pages/Hillel/214029558644294?sk=app_178091127385" },
  592. { "Israel's Hope", "http://www.fvgifts.com/music.html" },
  593. { "Joel Chernoff", "http://www.lambmessianicmusic.com/lamb_05_joelchernoff_mn.html" },
  594. { "Jonathan Kegans", "http://www.jonathankegans.com/index.html" },
  595. { "Jonathan Settel", "http://www.settel.org" },
  596. { "Joshua Aaron", "http://worshipinisrael.com/" },
  597. { "Joshua Rosen", "http://joshuarosen.bandcamp.com" },
  598. { "Justin Black", "http://www.facebook.com/profile.php?id=100000597792846#!/profile.php?id=100002453000204" },
  599. { "Karen Davis", "http://www.messianicweb.com/Music/GOTN/Davis/" },
  600. { "Kathy Shooster", "http://kathyshoostermusic.com/?page_id=10" },
  601. { "Kehilat Ha Ma'ayan Congregation", "http://kehilat-hamaayan.org.il/" },
  602. { "Lamb", "http://www.lambmessianicmusic.com/lamb_04_lamb_mn.html" },
  603. { "Lee Rothman", "http://www.purevolume.com/hisway" },
  604. { "Lenny & Varda Harris", "http://www.lennyandvarda.com/" },
  605. { "Leslie Ann", "http://www.songsofleslieann.com/" },
  606. { "Lev Shelo", "http://levshelo.com/store.cfm" },
  607. { "Lynne McDowell", "http://lynnemcdowell.com/" },
  608. { "Magen David", "http://www.cdbaby.com/cd/magendavid" },
  609. { "Martin Sarvis", "http://www.cdbaby.com/cd/martinsarvis" },
  610. { "Marty Goetz", "http://www.martygoetz.com/products/products.php" },
  611. { "Micha'el Eliyahu BenDavid", "http://emetzionmusic.com/index.php?option=com_maianmedia&view=music&Itemid=84" },
  612. { "Michael Nissim", "http://www.fvgifts.com/music.html" },
  613. { "Misha Goetz", "http://mishagoetz.com/" },
  614. { "Mishkanim", "http://mishkanim.com/" },
  615. { "Meha Shamayim", "http://www.galileeofthenations.com/" },
  616. { "Mijael Hayom", "http://www.librerialosolivos.com/index.php?cPath=615_70_267" },
  617. { "Natalie Isaacs", "http://natalieisaacs.com" },
  618. { "Natasha Kraus-Reynolds", "http://www.facebook.com/pages/Natasha-Kraus-Reynolds/199220550117812" },
  619. { "New Wine", "http://messianic.beithassedel.org/music.htm" },
  620. { "Paul Wilbur", "https://wilburministries.com/" },
  621. { "Philip Stanley Klein", "http://www.yeshuasongs.com" },
  622. { "Roeh Israel Worship Team", "http://www.storesonline.com/site/634305/page/916905" },
  623. { "Roman and Alaina", "http://romanandalaina.com/" },
  624. { "Ross", "http://www.andrewandsarahross.com" },
  625. { "Sally Klein O'Connor", "http://www.sallykleinoconnor.com/" },
  626. { "Sha'rei HaShamayim", "http://www.hebraic.info/Hebraic/Music.html" },
  627. { "Sharon Wilbur", "http://sharonwilbur.com" },
  628. { "Sons of Korah", "http://www.sonsofkorah.com" },
  629. { "Will Spires", "http://www.myspace.com/WiLLiamSpires/" },
  630. { "Steve McConnell", "http://www.amazon.com/s/ref=pd_lpo_k2_dp_sr_sq_top?ie=UTF8&keywords=steve%20mcconnell%20messianic%20music&index=blended&pf_rd_p=486539851&pf_rd_s=lpo-top-stripe-1&pf_rd_t=201&pf_rd_i=B000CAG4US&pf_rd_m=ATVPDKIKX0DER&pf_rd_r=1F61C18YZ8YW8XH4D8RH" },
  631. { "Ted Pearce", "http://www.tedpearce.com/music/" },
  632. { "Tents of Mercy", "http://www.fvgifts.com/music.html" },
  633. { "The Hebraism Music Project", "http://www.hebraism.org/Hebraism/Home.html" },
  634. { "The Lumbrosos", "http://thelumbrosos.com/" },
  635. { "Troy Mitchell", "http://ffoz.com/troy-mitchell-yoke-of-the-king-music-audio-cd.html" }
  636. };
  637. entities.SongInfoes
  638. .AsEnumerable()
  639. .Where(s => knownPurchaseLinks.ContainsKey(s.Artist))
  640. .Where(s => s.PurchaseUrl != knownPurchaseLinks[s.Artist])
  641. .ForEach(s => s.PurchaseUrl = knownPurchaseLinks[s.Artist]);
  642. entities.SaveChanges();
  643. }
  644. }
  645. private static void ClearOutOldLogs()
  646. {
  647. using (var entities = new ChavahEntities())
  648. {
  649. var monthAgo = DateTime.Now.Subtract(TimeSpan.FromDays(30));
  650. entities
  651. .Logs
  652. .Where(l => l.TimeStamp < monthAgo)
  653. .ForEach(entities.Logs.DeleteObject);
  654. }
  655. }
  656. private static List<SongInfo> GetCachedSongs()
  657. {
  658. var songsOnDisk = Directory.EnumerateFiles(Constants.MessianicMusicPath, "*.mp3").Select(Path.GetFileName).Memoize();
  659. var cachedSongsList = new List<SongInfo>(1800);
  660. using (var entities = new ChavahEntities())
  661. {
  662. // Ensure they're all in the database.
  663. var songsInDatabase = entities.SongInfoes.ToArray();
  664. songsOnDisk
  665. .Where(fileName => !songsInDatabase.Any(s => string.Equals(s.FileName, fileName, StringComparison.InvariantCultureIgnoreCase)))
  666. .Select(fileName => CreateSongFromDisk(fileName))
  667. .Do(s => entities.Log("Adding song to DB: " + s.FileName))
  668. .Do(entities.SongInfoes.AddObject)
  669. .Concat(songsInDatabase)
  670. .ForEach(cachedSongsList.Add);
  671. // Remove from the database any that are missing on disk.
  672. if (songsOnDisk.Any())
  673. {
  674. var songsRemovedFromDisk =
  675. (
  676. from dbSong in songsInDatabase
  677. where !songsOnDisk.Any(s => dbSong.FileName == s)
  678. select dbSong
  679. ).ToArray();
  680. // If we're missing a bunch of songs, something is wrong, don't delete.
  681. if (songsRemovedFromDisk.Any() && songsRemovedFromDisk.Length < 100)
  682. {
  683. songsRemovedFromDisk
  684. .Do(entities.SongInfoes.DeleteObject)
  685. .Do(s => cachedSongsList.Remove(s))
  686. .ForEach(s => entities.Log("Removing song from DB: " + s.FileName));
  687. }
  688. }
  689. entities.SaveChanges();
  690. cachedSongsList.ForEach(entities.SongInfoes.Detach);
  691. }
  692. return cachedSongsList;
  693. }
  694. private static SongInfo CreateSongFromDisk(string fileName)
  695. {
  696. var song = new SongInfo { FileName = fileName };
  697. song.FillSongDetailsFromFileName(fileName);
  698. return song;
  699. }
  700. private void StoreLikeActivity(long songId)
  701. {
  702. using (var session = raven.OpenSession())
  703. {
  704. var song = cachedSongs.Value.FirstOrDefault(s => s.Id == songId);
  705. if (song != null)
  706. {
  707. var songRankString = Match
  708. .Value(song.CommunityRank)
  709. .With(i => i > 0, "+")
  710. .DefaultTo("")
  711. .Evaluate() + song.CommunityRank.ToString();
  712. var songArtist = Match.Value(GetArtistTwitterHandle(song.Artist))
  713. .IfNotNull(h => h)
  714. .DefaultTo(song.Artist)
  715. .Evaluate();
  716. var activity = new Activity
  717. {
  718. DateTime = DateTime.Now,
  719. Title = string.Format("{0} - {1} was thumbed up", song.Artist, song.Name),
  720. Description = string.Format("\"{0}\" by {1} was thumbed up ({2}) on Chavah Messianic Radio.", song.Name, songArtist, songRankString),
  721. MoreInfoUri = song.GetAbsoluteSongUri()
  722. };
  723. session.Store(activity);
  724. session.AddRavenExpiration(activity, DateTime.UtcNow.AddDays(30));
  725. session.SaveChanges();
  726. }
  727. }
  728. }
  729. private static void UpdateLikeStatus(string userId, long songId, SongLikeStatus likeStatus)
  730. {
  731. var hasReversedLikeStatus = false;
  732. using (var session = RavenStore.Db.OpenSession())
  733. {
  734. var existingLike = session.Query<SongLike>().FirstOrDefault(l => l.SongId == songId && l.UserId == userId);
  735. if (existingLike != null)
  736. {
  737. if (existingLike.LikeStatus == likeStatus.ToBool())
  738. {
  739. // You already like/dislike this song. There's nothing to update.
  740. return;
  741. }
  742. hasReversedLikeStatus = true;
  743. existingLike.LikeStatus = likeStatus.ToBool();
  744. }
  745. else
  746. {
  747. var newLikeStatus = new SongLike
  748. {
  749. LikeStatus = likeStatus.ToBool(),
  750. SongId = (int)songId,
  751. UserId = userId,
  752. Date = DateTime.Now
  753. };
  754. session.Store(newLikeStatus);
  755. }
  756. session.SaveChanges();
  757. }
  758. // Update the community rank.
  759. using (var entities = new ChavahEntities())
  760. {
  761. var song = entities.SongInfoes.FirstOrDefault(s => s.Id == songId);
  762. if (song != null)
  763. {
  764. var adjustmentAmount = hasReversedLikeStatus ? 2 : 1;
  765. song.CommunityRank += likeStatus == SongLikeStatus.Like ? adjustmentAmount : (-1 * adjustmentAmount);
  766. }
  767. entities.SaveChanges();
  768. }
  769. Dependency.Get<LikesCache>().OnLikesChanged(userId);
  770. // Update the in-memory item.
  771. var adjustementAmount = Match.Value(likeStatus)
  772. .With(s => s == SongLikeStatus.Like && !hasReversedLikeStatus, 1)
  773. .With(s => s == SongLikeStatus.Like && hasReversedLikeStatus, 2)
  774. .With(s => s == SongLikeStatus.Dislike && !hasReversedLikeStatus, -1)
  775. .With(s => s == SongLikeStatus.Dislike && hasReversedLikeStatus, -2)
  776. .Evaluate();
  777. cachedSongs.Value
  778. .Where(s => s.Id == songId)
  779. .Take(1)
  780. .ForEach(s => s.CommunityRank += adjustementAmount);
  781. }
  782. //private static void UpdateLikeStatus(Guid clientId, Uri songUri, SongLike likeStatus)
  783. //{
  784. // var songName = songUri.Segments.Last().Replace("%20", " ");
  785. // using (var entities = new ChavahEntities())
  786. // {
  787. // var song = entities.SongInfoes.FirstOrDefault(s => s.FileName == songName);
  788. // if (song != null)
  789. // {
  790. // UpdateLikeStatus(clientId, song.Id, likeStatus);
  791. // }
  792. // }
  793. //}
  794. }
  795. }