0

Избавляемся от war

Теперь KinoSearch самодостаточен и может запускаться без внешних веб-контейнеров
This commit is contained in:
2017-12-11 12:17:51 +03:00
parent ef7c7b7a60
commit 6f40475dfb
34 changed files with 541 additions and 413 deletions

78
pom.xml
View File

@@ -15,46 +15,51 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<spring.version>4.2.5.RELEASE</spring.version> <spring.version>4.3.7.RELEASE</spring.version>
<jetty.version>9.4.0.v20161208</jetty.version>
</properties> </properties>
<groupId>ru.dmitriymx</groupId> <groupId>ru.dmitriymx</groupId>
<artifactId>kinosearch</artifactId> <artifactId>kinosearch</artifactId>
<version>2.0.9</version> <version>2.0.10-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies> <dependencies>
<!-- SPRING --> <!-- SPRING -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
<version>${spring.version}</version> <version>${spring.version}</version>
</dependency> </dependency>
<!-- JETTY -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- SPRING MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId> <artifactId>spring-context-support</artifactId>
<version>${spring.version}</version> <version>${spring.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- COMPONENTS --> <!-- COMPONENTS -->
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
@@ -76,6 +81,7 @@
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.5.2</version> <version>4.5.2</version>
</dependency> </dependency>
<!-- TESTING --> <!-- TESTING -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@@ -114,16 +120,30 @@
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.0.0</version> <version>2.4</version>
<configuration> <configuration>
<failOnMissingWebXml>false</failOnMissingWebXml> <archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>dependency/</classpathPrefix>
</manifest>
</archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.eclipse.jetty</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>jetty-maven-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>9.4.0.v20161208</version> <version>2.8</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>

View File

@@ -0,0 +1,16 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-12-11
*/
package kinosearch.webapp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
WebApp webApp = ctx.getBean("webapp", WebApp.class);
webApp.start();
}
}

View File

@@ -0,0 +1,40 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-12-11
*/
package kinosearch.webapp;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
@Configuration
@Import(kinosearch.core.SpringConfig.class)
@PropertySource("classpath:/application.properties")
public class SpringConfig {
@Bean
public WebApp webapp(@Value("${webapp.host}") String host, @Value("${webapp.port}") int port) {
return new WebApp(host, port);
}
@Bean
public ViewResolver viewResolver() {
FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
viewResolver.setContentType("text/html;charset=UTF-8");
viewResolver.setCache(true);
viewResolver.setSuffix(".ftl");
return viewResolver;
}
@Bean
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setTemplateLoaderPath("classpath:/kinosearch/webapp/");
return freeMarkerConfigurer;
}
}

View File

@@ -0,0 +1,31 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-12-11
*/
package kinosearch.webapp;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
final String staticPath = "classpath:/kinosearch/webapp/static/";
/* Styles */
registry.addResourceHandler("/css/**")
.addResourceLocations(staticPath+"css/");
/* Fonts */
registry.addResourceHandler("/fonts/**")
.addResourceLocations(staticPath+"fonts/");
/* JavaScript */
registry.addResourceHandler("/js/**")
.addResourceLocations(staticPath+"js/");
/* Other */
registry.addResourceHandler("/*.svg").addResourceLocations(staticPath);
registry.addResourceHandler("/*.png").addResourceLocations(staticPath);
}
}

View File

@@ -0,0 +1,62 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-12-11
*/
package kinosearch.webapp;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;
public class WebApp {
private final Logger logger = Logger.getLogger(WebApp.class.getName());
private final String host;
private final int port;
public WebApp(String host, int port) {
this.host = host;
this.port = port;
}
void start() {
Server server = new Server(new InetSocketAddress(host, port));
server.setHandler(getServletContextHandler(getContext()));
try {
server.start();
server.join();
} catch (Exception e) {
logger.log(Level.SEVERE, "Start server", e);
}
}
private ServletContextHandler getServletContextHandler(WebApplicationContext context) {
ServletContextHandler contextHandler = new ServletContextHandler();
contextHandler.setErrorHandler(null);
contextHandler.setContextPath("/");
contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), "/*");
contextHandler.addEventListener(new ContextLoaderListener(context));
return contextHandler;
}
private WebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("kinosearch.webapp");
return context;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}

View File

@@ -1,56 +0,0 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-01-04
*/
package kinosearch.webapp;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import kinosearch.core.KinoPlay;
import kinosearch.core.SpringConfig;
import org.springframework.context.annotation.*;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan
@Import(SpringConfig.class)
public class WebAppConfiguration extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
viewResolver.setCache(true);
viewResolver.setSuffix(".html");
return viewResolver;
}
@Bean
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setTemplateLoaderPath("/WEB-INF/");
return freeMarkerConfigurer;
}
@Bean
@Scope("singleton")
public Gson gson() {
return new GsonBuilder().registerTypeAdapter(KinoPlay.class, new KinoPlaySerializer()).create();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("/css/");
registry.addResourceHandler("/fonts/**")
.addResourceLocations("/fonts/");
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/");
registry.addResourceHandler("/*.png")
.addResourceLocations("/");
}
}

View File

@@ -16,57 +16,50 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
@Controller @Controller
@RequestMapping("/")
public class WebAppController { public class WebAppController {
@Autowired private final Logger logger = Logger.getLogger(WebAppController.class.getName());
private ServletContext webAppContext;
@Autowired @Autowired
private ApplicationContext coreContext; private ApplicationContext coreContext;
private void setDefaultModel(ModelMap model) { private void setDefaultModel(ModelMap model) {
model.addAttribute("basedir", webAppContext.getContextPath()); model.addAttribute("version", "2.0.10-SNAPSHOT");
model.addAttribute("version", "2.0.9");
model.addAttribute("rutext", "Поиск кино по пиратским кинотеатрам"); model.addAttribute("rutext", "Поиск кино по пиратским кинотеатрам");
} }
private void setDefaultResponse(HttpServletResponse response) { @RequestMapping(value = {"/", "/index.html"}, method = RequestMethod.GET)
response.setContentType("text/html"); public String index(ModelMap model) {
response.setCharacterEncoding("utf-8");
}
@RequestMapping(value = { "/", "/index.html" }, method = RequestMethod.GET)
public String index(ModelMap model, HttpServletRequest request, HttpServletResponse response) {
setDefaultModel(model); setDefaultModel(model);
setDefaultResponse(response);
if (request.getParameter("search") != null && !request.getParameter("search").trim().isEmpty()) {
boolean strong = (request.getParameter("strong") != null && request.getParameter("strong").equals("1"));
search(request.getParameter("search"), model, strong);
}
return "index"; return "index";
} }
private void search(String search, ModelMap model, boolean strong) { @RequestMapping(value = {"/", "/index.html"}, method = RequestMethod.GET, params = {"search"})
public String search(@RequestParam("search") String searchText, ModelMap model) {
if (searchText.trim().isEmpty()) {
return "redirect:/";
}
List<Kino> list = Collections.synchronizedList(new LinkedList<>()); List<Kino> list = Collections.synchronizedList(new LinkedList<>());
Map<String, KinoWarez> kinoWarezMap = coreContext.getBeansOfType(KinoWarez.class); Map<String, KinoWarez> kinoWarezMap = coreContext.getBeansOfType(KinoWarez.class);
ThreadGroup threadGroup = new ThreadGroup(""); ThreadGroup threadGroup = new ThreadGroup("");
for (KinoWarez kinoWarez : kinoWarezMap.values()) { //TODO на будущее надо ограничить количество одновременных потоков for (KinoWarez kinoWarez : kinoWarezMap.values()) { //TODO надо ограничить количество одновременных потоков
new Thread(threadGroup, () -> { new Thread(threadGroup, () -> {
List<Kino> outList = kinoWarez.search(search, strong); List<Kino> outList = kinoWarez.search(searchText, false); //FIXME "strong" нужно учитывать
for (Kino kino : outList) { for (Kino kino : outList) {
kino.setName("[" + kinoWarez.getName() + "] " + kino.getName()); kino.setName("[" + kinoWarez.getName() + "] " + kino.getName());
@@ -81,9 +74,12 @@ public class WebAppController {
Tools.SafeSleep(1000); Tools.SafeSleep(1000);
} }
model.put("searchtext", search); model.put("searchtext", searchText);
model.put("resultsearch", groupKino(list)); model.put("resultsearch", groupKino(list));
model.put("strong", strong); model.put("strong", false); //FIXME "strong" нужно учитывать
setDefaultModel(model);
return "index";
} }
private List<Kino> groupKino(List<Kino> list) { private List<Kino> groupKino(List<Kino> list) {
@@ -138,32 +134,28 @@ public class WebAppController {
return grouppedList; return grouppedList;
} }
@RequestMapping(value = "/about.html", method = RequestMethod.GET)
public String about(ModelMap model, HttpServletResponse response) {
setDefaultModel(model);
setDefaultResponse(response);
return "simple_template/about";
}
@RequestMapping(value = "/player/{warez}/**", method = RequestMethod.GET) @RequestMapping(value = "/player/{warez}/**", method = RequestMethod.GET)
public String player(@PathVariable() String warez, ModelMap model, HttpServletRequest request, HttpServletResponse response) { public String player(@PathVariable() String warez, ModelMap model, HttpServletRequest request) throws MalformedURLException {
setDefaultModel(model);
setDefaultResponse(response);
KinoWarez kinoWarez = coreContext.getBean(warez, KinoWarez.class); KinoWarez kinoWarez = coreContext.getBean(warez, KinoWarez.class);
if (kinoWarez == null) { if (kinoWarez == null) {
return "redirect:/"; return "redirect:/";
} }
KinoPlay kinoPlay = kinoWarez.player(request.getServletPath().substring(("/player/"+warez).length()));
//TODO а необходимость в URL точно оправдана?
URL requestUrl = new URL(request.getRequestURL().toString());
KinoPlay kinoPlay = kinoWarez.player(requestUrl.getPath().substring(("/player/"+warez).length()));
Gson gson = coreContext.getBean(Gson.class); Gson gson = coreContext.getBean(Gson.class);
model.put("json", gson.toJson(kinoPlay)); model.put("json", gson.toJson(kinoPlay));
setDefaultModel(model);
return "player"; return "player";
} }
@RequestMapping(value = "/proxy/{warez}/**", method = RequestMethod.GET) @RequestMapping(value = "/proxy/{warez}/**", method = RequestMethod.GET)
public void proxy(@PathVariable String warez, HttpServletRequest request, HttpServletResponse response) throws IOException { public void proxy(@PathVariable String warez, HttpServletRequest request, HttpServletResponse response) throws IOException {
String path = request.getServletPath().substring(("/proxy/"+warez+"/").length()); //TODO а необходимость в URL точно оправдана?
URL requestUrl = new URL(request.getRequestURL().toString());
String path = requestUrl.getPath().substring(("/proxy/"+warez+"/").length());
URL url = new URL("http://" + path); URL url = new URL("http://" + path);
HttpURLConnection con =(HttpURLConnection) url.openConnection(); HttpURLConnection con =(HttpURLConnection) url.openConnection();
@@ -211,4 +203,19 @@ public class WebAppController {
webToProxyBuf.close(); webToProxyBuf.close();
con.disconnect(); con.disconnect();
} }
@RequestMapping(value = {"/about", "/about.html"}, method = RequestMethod.GET)
public String about(ModelMap model) {
setDefaultModel(model);
return "about";
}
@RequestMapping(value = "/favicon.ico")
public void favicon(HttpServletResponse response) {
try {
response.sendError(404);
} catch (IOException e) {
logger.log(Level.WARNING, "favicon 404", e);
}
}
} }

View File

@@ -1,24 +0,0 @@
/*
* DmitriyMX <dmitriymx@yandex.ru>
* 2017-01-04
*/
package kinosearch.webapp;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{WebAppConfiguration.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[0];
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

View File

@@ -0,0 +1,2 @@
webapp.host=127.0.0.1
webapp.port=8080

View File

@@ -0,0 +1,28 @@
[#ftl]
[#include "/header.inc.ftl"]
<hr>
<p>
Частенько бывает, что желанный фильм, мультфильм или сериал располагается только на одном каком-то кино-ресурсе.
И хорошо, если этот ресурс был первым в вашем списке ручного поиска кино.<br>
А если нет?<br>
А если у вас таких сайтов 5-10?<br>
А если на всех ваших любимых сайтах не оказалась искомого?<br>
Тогда вы лезете в Google/Яндекс и...
И обязательно напарываетесь на какую-то напичканную рекламой хрень, где еще и вылезет ошибка "фильм не найден".
Поиск "кинца" начинает затягиваться, а желание его посмотреть и вовсе улетучится.
</p>
<p>
Нет, так быть не должно! Если уж решился смотреть кино в онлайне, то пусть это будет комфортно!<br>
Мой проект возьмет всю рутину поиска на себя, а вам остается только выбрать место просмотра и наслаждаться
фильмом/сериалом/еще чем-то.
</p>
<p>
Кинотеатры подбираются так, чтобы в них было поменьше рекламы и побольше нужных фильмов. А в скором времени я и
это ... упрощу =)
</p>
<p>
Приятного просмотра!
</p>
<hr>
<p>Автор: <a href="http://dmitriymx.di9.ru">DmitriyMX</a>/2015</p>
[#include "/fother.inc.ftl"]

View File

@@ -0,0 +1,35 @@
[#ftl]
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="${rutext}">
<meta name="version" content="${version}">
<title>KinoSearch :: ${rutext}</title>
<link rel="icon" type="image/svg+xml" href="/ks3logo.svg" >
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/css/awesome-bootstrap-checkbox.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
<script type="text/javascript" src="/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1 class="title"><a href="/"><img src="/ks3logo.svg"> KinoSearch</a></h1>
<p class="text-center">${rutext}</p>
<form method="get" action="/">
<div class="input-group">
<input class="form-control" type="text" placeholder="Что ищем?" name="search" value="[#if searchtext??]${searchtext}[/#if]"/>
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Ищи!</button>
</span>
</div>
<div class="checkbox checkbox-primary">
<input type="checkbox" id="strong" name="strong" value="1"[#if strong?? && strong = true] checked[/#if]>
<label for="strong">Точное совпадение</label>
</div>
</form>
<div class="text-center">
<a href="/about.html">О проекте</a> | <a href="mailto:dmitriymx@yandex.ru">Написать отзыв</a>
</div>

View File

@@ -0,0 +1,62 @@
[#ftl]
[#include "/header.inc.ftl"]
[#if resultsearch??]
<script type="text/javascript">
$(function(){
$('a.spoiler').bind('click',function(){
$(this).next().collapse('toggle');
return false;
});
});
</script>
<hr>
[#if resultsearch?has_content]
[#list resultsearch as kino]
[#if kino.getClass().getSimpleName() == "KinoGroup"]
<div class="panel panel-default">
<a href="#" class="panel-heading spoiler">
<span class="glyphicon glyphicon-chevron-down">&nbsp;</span><b>${kino.name}</b>
</a>
<div class="panel-collapse collapse out">
<div class="panel-body">
[#list kino.kinolist as kino_groupped]
<div class="panel panel-default">
<div class="panel-heading"><b>${kino_groupped.name}</b></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
<a class="btn btn-primary btn-block" href="${kino_groupped.url}" target="_blank">на сайте</a>
</div>
<div class="col-sm-6">
<a class="btn btn-danger btn-block" href="/player/${kino_groupped.getPlayerUrl()}" target="_blank">в плеере</a>
</div>
</div>
</div>
</div>
[/#list]
</div>
</div>
</div>
[#else]
<div class="panel panel-default">
<div class="panel-heading"><b>${kino.name}</b></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
<a class="btn btn-primary btn-block" href="${kino.url}" target="_blank">на сайте</a>
</div>
<div class="col-sm-6">
<a class="btn btn-danger btn-block" href="/player/${kino.getPlayerUrl()}" target="_blank">в плеере</a>
</div>
</div>
</div>
</div>
[/#if]
[/#list]
[#else]
<p>Ничего не найдено =(</p>
[/#if]
[#else]
[#include "/news.inc.ftl"]
[/#if]
[#include "/fother.inc.ftl"]

View File

@@ -46,9 +46,9 @@
</div> </div>
<div class="col-sm-10"> <div class="col-sm-10">
<p><em>Version 2.0.7</em></p> <p><em>Version 2.0.7</em></p>
<p>Долгожданное обновление: наконец то появился плеер! О да! Долой центнеры рекламы, что предлагает нам... да что только не! Начиная от <em>"купи слона"</em>, до <em>"мега-умбер-онлайн игра, клонов которой уже нисчесть! играй сейчас! живо!!!"</em>.</p> <p>Долгожданное обновление: наконец то появился плеер! О да! Долой центнеры рекламы, что предлагает нам... да что только не! Начиная от <em>"купи слона"</em>, до <em>"мега-умбер-онлайн игра, клонов которой уже нисчесть! играй сейчас! живо!!!"</em>.</p>
<p>Кхм. Простите.</p> <p>Кхм. Простите.</p>
<p>Вообщем, тестовая вариация плеера запущена для кинотеатра <b>OnlineLife</b>. Если нареканий не будет, то запущу и для остальных.</p> <p>Вообщем, тестовая вариация плеера запущена для кинотеатра <b>OnlineLife</b>. Если нареканий не будет, то запущу и для остальных.</p>
</div> </div>
<div class="col-sm-2"> <div class="col-sm-2">

View File

@@ -0,0 +1,117 @@
[#ftl]
[#include "/header.inc.ftl"]
<script type="text/javascript" src="/js/js.cookie-2.1.0.min.js"></script>
<script type="text/javascript" src="/js/player.js"></script>
<script type="text/javascript">
var playerCore;
var data;
var plSeason;
var plSerial;
var playerObj;
$(function(){
var video_data = ${json};
var titleObj = $('#title');
plSeason = $('#pl-season');
plSerial = $('#pl-serial');
playerObj = $('#player');
playerCore = new PlayerCore(playerObj, titleObj, video_data);
playerCore.setupPlayer();
// загрузка ранее сохранённых данных
data = Cookies.getJSON(playerCore.path);
if (data != null) {
var fulltime = playerCore.msToTime(data.time * 1000);
$('#mdl-vtime').text(fulltime.h + ':' + fulltime.m + ':' + fulltime.s);
if (playerCore.getType() == 'simple_serial') {
$('#mdl-serial').find('span').text(data.serial + 1);
$('#mdl-serial').removeClass('hide');
} else if (playerCore.getType() == 'seasons_serial') {
$('#mdl-season').find('span').text(data.season + 1);
$('#mdl-season').removeClass('hide');
$('#mdl-serial').find('span').text(data.serial + 1);
$('#mdl-serial').removeClass('hide');
}
$('#modal').modal('show');
}
if (playerCore.getType() == 'one_film') {
playerCore.setTitle();
playerCore.setupPlayerForOneFilm();
} else if (playerCore.getType() == 'simple_serial') {
playerCore.setupPlayerForSimpleSerial($('#pl-serial'));
titleObj.hide();
} else if (playerCore.getType() == 'seasons_serial') {
playerCore.setupPlayerForSeasonSerial($('#pl-season'), $('#pl-serial'));
titleObj.hide();
} else {
console.debug(playerCore.videoData);
}
});
function continueVideo() {
if (playerCore.getType() == 'simple_serial') {
playerCore.setSerial(data.serial, plSerial);
} else if (playerCore.getType() == 'seasons_serial') {
playerCore.setSeason(data.season, plSeason);
playerCore.setSerial(data.serial, plSerial, data.season);
}
playerObj[0].currentTime = data.time;
playerObj.load();
}
</script>
<div class="modal fade" tabindex="-1" role="dialog" id="modal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Продолжить просмотр?</h4>
</div>
<div class="modal-body">
<b>Время:</b> <span id="mdl-vtime"></span>
<div id="mdl-blk-serial">
<div id="mdl-season" class="hide"><b>Сезон:</b> <span></span></div>
<div id="mdl-serial" class="hide"><b>Серия:</b> <span></span></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" onclick="continueVideo()">Да</button>
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="playerObj.load()">Нет</button>
</div>
</div>
</div>
</div>
<hr>
<div id="pl-season" class="dropdown hide" style="display: inline-block;">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownSeason" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Seasons
<span class="caret"></span>
</button>
<ul id="pl-season-menu" class="dropdown-menu" aria-labelledby="dropdownSeason">
<li><a href="#">Season</a></li>
</ul>
</div>
<div id="pl-serial" class="dropdown hide" style="display: inline-block;">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownSerial" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Serials
<span class="caret"></span>
</button>
<ul id="pl-serial-menu" class="dropdown-menu" aria-labelledby="dropdownSerial">
<li><a href="#">Serial</a></li>
</ul>
</div>
<br id="plbr">
<h2 id="title"></h2>
<br>
<video id="player" class="center-block" controls="controls" preload="none"></video>
<br>
[#include "/fother.inc.ftl"]

View File

@@ -12,12 +12,10 @@ body {
font-weight: bold; font-weight: bold;
} }
.title .logo { .title img {
background-image: url("../favicon.png"); vertical-align: bottom;
width: 2em; height: 1.119em;
display: inline-block; display: inline-block;
background-size: 2em auto;
background-position: 0px center;
} }
.title a { .title a {

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -1,11 +1,9 @@
function PlayerCore(baseDir, playerObj, titleObj, videoData) { function PlayerCore(playerObj, titleObj, videoData) {
this.baseDir = baseDir;
this.playerObj = playerObj; this.playerObj = playerObj;
this.titleObj = titleObj; this.titleObj = titleObj;
this.videoData = videoData; this.videoData = videoData;
this.origDocTitle = document.title; this.origDocTitle = document.title;
this.path = window.location.pathname.substr(baseDir.length);
this.timeLast = 0; this.timeLast = 0;
this.msToTime = function(ms) { this.msToTime = function(ms) {
@@ -23,22 +21,22 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
'm': addZ(_min), 'm': addZ(_min),
'h': addZ(_hr) 'h': addZ(_hr)
}; };
} };
this.setTitle = function(title = videoData.title) { this.setTitle = function(title = videoData.title) {
document.title = title + " :: " + this.origDocTitle; document.title = title + " :: " + this.origDocTitle;
titleObj.text(title); titleObj.text(title);
titleObj.show(); titleObj.show();
} };
this.getType = function() { this.getType = function() {
return this.videoData.type; return this.videoData.type;
} };
this.setupPlayerForOneFilm = function() { this.setupPlayerForOneFilm = function() {
playerObj.attr('src', this.baseDir + videoData.file); playerObj.attr('src', videoData.file);
playerObj.load(); playerObj.load();
} };
this.setupPlayerForSimpleSerial = function(serialBlock) { this.setupPlayerForSimpleSerial = function(serialBlock) {
var menu = serialBlock.find('.dropdown-menu'); var menu = serialBlock.find('.dropdown-menu');
@@ -53,7 +51,7 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
}); });
serialBlock.removeClass('hide'); serialBlock.removeClass('hide');
} };
this.setupPlayerForSeasonSerial = function(seasonBlock, serialBlock) { this.setupPlayerForSeasonSerial = function(seasonBlock, serialBlock) {
var menu = seasonBlock.find('.dropdown-menu'); var menu = seasonBlock.find('.dropdown-menu');
@@ -62,13 +60,13 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
this.videoData.seasons.forEach(function(item, i) { this.videoData.seasons.forEach(function(item, i) {
var aTag = $('<a/>', {'href':'#', 'text':item.title}); var aTag = $('<a/>', {'href':'#', 'text':item.title});
aTag.click(function(){_self.setSeason(i, seasonBlock, serialBlock)}); aTag.click(function(){_self.setSeason(i, seasonBlock, serialBlock)});
var liTag = $('<li/>') var liTag = $('<li/>');
liTag.append(aTag); liTag.append(aTag);
menu.append(liTag); menu.append(liTag);
}); });
seasonBlock.removeClass('hide'); seasonBlock.removeClass('hide');
} };
this.setupPlayer = function() { this.setupPlayer = function() {
var _self = this; var _self = this;
@@ -90,17 +88,17 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
console.debug({'path': _self.path, 'saveTime': save_data}); console.debug({'path': _self.path, 'saveTime': save_data});
} }
}); });
} };
this.setSerial = function(idx, serialBlock, sidx = 0) { this.setSerial = function(idx, serialBlock, sidx = 0) {
var title; var title;
var playerSrc; var playerSrc;
if (this.getType() == 'seasons_serial') { if (this.getType() == 'seasons_serial') {
title = videoData.seasons[sidx].serials[idx].title; title = videoData.seasons[sidx].serials[idx].title;
playerSrc = this.baseDir + videoData.seasons[sidx].serials[idx].file; playerSrc = videoData.seasons[sidx].serials[idx].file;
} else { } else {
title = videoData.serials[idx].title; title = videoData.serials[idx].title;
playerSrc = this.baseDir + videoData.serials[idx].file; playerSrc = videoData.serials[idx].file;
} }
this.setTitle(title); this.setTitle(title);
@@ -110,7 +108,7 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
playerObj.attr('src', playerSrc); playerObj.attr('src', playerSrc);
playerObj.attr('data-serial', idx); playerObj.attr('data-serial', idx);
playerObj.load(); playerObj.load();
} };
this.setSeason = function(idx, seasonBlock, serialBlock) { this.setSeason = function(idx, seasonBlock, serialBlock) {
var title = videoData.seasons[idx].title; var title = videoData.seasons[idx].title;
@@ -124,7 +122,7 @@ function PlayerCore(baseDir, playerObj, titleObj, videoData) {
this.videoData.seasons[idx].serials.forEach(function(item, i) { this.videoData.seasons[idx].serials.forEach(function(item, i) {
var aTag = $('<a/>', {'href':'#', 'text':item.title}); var aTag = $('<a/>', {'href':'#', 'text':item.title});
aTag.click(function(){_self.setSerial(i, serialBlock, idx)}); aTag.click(function(){_self.setSerial(i, serialBlock, idx)});
var liTag = $('<li/>') var liTag = $('<li/>');
liTag.append(aTag); liTag.append(aTag);
menu.append(liTag); menu.append(liTag);
}); });

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" id="ks3logo" viewBox="0 0 492 208" version="1.1" xml:space="preserve" x="0px" y="0px" width="492" height="208">
<g id="node0">
<g id="node1">
<path d="m 149.1667,7.1667 c 22.5,-9.6667 45.1666,-10.8334 58.3333,6.5 C 196.8333,21.3333 23.6667,69 23.6667,69 L 0,69 C 0,69 148.5,7.5 149.1667,7.1667 Z" id="path5" style="fill:#e2e2e2"/>
<path d="m 165.1667,7.1667 c 28.8333,-5.8334 51,-5.3334 54.5,47 1.5,23.3333 -25.1463,28.4274 -31.1667,13 C 185.6667,59 198.25,23.5 172.4344,32.3417 154.3706,38.5285 84,69 84,69 L 16,69 C 16,69 164.5,7.5 165.1667,7.1667 Z" id="path7" style="fill:#c9c9c9"/>
<path d="m 219.5115,52.0346 c 0.0552,0.7009 0.107,1.4116 0.1552,2.1321 1.5,23.3333 -25.1463,28.4274 -31.1667,13 -0.2215,-0.6384 -0.4032,-1.3449 -0.5529,-2.1074" id="path9" style="opacity:0.2;fill:#000000"/>
<path d="M 10,65 94,65 84,69 0,69 10,65 Z" id="path11" style="opacity:0.2;fill:#000000"/>
</g>
<g id="node2">
<path d="M 343.3333,7.1667 C 320.8334,-2.5 298.1666,-3.6667 285,13.6667 295.6666,21.3333 468.3334,69 468.3334,69 L 492,69 C 492,69 344,7.5 343.3333,7.1667 Z" id="path14" style="fill:#e2e2e2"/>
<path d="m 327.5609,7.1667 c -28.8333,-5.8334 -51,-5.3334 -54.5,47 -1.5,23.3333 25.1463,28.4274 31.1667,13 C 307.0609,59 294.4776,23.5 320.2932,32.3417 338.357,38.5285 408.7276,69 408.7276,69 l 68,0 c 0,0 -148.5001,-61.5 -149.1667,-61.8333 z" id="path16" style="fill:#c9c9c9"/>
<path d="m 273.1631,52.0346 c -0.0552,0.7009 -0.1069,1.4116 -0.1552,2.1321 -1.5,23.3333 25.1463,28.4274 31.1667,13 0.2215,-0.6384 0.4033,-1.3449 0.5529,-2.1074" id="path18" style="opacity:0.2;fill:#000000"/>
<path d="m 482,65 -83,0 10,4 83,0 -10,-4 z" id="path20" style="opacity:0.2;fill:#000000"/>
</g>
<g id="node3">
<path d="m 0,69 492,0 0,139 -214,0 -21,-75 -22,0 -21,75 L 0,208 0,69 Z" id="path23" style="fill:#e2e2e2"/>
<path d="m 0,197 217,0 -3,11 -214,0 0,-11 z" id="path25" style="fill:#cbcbcb"/>
<path d="m 275,197 217,0 0,11 -214,0 -3,-11 z" id="path27" style="fill:#cbcbcb"/>
<path d="m 0,69 492,0 0,5 -492,0 0,-5 z" id="path29" style="fill:#fcfcfc"/>
</g>
<g id="node4">
<path d="M 294,101.125 C 294,97.7422 296.7422,95 300.125,95 l 169.75,0 c 3.3828,0 6.125,2.7422 6.125,6.125 l 0,79.75 c 0,3.3828 -2.7422,6.125 -6.125,6.125 l -169.75,0 C 296.7422,187 294,184.2578 294,180.875 l 0,-79.75 z" id="path32" style="fill:#ce504d"/>
<path d="m 398,95 45,0 -92,92 -45,0 92,-92 z" id="path34" style="fill:#e56161"/>
<path d="m 294,174.875 0,6 c 0,3.3828 2.7422,6.125 6.125,6.125 l 169.75,0 c 3.3828,0 6.125,-2.7422 6.125,-6.125 l 0,-6 c 0,3.3828 -2.7422,6.125 -6.125,6.125 l -169.75,0 C 296.7422,181 294,178.2578 294,174.875 Z" id="path36" style="opacity:0.2196;fill:#000000"/>
</g>
<g id="node5">
<path d="M 16,101.125 C 16,97.7422 18.7422,95 22.125,95 l 169.75,0 c 3.3828,0 6.125,2.7422 6.125,6.125 l 0,79.75 c 0,3.3828 -2.7422,6.125 -6.125,6.125 l -169.75,0 C 18.7422,187 16,184.2578 16,180.875 l 0,-79.75 z" id="path39" style="fill:#4e96cc"/>
<path d="m 59.5,187 -19.5,0 92,-92 19,0 -91.5,92 z" id="path41" style="fill:#5ab0e5"/>
<path d="M 158,95 168,95 76.6667,187 66,187 158,95 Z" id="path43" style="fill:#5ab0e5"/>
<path d="m 16,174.875 0,6 c 0,3.3828 2.7422,6.125 6.125,6.125 l 169.75,0 c 3.3828,0 6.125,-2.7422 6.125,-6.125 l 0,-6 c 0,3.3828 -2.7422,6.125 -6.125,6.125 l -169.75,0 C 18.7422,181 16,178.2578 16,174.875 Z" id="path45" style="opacity:0.2196;fill:#000000"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -1,35 +0,0 @@
[#ftl]
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="${rutext}">
<meta name="version" content="${version}">
<title>KinoSearch :: ${rutext}</title>
<link rel="icon" type="image/png" href="${basedir}/favicon.png" >
<link rel="stylesheet" type="text/css" href="${basedir}/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="${basedir}/css/awesome-bootstrap-checkbox.css">
<link rel="stylesheet" type="text/css" href="${basedir}/css/style.css">
<script type="text/javascript" src="${basedir}/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="${basedir}/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1 class="title"><a href="${basedir}/"><span class="logo">&nbsp;</span> KinoSearch</a></h1>
<p class="text-center">${rutext}</p>
<form method="get" action="${basedir}/">
<div class="input-group">
<input class="form-control" type="text" placeholder="Что ищем?" name="search" value="[#if searchtext??]${searchtext}[/#if]"/>
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Ищи!</button>
</span>
</div>
<div class="checkbox checkbox-primary">
<input type="checkbox" id="strong" name="strong" value="1"[#if strong?? && strong = true] checked[/#if]>
<label for="strong">Точное совпадение</label>
</div>
</form>
<div class="text-center">
<a href="${basedir}/about.html">О проекте</a> | <a href="mailto:admin@dmitriymx.ru">Написать отзыв</a>
</div>

View File

@@ -1,62 +0,0 @@
[#ftl]
[#include "/header.inc.html"]
[#if resultsearch??]
<script type="text/javascript">
$(function(){
$('a.spoiler').bind('click',function(){
$(this).next().collapse('toggle');
return false;
});
});
</script>
<hr>
[#if resultsearch?has_content]
[#list resultsearch as kino]
[#if kino.getClass().getSimpleName() == "KinoGroup"]
<div class="panel panel-default">
<a href="#" class="panel-heading spoiler">
<span class="glyphicon glyphicon-chevron-down">&nbsp;</span><b>${kino.name}</b>
</a>
<div class="panel-collapse collapse out">
<div class="panel-body">
[#list kino.kinolist as kino_groupped]
<div class="panel panel-default">
<div class="panel-heading"><b>${kino_groupped.name}</b></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
<a class="btn btn-primary btn-block" href="${kino_groupped.url}" target="_blank">на сайте</a>
</div>
<div class="col-sm-6">
<a class="btn btn-danger btn-block" href="/player/${kino_groupped.getPlayerUrl()}" target="_blank">в плеере</a>
</div>
</div>
</div>
</div>
[/#list]
</div>
</div>
</div>
[#else]
<div class="panel panel-default">
<div class="panel-heading"><b>${kino.name}</b></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
<a class="btn btn-primary btn-block" href="${kino.url}" target="_blank">на сайте</a>
</div>
<div class="col-sm-6">
<a class="btn btn-danger btn-block" href="/player/${kino.getPlayerUrl()}" target="_blank">в плеере</a>
</div>
</div>
</div>
</div>
[/#if]
[/#list]
[#else]
<p>Ничего не найдено =(</p>
[/#if]
[#else]
[#include "/news.inc.html"]
[/#if]
[#include "/fother.inc.html"]

View File

@@ -1,117 +0,0 @@
[#ftl]
[#include "/header.inc.html"]
<script type="text/javascript" src="${basedir}/js/js.cookie-2.1.0.min.js"></script>
<script type="text/javascript" src="${basedir}/js/player.js"></script>
<script type="text/javascript">
var playerCore;
var data;
var plSeason;
var plSerial;
var playerObj;
$(function(){
var video_data = ${json};
var titleObj = $('#title');
plSeason = $('#pl-season');
plSerial = $('#pl-serial');
playerObj = $('#player');
playerCore = new PlayerCore('${basedir}', playerObj, titleObj, video_data);
playerCore.setupPlayer();
// загрузка ранее сохранённых данных
data = Cookies.getJSON(playerCore.path);
if (data != null) {
var fulltime = playerCore.msToTime(data.time * 1000);
$('#mdl-vtime').text(fulltime.h + ':' + fulltime.m + ':' + fulltime.s);
if (playerCore.getType() == 'simple_serial') {
$('#mdl-serial').find('span').text(data.serial + 1);
$('#mdl-serial').removeClass('hide');
} else if (playerCore.getType() == 'seasons_serial') {
$('#mdl-season').find('span').text(data.season + 1);
$('#mdl-season').removeClass('hide');
$('#mdl-serial').find('span').text(data.serial + 1);
$('#mdl-serial').removeClass('hide');
}
$('#modal').modal('show');
}
if (playerCore.getType() == 'one_film') {
playerCore.setTitle();
playerCore.setupPlayerForOneFilm();
} else if (playerCore.getType() == 'simple_serial') {
playerCore.setupPlayerForSimpleSerial($('#pl-serial'));
titleObj.hide();
} else if (playerCore.getType() == 'seasons_serial') {
playerCore.setupPlayerForSeasonSerial($('#pl-season'), $('#pl-serial'));
titleObj.hide();
} else {
console.debug(playerCore.videoData);
}
});
function continueVideo() {
if (playerCore.getType() == 'simple_serial') {
playerCore.setSerial(data.serial, plSerial);
} else if (playerCore.getType() == 'seasons_serial') {
playerCore.setSeason(data.season, plSeason);
playerCore.setSerial(data.serial, plSerial, data.season);
}
playerObj[0].currentTime = data.time;
playerObj.load();
}
</script>
<div class="modal fade" tabindex="-1" role="dialog" id="modal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Продолжить просмотр?</h4>
</div>
<div class="modal-body">
<b>Время:</b> <span id="mdl-vtime"></span>
<div id="mdl-blk-serial">
<div id="mdl-season" class="hide"><b>Сезон:</b> <span></span></div>
<div id="mdl-serial" class="hide"><b>Серия:</b> <span></span></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" onclick="continueVideo()">Да</button>
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="playerObj.load()">Нет</button>
</div>
</div>
</div>
</div>
<hr>
<div id="pl-season" class="dropdown hide" style="display: inline-block;">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownSeason" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Seasons
<span class="caret"></span>
</button>
<ul id="pl-season-menu" class="dropdown-menu" aria-labelledby="dropdownSeason">
<li><a href="#">Season</a></li>
</ul>
</div>
<div id="pl-serial" class="dropdown hide" style="display: inline-block;">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownSerial" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Serials
<span class="caret"></span>
</button>
<ul id="pl-serial-menu" class="dropdown-menu" aria-labelledby="dropdownSerial">
<li><a href="#">Serial</a></li>
</ul>
</div>
<br id="plbr">
<h2 id="title"></h2>
<br>
<video id="player" class="center-block" controls="controls" preload="none"></video>
<br>
[#include "/fother.inc.html"]

View File

@@ -1,28 +0,0 @@
[#ftl]
[#include "/header.inc.html"]
<hr>
<p>
Частенько бывает, что желанный фильм, мультфильм или сериал располагается только на одном каком-то кино-ресурсе.
И хорошо, если этот ресурс был первым в вашем списке ручного поиска кино.<br>
А если нет?<br>
А если у вас таких сайтов 5-10?<br>
А если на всех ваших любимых сайтах не оказалась искомого?<br>
Тогда вы лезете в Google/Яндекс и...
И обязательно напарываетесь на какую-то напичканную рекламой хрень, где еще и вылезет ошибка "фильм не найден".
Поиск "кинца" начинает затягиваться, а желание его посмотреть и вовсе улетучится.
</p>
<p>
Нет, так быть не должно! Если уж решился смотреть кино в онлайне, то пусть это будет комфортно!<br>
Мой проект возьмет всю рутину поиска на себя, а вам остается только выбрать место просмотра и наслаждаться
фильмом/сериалом/еще чем-то.
</p>
<p>
Кинотеатры подбираются так, чтобы в них было поменьше рекламы и побольше нужных фильмов. А в скором времени я и
это ... упрощу =)
</p>
<p>
Приятного просмотра!
</p>
<hr>
<p>Автор: <a href="http://dmitriymx.ru">DmitriyMX</a> / 2015</p>
[#include "/fother.inc.html"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB