The next generation of the Teknik Services. Written in ASP.NET. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PodcastController.cs 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.Extensions.Logging;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Threading.Tasks;
  11. using Teknik.Areas.Podcast.Models;
  12. using Teknik.Areas.Podcast.ViewModels;
  13. using Teknik.Areas.Users.Utility;
  14. using Teknik.Attributes;
  15. using Teknik.Configuration;
  16. using Teknik.Controllers;
  17. using Teknik.Data;
  18. using Teknik.Filters;
  19. using Teknik.Models;
  20. using Teknik.Utilities;
  21. using Teknik.Logging;
  22. namespace Teknik.Areas.Podcast.Controllers
  23. {
  24. [Authorize]
  25. [Area("Podcast")]
  26. public class PodcastController : DefaultController
  27. {
  28. public PodcastController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
  29. [AllowAnonymous]
  30. public IActionResult Index()
  31. {
  32. MainViewModel model = new MainViewModel();
  33. model.Title = _config.PodcastConfig.Title;
  34. model.Description = _config.PodcastConfig.Description;
  35. try
  36. {
  37. ViewBag.Title = _config.PodcastConfig.Title;
  38. ViewBag.Description = _config.PodcastConfig.Description;
  39. bool editor = User.IsInRole("Podcast");
  40. var foundPodcasts = _dbContext.Podcasts.Where(p => (p.Published || editor)).FirstOrDefault();
  41. if (foundPodcasts != null)
  42. {
  43. model.HasPodcasts = (foundPodcasts != null);
  44. }
  45. else
  46. {
  47. model.Error = true;
  48. model.ErrorMessage = "No Podcasts Available";
  49. }
  50. return View("~/Areas/Podcast/Views/Podcast/Main.cshtml", model);
  51. }
  52. catch (Exception ex)
  53. {
  54. model.Error = true;
  55. model.ErrorMessage = ex.Message;
  56. return View("~/Areas/Podcast/Views/Podcast/Main.cshtml", model);
  57. }
  58. }
  59. #region Podcasts
  60. [AllowAnonymous]
  61. public IActionResult View(int episode)
  62. {
  63. PodcastViewModel model = new PodcastViewModel();
  64. // find the podcast specified
  65. bool editor = User.IsInRole("Podcast");
  66. var foundPodcast = _dbContext.Podcasts.Where(p => ((p.Published || editor) && p.Episode == episode)).FirstOrDefault();
  67. if (foundPodcast != null)
  68. {
  69. model = new PodcastViewModel(foundPodcast);
  70. ViewBag.Title = model.Title + " | Teknikast";
  71. return View("~/Areas/Podcast/Views/Podcast/ViewPodcast.cshtml", model);
  72. }
  73. model.Error = true;
  74. model.ErrorMessage = "No Podcasts Available";
  75. return View("~/Areas/Podcast/Views/Podcast/ViewPodcast.cshtml", model);
  76. }
  77. [HttpGet]
  78. [AllowAnonymous]
  79. [ResponseCache(Duration = 31536000, Location = ResponseCacheLocation.Any)]
  80. public IActionResult Download(int episode, string fileName)
  81. {
  82. string path = string.Empty;
  83. string contentType = string.Empty;
  84. long contentLength = 0;
  85. DateTime dateUploaded = new DateTime(1900, 1, 1);
  86. // find the podcast specified
  87. var foundPodcast = _dbContext.Podcasts.Where(p => (p.Published && p.Episode == episode)).FirstOrDefault();
  88. if (foundPodcast != null)
  89. {
  90. PodcastFile file = foundPodcast.Files.Where(f => f.FileName == fileName).FirstOrDefault();
  91. if (file != null)
  92. {
  93. path = file.Path;
  94. contentType = file.ContentType;
  95. contentLength = file.ContentLength;
  96. fileName = file.FileName;
  97. dateUploaded = foundPodcast.DateEdited;
  98. }
  99. else
  100. {
  101. return new StatusCodeResult(StatusCodes.Status404NotFound);
  102. }
  103. }
  104. else
  105. {
  106. return new StatusCodeResult(StatusCodes.Status404NotFound);
  107. }
  108. if (System.IO.File.Exists(path))
  109. {
  110. // Are they downloading it by range?
  111. bool byRange = !string.IsNullOrEmpty(Request.Headers["Range"]); // We do not support ranges
  112. bool isCached = !string.IsNullOrEmpty(Request.Headers["If-Modified-Since"]); // Check to see if they have a cache
  113. if (isCached)
  114. {
  115. // The file is cached, let's just 304 this
  116. Response.StatusCode = 304;
  117. Response.Headers.Add("Content-Length", "0");
  118. return Content(string.Empty);
  119. }
  120. else
  121. {
  122. long startByte = 0;
  123. long endByte = contentLength - 1;
  124. long length = contentLength;
  125. #region Range Calculation
  126. // check to see if we need to pass a specified range
  127. if (byRange)
  128. {
  129. long anotherStart = startByte;
  130. long anotherEnd = endByte;
  131. string[] arr_split = Request.Headers["Range"].ToString().Split(new char[] { '=' });
  132. string range = arr_split[1];
  133. // Make sure the client hasn't sent us a multibyte range
  134. if (range.IndexOf(",") > -1)
  135. {
  136. // (?) Shoud this be issued here, or should the first
  137. // range be used? Or should the header be ignored and
  138. // we output the whole content?
  139. Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength);
  140. return new StatusCodeResult(StatusCodes.Status416RequestedRangeNotSatisfiable);
  141. }
  142. // If the range starts with an '-' we start from the beginning
  143. // If not, we forward the file pointer
  144. // And make sure to get the end byte if spesified
  145. if (range.StartsWith("-"))
  146. {
  147. // The n-number of the last bytes is requested
  148. anotherStart = startByte - Convert.ToInt64(range.Substring(1));
  149. }
  150. else
  151. {
  152. arr_split = range.Split(new char[] { '-' });
  153. anotherStart = Convert.ToInt64(arr_split[0]);
  154. long temp = 0;
  155. anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : contentLength;
  156. }
  157. /* Check the range and make sure it's treated according to the specs.
  158. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  159. */
  160. // End bytes can not be larger than $end.
  161. anotherEnd = (anotherEnd > endByte) ? endByte : anotherEnd;
  162. // Validate the requested range and return an error if it's not correct.
  163. if (anotherStart > anotherEnd || anotherStart > contentLength - 1 || anotherEnd >= contentLength)
  164. {
  165. Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength);
  166. return new StatusCodeResult(StatusCodes.Status416RequestedRangeNotSatisfiable);
  167. }
  168. startByte = anotherStart;
  169. endByte = anotherEnd;
  170. length = endByte - startByte + 1; // Calculate new content length
  171. // Ranges are response of 206
  172. Response.StatusCode = 206;
  173. }
  174. #endregion
  175. // We accept ranges
  176. Response.Headers.Add("Accept-Ranges", "0-" + contentLength);
  177. // Notify the client the byte range we'll be outputting
  178. Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength);
  179. // Notify the client the content length we'll be outputting
  180. Response.Headers.Add("Content-Length", length.ToString());
  181. var cd = new System.Net.Mime.ContentDisposition
  182. {
  183. FileName = fileName,
  184. Inline = true
  185. };
  186. Response.Headers.Add("Content-Disposition", cd.ToString());
  187. FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  188. // Reset file stream to starting position (or start of range)
  189. fileStream.Seek(startByte, SeekOrigin.Begin);
  190. return new BufferedFileStreamResult(contentType, (response) => ResponseHelper.StreamToOutput(response, true, fileStream, (int)length, 4 * 1024), false);
  191. }
  192. }
  193. return new StatusCodeResult(StatusCodes.Status404NotFound);
  194. }
  195. [HttpPost]
  196. [AllowAnonymous]
  197. public IActionResult GetPodcasts(int startPodcastID, int count)
  198. {
  199. bool editor = User.IsInRole("Podcast");
  200. var podcasts = _dbContext.Podcasts.Where(p => p.Published || editor).OrderByDescending(p => p.DatePosted).Skip(startPodcastID).Take(count).ToList();
  201. List<PodcastViewModel> podcastViews = new List<PodcastViewModel>();
  202. if (podcasts != null)
  203. {
  204. foreach (Models.Podcast podcast in podcasts)
  205. {
  206. podcastViews.Add(new PodcastViewModel(podcast));
  207. }
  208. }
  209. return PartialView("~/Areas/Podcast/Views/Podcast/Podcasts.cshtml", podcastViews);
  210. }
  211. [HttpPost]
  212. [AllowAnonymous]
  213. public IActionResult GetPodcastEpisode(int podcastId)
  214. {
  215. bool editor = User.IsInRole("Podcast");
  216. var foundPodcast = _dbContext.Podcasts.Where(p => ((p.Published || editor) && p.PodcastId == podcastId)).FirstOrDefault();
  217. if (foundPodcast != null)
  218. {
  219. return Json(new { result = foundPodcast.Episode });
  220. }
  221. return Json(new { error = "No podcast found" });
  222. }
  223. [HttpPost]
  224. [AllowAnonymous]
  225. public IActionResult GetPodcastTitle(int podcastId)
  226. {
  227. bool editor = User.IsInRole("Podcast");
  228. var foundPodcast = _dbContext.Podcasts.Where(p => ((p.Published || editor) && p.PodcastId == podcastId)).FirstOrDefault();
  229. if (foundPodcast != null)
  230. {
  231. return Json(new { result = foundPodcast.Title });
  232. }
  233. return Json(new { error = "No podcast found" });
  234. }
  235. [HttpPost]
  236. [AllowAnonymous]
  237. public IActionResult GetPodcastDescription(int podcastId)
  238. {
  239. bool editor = User.IsInRole("Podcast");
  240. var foundPodcast = _dbContext.Podcasts.Where(p => ((p.Published || editor) && p.PodcastId == podcastId)).FirstOrDefault();
  241. if (foundPodcast != null)
  242. {
  243. return Json(new { result = foundPodcast.Description });
  244. }
  245. return Json(new { error = "No podcast found" });
  246. }
  247. [HttpPost]
  248. [AllowAnonymous]
  249. public IActionResult GetPodcastFiles(int podcastId)
  250. {
  251. bool editor = User.IsInRole("Podcast");
  252. var foundPodcast = _dbContext.Podcasts.Where(p => ((p.Published || editor) && p.PodcastId == podcastId)).FirstOrDefault();
  253. if (foundPodcast != null)
  254. {
  255. List<object> files = new List<object>();
  256. foreach (PodcastFile file in foundPodcast.Files)
  257. {
  258. object fileObj = new
  259. {
  260. name = file.FileName,
  261. id = file.PodcastFileId
  262. };
  263. files.Add(fileObj);
  264. }
  265. return Json(new { result = new { files = files } });
  266. }
  267. return Json(new { error = "No podcast found" });
  268. }
  269. [HttpPost]
  270. public async Task<IActionResult> CreatePodcast(int episode, string title, string description)
  271. {
  272. if (ModelState.IsValid)
  273. {
  274. if (User.IsInRole("Podcast"))
  275. {
  276. // Grab the next episode number
  277. Models.Podcast lastPod = _dbContext.Podcasts.Where(p => p.Episode == episode).FirstOrDefault();
  278. if (lastPod == null)
  279. {
  280. // Create the podcast object
  281. Models.Podcast podcast = new Models.Podcast();
  282. podcast.Episode = episode;
  283. podcast.Title = title;
  284. podcast.Description = description;
  285. podcast.DatePosted = DateTime.Now;
  286. podcast.DatePublished = DateTime.Now;
  287. podcast.DateEdited = DateTime.Now;
  288. podcast.Files = await SaveFilesAsync(Request.Form.Files, episode);
  289. _dbContext.Podcasts.Add(podcast);
  290. _dbContext.SaveChanges();
  291. return Json(new { result = true });
  292. }
  293. return Json(new { error = "That episode already exists" });
  294. }
  295. return Json(new { error = "You don't have permission to create a podcast" });
  296. }
  297. return Json(new { error = "No podcast created" });
  298. }
  299. [HttpPost]
  300. public async Task<IActionResult> EditPodcast(int podcastId, int episode, string title, string description, string fileIds)
  301. {
  302. if (ModelState.IsValid)
  303. {
  304. if (User.IsInRole("Podcast"))
  305. {
  306. Models.Podcast podcast = _dbContext.Podcasts.Where(p => p.PodcastId == podcastId).FirstOrDefault();
  307. if (podcast != null)
  308. {
  309. if (_dbContext.Podcasts.Where(p => p.Episode != episode).FirstOrDefault() == null || podcast.Episode == episode)
  310. {
  311. podcast.Episode = episode;
  312. podcast.Title = title;
  313. podcast.Description = description;
  314. podcast.DateEdited = DateTime.Now;
  315. // Remove any files not in fileIds
  316. List<string> fileIdList = new List<string>();
  317. if (!string.IsNullOrEmpty(fileIds))
  318. {
  319. fileIdList = fileIds.Split(',').ToList();
  320. }
  321. for (int i = 0; i < podcast.Files.Count; i++)
  322. {
  323. PodcastFile curFile = podcast.Files.ElementAt(i);
  324. if (!fileIdList.Exists(id => id == curFile.PodcastFileId.ToString()))
  325. {
  326. if (System.IO.File.Exists(curFile.Path))
  327. {
  328. System.IO.File.Delete(curFile.Path);
  329. }
  330. _dbContext.PodcastFiles.Remove(curFile);
  331. podcast.Files.Remove(curFile);
  332. }
  333. }
  334. await SaveFilesAsync(Request.Form.Files, episode);
  335. // Add any new files
  336. List<PodcastFile> newFiles = await SaveFilesAsync(Request.Form.Files, episode);
  337. foreach (PodcastFile file in newFiles)
  338. {
  339. podcast.Files.Add(file);
  340. }
  341. // Save podcast
  342. _dbContext.Entry(podcast).State = EntityState.Modified;
  343. _dbContext.SaveChanges();
  344. return Json(new { result = true });
  345. }
  346. return Json(new { error = "That episode already exists" });
  347. }
  348. return Json(new { error = "No podcast found" });
  349. }
  350. return Json(new { error = "You don't have permission to edit this podcast" });
  351. }
  352. return Json(new { error = "Invalid Inputs" });
  353. }
  354. [HttpPost]
  355. public IActionResult PublishPodcast(int podcastId, bool publish)
  356. {
  357. if (ModelState.IsValid)
  358. {
  359. if (User.IsInRole("Podcast"))
  360. {
  361. Models.Podcast podcast = _dbContext.Podcasts.Find(podcastId);
  362. if (podcast != null)
  363. {
  364. podcast.Published = publish;
  365. if (publish)
  366. podcast.DatePublished = DateTime.Now;
  367. _dbContext.Entry(podcast).State = EntityState.Modified;
  368. _dbContext.SaveChanges();
  369. return Json(new { result = true });
  370. }
  371. return Json(new { error = "No podcast found" });
  372. }
  373. return Json(new { error = "You don't have permission to publish this podcast" });
  374. }
  375. return Json(new { error = "Invalid Inputs" });
  376. }
  377. [HttpPost]
  378. public IActionResult DeletePodcast(int podcastId)
  379. {
  380. if (ModelState.IsValid)
  381. {
  382. if (User.IsInRole("Podcast"))
  383. {
  384. Models.Podcast podcast = _dbContext.Podcasts.Where(p => p.PodcastId == podcastId).FirstOrDefault();
  385. if (podcast != null)
  386. {
  387. foreach (PodcastFile file in podcast.Files)
  388. {
  389. System.IO.File.Delete(file.Path);
  390. }
  391. _dbContext.Podcasts.Remove(podcast);
  392. _dbContext.SaveChanges();
  393. return Json(new { result = true });
  394. }
  395. return Json(new { error = "No podcast found" });
  396. }
  397. return Json(new { error = "You don't have permission to delete this podcast" });
  398. }
  399. return Json(new { error = "Invalid Inputs" });
  400. }
  401. #endregion
  402. #region Comments
  403. [HttpPost]
  404. [AllowAnonymous]
  405. public IActionResult GetComments(int podcastId, int startCommentID, int count)
  406. {
  407. var comments = _dbContext.PodcastComments.Where(p => (p.PodcastId == podcastId)).OrderByDescending(p => p.DatePosted).Skip(startCommentID).Take(count).ToList();
  408. List<CommentViewModel> commentViews = new List<CommentViewModel>();
  409. if (comments != null)
  410. {
  411. foreach (PodcastComment comment in comments)
  412. {
  413. commentViews.Add(new CommentViewModel(comment));
  414. }
  415. }
  416. return PartialView("~/Areas/Podcast/Views/Podcast/Comments.cshtml", commentViews);
  417. }
  418. [HttpPost]
  419. [AllowAnonymous]
  420. public IActionResult GetCommentArticle(int commentID)
  421. {
  422. PodcastComment comment = _dbContext.PodcastComments.Where(p => (p.PodcastCommentId == commentID)).FirstOrDefault();
  423. if (comment != null)
  424. {
  425. return Json(new { result = comment.Article });
  426. }
  427. return Json(new { error = "No article found" });
  428. }
  429. [HttpPost]
  430. public IActionResult CreateComment(int podcastId, string article)
  431. {
  432. if (ModelState.IsValid)
  433. {
  434. if (_dbContext.Podcasts.Where(p => p.PodcastId == podcastId).FirstOrDefault() != null)
  435. {
  436. PodcastComment comment = new PodcastComment();
  437. comment.PodcastId = podcastId;
  438. comment.UserId = UserHelper.GetUser(_dbContext, User.Identity.Name).UserId;
  439. comment.Article = article;
  440. comment.DatePosted = DateTime.Now;
  441. comment.DateEdited = DateTime.Now;
  442. _dbContext.PodcastComments.Add(comment);
  443. _dbContext.SaveChanges();
  444. return Json(new { result = true });
  445. }
  446. return Json(new { error = "That podcast does not exist" });
  447. }
  448. return Json(new { error = "Invalid Parameters" });
  449. }
  450. [HttpPost]
  451. public IActionResult EditComment(int commentID, string article)
  452. {
  453. if (ModelState.IsValid)
  454. {
  455. PodcastComment comment = _dbContext.PodcastComments.Where(c => c.PodcastCommentId == commentID).FirstOrDefault();
  456. if (comment != null)
  457. {
  458. if (comment.User.Username == User.Identity.Name || User.IsInRole("Admin"))
  459. {
  460. comment.Article = article;
  461. comment.DateEdited = DateTime.Now;
  462. _dbContext.Entry(comment).State = EntityState.Modified;
  463. _dbContext.SaveChanges();
  464. return Json(new { result = true });
  465. }
  466. return Json(new { error = "You don't have permission to edit this comment" });
  467. }
  468. return Json(new { error = "No comment found" });
  469. }
  470. return Json(new { error = "Invalid Parameters" });
  471. }
  472. [HttpPost]
  473. public IActionResult DeleteComment(int commentID)
  474. {
  475. if (ModelState.IsValid)
  476. {
  477. PodcastComment comment = _dbContext.PodcastComments.Where(c => c.PodcastCommentId == commentID).FirstOrDefault();
  478. if (comment != null)
  479. {
  480. if (comment.User.Username == User.Identity.Name || User.IsInRole("Admin"))
  481. {
  482. _dbContext.PodcastComments.Remove(comment);
  483. _dbContext.SaveChanges();
  484. return Json(new { result = true });
  485. }
  486. return Json(new { error = "You don't have permission to delete this comment" });
  487. }
  488. return Json(new { error = "No comment found" });
  489. }
  490. return Json(new { error = "Invalid Parameters" });
  491. }
  492. #endregion
  493. [DisableRequestSizeLimit]
  494. public async Task<List<PodcastFile>> SaveFilesAsync(IFormFileCollection files, int episode)
  495. {
  496. List<PodcastFile> podFiles = new List<PodcastFile>();
  497. if (files.Count > 0)
  498. {
  499. for (int i = 0; i < files.Count; i++)
  500. {
  501. IFormFile file = files[i];
  502. long fileSize = file.Length;
  503. string fileName = file.FileName;
  504. string fileExt = Path.GetExtension(fileName);
  505. if (!Directory.Exists(_config.PodcastConfig.PodcastDirectory))
  506. {
  507. Directory.CreateDirectory(_config.PodcastConfig.PodcastDirectory);
  508. }
  509. string newName = string.Format("Teknikast_Episode_{0}{1}", episode, fileExt);
  510. int index = 1;
  511. while (System.IO.File.Exists(Path.Combine(_config.PodcastConfig.PodcastDirectory, newName)))
  512. {
  513. newName = string.Format("Teknikast_Episode_{0} ({1}){2}", episode, index, fileExt);
  514. index++;
  515. }
  516. string fullPath = Path.Combine(_config.PodcastConfig.PodcastDirectory, newName);
  517. PodcastFile podFile = new PodcastFile();
  518. podFile.Path = fullPath;
  519. podFile.FileName = newName;
  520. podFile.ContentType = file.ContentType;
  521. podFile.ContentLength = file.Length;
  522. podFiles.Add(podFile);
  523. using (FileStream fs = new FileStream(fullPath, FileMode.Create))
  524. {
  525. await file.CopyToAsync(fs);
  526. }
  527. }
  528. }
  529. return podFiles;
  530. }
  531. }
  532. }