diff --git a/Kyoo/ClientApp/src/app/app-routing.module.ts b/Kyoo/ClientApp/src/app/app-routing.module.ts
index c0aa7cf2..9976b1fb 100644
--- a/Kyoo/ClientApp/src/app/app-routing.module.ts
+++ b/Kyoo/ClientApp/src/app/app-routing.module.ts
@@ -1,17 +1,17 @@
import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-
+import { RouterModule, Routes } from '@angular/router';
import { BrowseComponent } from './browse/browse.component';
-import { ShowDetailsComponent } from './show-details/show-details.component';
-import { NotFoundComponent } from './not-found/not-found.component';
-import { ShowResolverService } from './services/show-resolver.service';
-import { LibraryResolverService } from './services/library-resolver.service';
-import { PlayerComponent } from "./player/player.component";
-import { StreamResolverService } from "./services/stream-resolver.service";
import { CollectionComponent } from "./collection/collection.component";
+import { NotFoundComponent } from './not-found/not-found.component';
+import { PlayerComponent } from "./player/player.component";
+import { SearchComponent } from "./search/search.component";
import { CollectionResolverService } from "./services/collection-resolver.service";
+import { LibraryResolverService } from './services/library-resolver.service';
import { PeopleResolverService } from "./services/people-resolver.service";
-
+import { SearchResolverService } from "./services/search-resolver.service";
+import { ShowResolverService } from './services/show-resolver.service';
+import { StreamResolverService } from "./services/stream-resolver.service";
+import { ShowDetailsComponent } from './show-details/show-details.component';
const routes: Routes = [
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
@@ -19,7 +19,8 @@ const routes: Routes = [
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
{ path: "collection/:collection-slug", component: CollectionComponent, resolve: { collection: CollectionResolverService } },
{ path: "people/:people-slug", component: CollectionComponent, resolve: { collection: PeopleResolverService } },
- { path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService } },
+ { path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService } },
+ { path: "search/:query", component: SearchComponent, resolve: { items: SearchResolverService } },
{ path: "**", component: NotFoundComponent }
];
diff --git a/Kyoo/ClientApp/src/app/app.component.html b/Kyoo/ClientApp/src/app/app.component.html
index e2a76bd6..1c8e3f4b 100644
--- a/Kyoo/ClientApp/src/app/app.component.html
+++ b/Kyoo/ClientApp/src/app/app.component.html
@@ -15,8 +15,9 @@
- -
- search
+
-
+
+ search
-
diff --git a/Kyoo/ClientApp/src/app/app.component.scss b/Kyoo/ClientApp/src/app/app.component.scss
index bd66b23f..4f69b8a0 100644
--- a/Kyoo/ClientApp/src/app/app.component.scss
+++ b/Kyoo/ClientApp/src/app/app.component.scss
@@ -1,3 +1,7 @@
+@import "~bootstrap/scss/functions";
+@import "~bootstrap/scss/variables";
+@import "~bootstrap/scss/mixins/breakpoints";
+
.navbar
{
justify-content: left;
@@ -35,6 +39,33 @@
color: var(--accentColor);
}
+.searchbar
+{
+ border-radius: 30px;
+
+ > input
+ {
+ background: none !important;
+ color: white;
+ outline: none;
+ border: none;
+ border-bottom: 1px solid #cfcfcf;
+ width: 0;
+ padding: 0;
+ transition: width 0.4s ease-in-out;
+
+ &:focus, .searching
+ {
+ width: 12rem;
+
+ @include media-breakpoint-up(sm)
+ {
+ width: 20rem;
+ }
+ }
+ }
+}
+
.icon
{
padding: 8px;
diff --git a/Kyoo/ClientApp/src/app/app.component.ts b/Kyoo/ClientApp/src/app/app.component.ts
index e6041d56..fb611825 100644
--- a/Kyoo/ClientApp/src/app/app.component.ts
+++ b/Kyoo/ClientApp/src/app/app.component.ts
@@ -39,6 +39,19 @@ export class AppComponent
}
});
}
+
+ openSearch()
+ {
+ let input = document.getElementById("search");
+
+ input.value = "";
+ input.focus();
+ }
+
+ onUpdateValue(value: string)
+ {
+ console.log("Value: " + value);
+ }
}
interface Library
diff --git a/Kyoo/ClientApp/src/app/app.module.ts b/Kyoo/ClientApp/src/app/app.module.ts
index 7da5b16d..ec0f12b5 100644
--- a/Kyoo/ClientApp/src/app/app.module.ts
+++ b/Kyoo/ClientApp/src/app/app.module.ts
@@ -19,6 +19,7 @@ import { EpisodesListComponent } from './episodes-list/episodes-list.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { PlayerComponent } from './player/player.component';
import { ShowDetailsComponent } from './show-details/show-details.component';
+import { SearchComponent } from './search/search.component';
@NgModule({
@@ -29,7 +30,8 @@ import { ShowDetailsComponent } from './show-details/show-details.component';
ShowDetailsComponent,
EpisodesListComponent,
PlayerComponent,
- CollectionComponent
+ CollectionComponent,
+ SearchComponent
],
imports: [
BrowserModule,
diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.scss b/Kyoo/ClientApp/src/app/browse/browse.component.scss
index 54c6ce9e..2a80c63c 100644
--- a/Kyoo/ClientApp/src/app/browse/browse.component.scss
+++ b/Kyoo/ClientApp/src/app/browse/browse.component.scss
@@ -1,6 +1,6 @@
-@import "~bootstrap//scss/functions";
+@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
-@import "~bootstrap/scss//mixins/breakpoints";
+@import "~bootstrap/scss/mixins/breakpoints";
button
{
diff --git a/Kyoo/ClientApp/src/app/search/search.component.html b/Kyoo/ClientApp/src/app/search/search.component.html
new file mode 100644
index 00000000..c99423e7
--- /dev/null
+++ b/Kyoo/ClientApp/src/app/search/search.component.html
@@ -0,0 +1 @@
+
search works!
diff --git a/Kyoo/ClientApp/src/app/search/search.component.scss b/Kyoo/ClientApp/src/app/search/search.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/Kyoo/ClientApp/src/app/search/search.component.spec.ts b/Kyoo/ClientApp/src/app/search/search.component.spec.ts
new file mode 100644
index 00000000..43729199
--- /dev/null
+++ b/Kyoo/ClientApp/src/app/search/search.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SearchComponent } from './search.component';
+
+describe('SearchComponent', () => {
+ let component: SearchComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ SearchComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SearchComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/Kyoo/ClientApp/src/app/search/search.component.ts b/Kyoo/ClientApp/src/app/search/search.component.ts
new file mode 100644
index 00000000..3ba8a3f7
--- /dev/null
+++ b/Kyoo/ClientApp/src/app/search/search.component.ts
@@ -0,0 +1,20 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from "@angular/router";
+import { SearchResut } from "../../models/search-result";
+
+@Component({
+ selector: 'app-search',
+ templateUrl: './search.component.html',
+ styleUrls: ['./search.component.scss']
+})
+export class SearchComponent implements OnInit
+{
+ items: SearchResut;
+
+ constructor(private route: ActivatedRoute) { }
+
+ ngOnInit()
+ {
+ this.items = this.route.snapshot.data.items;
+ }
+}
diff --git a/Kyoo/ClientApp/src/app/services/search-resolver.service.ts b/Kyoo/ClientApp/src/app/services/search-resolver.service.ts
new file mode 100644
index 00000000..6c89018a
--- /dev/null
+++ b/Kyoo/ClientApp/src/app/services/search-resolver.service.ts
@@ -0,0 +1,26 @@
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
+import { EMPTY, Observable } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+import { SearchResut } from "../../models/search-result";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class SearchResolverService implements Resolve
+{
+ constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
+
+ resolve(route: ActivatedRouteSnapshot): SearchResut | Observable | Promise
+ {
+ let query: string = route.paramMap.get("query");
+ return this.http.get("api/search/" + query).pipe(catchError((error: HttpErrorResponse) =>
+ {
+ console.log(error.status + " - " + error.message);
+ this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
+ return EMPTY;
+ }));
+ }
+}
diff --git a/Kyoo/ClientApp/src/models/search-result.js b/Kyoo/ClientApp/src/models/search-result.js
new file mode 100644
index 00000000..5619529e
--- /dev/null
+++ b/Kyoo/ClientApp/src/models/search-result.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=search-result.js.map
\ No newline at end of file
diff --git a/Kyoo/ClientApp/src/models/search-result.js.map b/Kyoo/ClientApp/src/models/search-result.js.map
new file mode 100644
index 00000000..8a73ca56
--- /dev/null
+++ b/Kyoo/ClientApp/src/models/search-result.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"search-result.js","sourceRoot":"","sources":["search-result.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/Kyoo/ClientApp/src/models/search-result.ts b/Kyoo/ClientApp/src/models/search-result.ts
new file mode 100644
index 00000000..3463e07b
--- /dev/null
+++ b/Kyoo/ClientApp/src/models/search-result.ts
@@ -0,0 +1,14 @@
+import { Show } from "./show";
+import { Episode } from "./episode";
+import { People } from "./people";
+import { Studio } from "./studio";
+import { Genre } from "./genre";
+
+export interface SearchResut
+{
+ shows: Show[];
+ episodes: Episode[];
+ people: People[];
+ genres: Genre[];
+ studios: Studio[];
+}
diff --git a/Kyoo/Controllers/SearchController.cs b/Kyoo/Controllers/SearchController.cs
new file mode 100644
index 00000000..b33acd3f
--- /dev/null
+++ b/Kyoo/Controllers/SearchController.cs
@@ -0,0 +1,32 @@
+using Kyoo.InternalAPI;
+using Kyoo.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Kyoo.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class SearchController : ControllerBase
+ {
+ private readonly ILibraryManager libraryManager;
+
+ public SearchController(ILibraryManager libraryManager)
+ {
+ this.libraryManager = libraryManager;
+ }
+
+ [HttpGet("{query}")]
+ public ActionResult Search(string query)
+ {
+ SearchResult result = new SearchResult
+ {
+ shows = libraryManager.GetShows(query),
+ episodes = libraryManager.SearchEpisodes(query),
+ people = libraryManager.SearchPeople(query),
+ genres = libraryManager.SearchGenres(query),
+ studios = libraryManager.SearchStudios(query)
+ };
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Kyoo/Controllers/SubtitleController.cs b/Kyoo/Controllers/SubtitleController.cs
index f42d791d..c3487a42 100644
--- a/Kyoo/Controllers/SubtitleController.cs
+++ b/Kyoo/Controllers/SubtitleController.cs
@@ -134,7 +134,6 @@ namespace Kyoo.Controllers
lines[1] = lines[1].Replace(',', '.');
if (lines[2].Length > 5)
{
- Debug.WriteLine("&Line2 sub: " + lines[2].Substring(0, 6));
switch (lines[2].Substring(0, 6))
{
case "{\\an1}":
diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs
index e05fba80..cc6f46a3 100644
--- a/Kyoo/InternalAPI/Crawler/Crawler.cs
+++ b/Kyoo/InternalAPI/Crawler/Crawler.cs
@@ -280,7 +280,7 @@ namespace Kyoo.InternalAPI
case ".ass":
codec = "ass";
break;
- case ".str":
+ case ".srt":
codec = "subrip";
break;
default:
diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
index 3f335de5..8f67ac71 100644
--- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
+++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
@@ -8,7 +8,6 @@ namespace Kyoo.InternalAPI
{
//Read values
string GetShowExternalIDs(long showID);
- IEnumerable GetShows();
Studio GetStudio(long showID);
List GetDirectors(long showID);
List GetPeople(long showID);
@@ -25,6 +24,8 @@ namespace Kyoo.InternalAPI
Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced);
//Public read
+ IEnumerable GetShows();
+ IEnumerable GetShows(string searchQuery);
Library GetLibrary(string librarySlug);
IEnumerable GetLibraries();
Show GetShowBySlug(string slug);
@@ -38,6 +39,10 @@ namespace Kyoo.InternalAPI
Studio GetStudioBySlug(string slug);
Collection GetCollection(string slug);
IEnumerable GetAllEpisodes();
+ IEnumerable SearchEpisodes(string searchQuery);
+ IEnumerable SearchPeople(string searchQuery);
+ IEnumerable SearchGenres(string searchQuery);
+ IEnumerable SearchStudios(string searchQuery);
//Check if value exists
bool IsCollectionRegistered(string collectionSlug);
diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
index 9e5b0aae..b40cb4bc 100644
--- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
+++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
@@ -302,6 +302,22 @@ namespace Kyoo.InternalAPI
return shows;
}
+ public IEnumerable GetShows(string searchQuery)
+ {
+ List shows = new List();
+ SQLiteDataReader reader;
+ string query = "SELECT slug, title, aliases, startYear, endYear, '0' FROM (SELECT slug, title, aliases, startYear, endYear, '0' FROM shows LEFT JOIN collectionsLinks l ON l.showID = shows.id WHERE l.showID IS NULL UNION SELECT slug, name, null, startYear, endYear, '1' FROM collections) WHERE title LIKE $query OR aliases LIKE $query ORDER BY title;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$query", "%" + searchQuery + "%");
+ reader = cmd.ExecuteReader();
+ while (reader.Read())
+ shows.Add(Show.FromQueryReader(reader, true));
+ }
+ return shows;
+ }
+
public Show GetShowBySlug(string slug)
{
string query = "SELECT * FROM shows WHERE slug = $slug;";
@@ -661,6 +677,70 @@ namespace Kyoo.InternalAPI
return episodes;
}
}
+
+ public IEnumerable SearchEpisodes(string searchQuery)
+ {
+ List episodes = new List();
+ SQLiteDataReader reader;
+ string query = "SELECT * FROM episodes WHERE title LIKE $query ORDER BY seasonNumber, episodeNumber;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$query", "%" + searchQuery + "%");
+ reader = cmd.ExecuteReader();
+ while (reader.Read())
+ episodes.Add(Episode.FromReader(reader));
+ }
+ return episodes;
+ }
+
+ public IEnumerable SearchPeople(string searchQuery)
+ {
+ List people = new List();
+ SQLiteDataReader reader;
+ string query = "SELECT * FROM people WHERE name LIKE $query ORDER BY name;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$query", "%" + searchQuery + "%");
+ reader = cmd.ExecuteReader();
+ while (reader.Read())
+ people.Add(People.FromReader(reader));
+ }
+ return people;
+ }
+
+ public IEnumerable SearchGenres(string searchQuery)
+ {
+ List genres = new List();
+ SQLiteDataReader reader;
+ string query = "SELECT * FROM genres WHERE name LIKE $query ORDER BY name;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$query", "%" + searchQuery + "%");
+ reader = cmd.ExecuteReader();
+ while (reader.Read())
+ genres.Add(Genre.FromReader(reader));
+ }
+ return genres;
+ }
+
+ public IEnumerable SearchStudios(string searchQuery)
+ {
+ List studios = new List();
+ SQLiteDataReader reader;
+ string query = "SELECT * FROM studios WHERE name LIKE $query ORDER BY name;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$query", "%" + searchQuery + "%");
+ reader = cmd.ExecuteReader();
+ while (reader.Read())
+ studios.Add(Studio.FromReader(reader));
+ }
+ return studios;
+ }
#endregion
#region Check if items exists
diff --git a/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs
index 0a4d8f3b..77d7d990 100644
--- a/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs
+++ b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs
@@ -25,37 +25,49 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
string localBackdrop = Path.Combine(show.Path, "backdrop.jpg");
- if (show.ImgPrimary != null)
+ if (show.ImgPrimary != null && !File.Exists(localThumb))
{
- if (!File.Exists(localThumb))
+ try
{
using (WebClient client = new WebClient())
{
await client.DownloadFileTaskAsync(new Uri(show.ImgPrimary), localThumb);
}
}
+ catch (WebException)
+ {
+ Console.Error.WriteLine("Couldn't download an image.");
+ }
}
- if (show.ImgLogo != null)
+ if (show.ImgLogo != null && !File.Exists(localLogo))
{
- if (!File.Exists(localLogo))
+ try
{
using (WebClient client = new WebClient())
{
await client.DownloadFileTaskAsync(new Uri(show.ImgLogo), localLogo);
}
}
+ catch (WebException)
+ {
+ Console.Error.WriteLine("Couldn't download an image.");
+ }
}
- if (show.ImgBackdrop != null)
+ if (show.ImgBackdrop != null && !File.Exists(localBackdrop))
{
- if (!File.Exists(localBackdrop))
+ try
{
using (WebClient client = new WebClient())
{
await client.DownloadFileTaskAsync(new Uri(show.ImgBackdrop), localBackdrop);
}
}
+ catch (WebException)
+ {
+ Console.Error.WriteLine("Couldn't download an image.");
+ }
}
return show;
@@ -71,10 +83,16 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
string localThumb = root + "/" + people[i].slug + ".jpg";
if (people[i].imgPrimary != null && !File.Exists(localThumb))
{
- using (WebClient client = new WebClient())
+ try
{
- Debug.WriteLine("&" + localThumb);
- await client.DownloadFileTaskAsync(new Uri(people[i].imgPrimary), localThumb);
+ using (WebClient client = new WebClient())
+ {
+ await client.DownloadFileTaskAsync(new Uri(people[i].imgPrimary), localThumb);
+ }
+ }
+ catch (WebException)
+ {
+ Console.Error.WriteLine("Couldn't download an image.");
}
}
}
@@ -88,9 +106,16 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
string localThumb = episode.Path.Replace(Path.GetExtension(episode.Path), "-thumb.jpg");
if (episode.ImgPrimary != null && !File.Exists(localThumb))
{
- using (WebClient client = new WebClient())
+ try
{
- await client.DownloadFileTaskAsync(new Uri(episode.ImgPrimary), localThumb);
+ using (WebClient client = new WebClient())
+ {
+ await client.DownloadFileTaskAsync(new Uri(episode.ImgPrimary), localThumb);
+ }
+ }
+ catch (WebException)
+ {
+ Console.Error.WriteLine("Couldn't download an image.");
}
}
diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj
index c767bc03..54b2caf7 100644
--- a/Kyoo/Kyoo.csproj
+++ b/Kyoo/Kyoo.csproj
@@ -37,6 +37,7 @@
+
@@ -77,6 +78,9 @@
+
+ Code
+
diff --git a/Kyoo/Models/SearchResult.cs b/Kyoo/Models/SearchResult.cs
new file mode 100644
index 00000000..57ce32f8
--- /dev/null
+++ b/Kyoo/Models/SearchResult.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Kyoo.Models
+{
+ public class SearchResult
+ {
+ public IEnumerable shows;
+ public IEnumerable episodes;
+ public IEnumerable people;
+ public IEnumerable genres;
+ public IEnumerable studios;
+ }
+}
diff --git a/Kyoo/Models/Show.cs b/Kyoo/Models/Show.cs
index 634ef121..b2c267ee 100644
--- a/Kyoo/Models/Show.cs
+++ b/Kyoo/Models/Show.cs
@@ -91,9 +91,9 @@ namespace Kyoo.Models
IsCollection = false;
}
- public static Show FromQueryReader(System.Data.SQLite.SQLiteDataReader reader)
+ public static Show FromQueryReader(System.Data.SQLite.SQLiteDataReader reader, bool containsAliases = false)
{
- return new Show()
+ Show show = new Show()
{
Slug = reader["slug"] as string,
Title = reader["title"] as string,
@@ -101,6 +101,9 @@ namespace Kyoo.Models
EndYear = reader["endYear"] as long?,
IsCollection = reader["'0'"] as string == "1"
};
+ if (containsAliases)
+ show.Aliases = (reader["aliases"] as string)?.Split('|');
+ return show;
}
public static Show FromReader(System.Data.SQLite.SQLiteDataReader reader)