<commit_before>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const Vibrant = require('node-vibrant');
const stripTags = require('striptags');
const magnetUri = require('magnet-uri');

const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const DEFAULT_IMAGE = 'https://yts.am/assets/images/website/logo-YTS.svg';

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(clients) {
    this.lineClient = clients.lineClient;
    this.ytsClient = clients.ytsClient;
    this.firebaseClient = clients.firebaseClient;
    this.osClient = clients.osClient;
    this.serialClient = clients.serialClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId).then(profile => {
      this.firebaseClient.addUser(profile);
      const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.\nTry typing "search<space>your movie title" to getting started, or "series<space>tv series title" for streaming TV Series.\n\nHappy watching!`;
      this.lineClient.replyMessage(
        event.replyToken,
        messages.textMessage(greetingMessage)
      );
    });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Watch Now',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        ),
      ];
      const backgroundPalettePromise = Vibrant.from(movie.large_cover_image)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          movie.large_cover_image,
          _.truncate(movie.title, { length: 35, separator: /\W/ }),
          _.truncate(movie.summary, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(carouselMessage).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      case 'link':
        this.processTorrent(replyToken, source, {
          movie: term,
          image: '',
          title: term,
          qty: '',
          custom: true,
        });
        break;
      case 'series':
        this.serialClient.searchSeries(term).then(result => {
          return this.sendSeriesList(replyToken, result);
        });
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  sendSeriesList(replyToken, result) {
    const seriesList = _.slice(
      _.orderBy(result.serials, ['imdb_rating'], ['desc']),
      0,
      10
    ).map(serial => {
      const actionDetails = messages.actionPostbackTemplate(
        'Details',
        qs.stringify({ keyword: 'series-detail', id: serial.id })
      );
      const actionSubscribe = messages.actionPostbackTemplate(
        'Subscribe',
        qs.stringify({ keyword: 'series-subscribe', id: serial.id })
      );
      const actions = [actionDetails, actionSubscribe];
      const schedule =
        serial.airday && serial.airtime
          ? `every ${serial.airday} on ${serial.airtime}`
          : 'Ended';

      const mainText = `${serial.start} - ${serial.end}\n${schedule}`;

      const mainImage = serial.poster_id
        ? this.serialClient.getImageUrl(serial.poster.name)
        : DEFAULT_IMAGE;

      const backgroundPalettePromise = Vibrant.from(mainImage)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          mainImage,
          _.truncate(serial.title, { length: 35, separator: /\W/ }),
          _.truncate(mainText, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(seriesList).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  processTorrent(replyToken, source, params) {
    console.log(params);
    const contentString = params.custom
      ? 'your torrent'
      : `${params.title} (${params.qty})`;
    this.firebaseClient.addWatchSession(source.userId, params).then(result => {
      const unsubscribe = this.firebaseClient
        .getFirestore()
        .doc(`/session/${result.id}`)
        .onSnapshot(doc => {
          if (doc.data().status === 'ready') {
            const sessionData = doc.data();
            unsubscribe();
            let osParams = {
              episode: params.episode,
              season: params.season,
              filename: params.filename,
            };
            if (params.custom) {
              osParams = Object.assign(osParams, {
                filename: doc.data().title,
              });
            } else {
              osParams = Object.assign(osParams, {
                imdbid: params.imdb,
                filesize: params.size,
              });
            }
            this.osClient.getSubsLink(result.id, osParams).then(() => {
              this.lineClient.pushMessage(
                sessionData.userId,
                messages.textMessage(
                  `Watch ${contentString} here\n${result.url}`
                )
              );
            });
          }
        });
    });
    this.lineClient.replyMessage(
      replyToken,
      messages.textMessage(
        `Preparing ${contentString}...\nWe will notify you once the movie is ready`
      )
    );
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', '),
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movieId: movie.id,
                  movie: torr.url,
                  qty: torr.quality,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

      case 'watchlink':
        this.ytsClient.getMovie(parsedData.movieId).then(result => {
          const movie = result.movie;
          const movieData = {
            movie: parsedData.movie,
            image: movie.large_cover_image,
            title: movie.title,
            qty: parsedData.qty,
            imdb: movie.imdb_code,
            size: parsedData.size,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      case 'series-detail':
        this.serialClient
          .seriesDetails(parsedData.id)
          .then(result => this.sendSeriesDetails(replyToken, result.serial));
        break;
      case 'series-watch-link':
        this.serialClient.seriesDetails(parsedData.id).then(result => {
          const series = result.serial;
          const movieData = {
            movie: parsedData.url,
            size: parsedData.size,
            title: `${series.title} S${parsedData.season}·E${parsedData.ep}`,
            season: parsedData.season,
            episode: parsedData.ep,
            imdb: series.imdb_id.split('/').pop(),
            image: series.poster_id
              ? this.serialClient.getImageUrl(series.poster.name)
              : DEFAULT_IMAGE,
            qty: parsedData.qty,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      default:
        break;
    }
  }

  sendSeriesDetails(replyToken, series) {
    const seriesDetailMessages = [];
    if (series.poster_id) {
      seriesDetailMessages.push(
        messages.imageMessage(
          this.serialClient.getImageUrl(series.poster.name),
          this.serialClient.getImageUrl(series.poster.name)
        )
      );
    }
    const textDetails = [];
    if (series.genre) {
      const allGenre = series.genre.map(genre => genre.name).join(', ');
      textDetails.push(`Genre : ${allGenre}`);
    }
    if (series.network) {
      const allNetwork = series.network.map(network => network.name).join(', ');
      textDetails.push(`Network : ${allNetwork}`);
    }
    if (series.description.length > 0) {
      textDetails.push('', stripTags(series.description[0].body));
    }
    seriesDetailMessages.push(messages.textMessage(textDetails.join('\n')));
    const latestEpisode = [];
    let episodeIndex = series.ep.length - 1;
    while (latestEpisode.length < 10) {
      const currentEpi = series.ep[episodeIndex];
      if (currentEpi.torrent && currentEpi.torrent.length > 0) {
        const quality = {};
        _.orderBy(currentEpi.torrent, ['seed']).forEach(torr => {
          if (!quality[torr.quality.name]) {
            quality[torr.quality.name] = { url: torr.value, size: torr.size };
          }
        });
        const actions = _.slice(Object.keys(quality), 0, 3).map(qty => {
          const parsedMagnet = magnetUri.decode(quality[qty].url);
          const trimmedUrlObject = Object.assign(parsedMagnet, {
            announce: [],
            tr: [],
          });
          return messages.actionPostbackTemplate(
            `Watch in ${qty}`,
            qs.stringify({
              keyword: 'series-watch-link',
              id: series.id,
              url: magnetUri.encode(trimmedUrlObject),
              size: quality[qty].size,
              season: currentEpi.season,
              ep: currentEpi.ep,
              qty,
            })
          );
        });
        const episodeButton = messages.carouselColumnTemplate(
          undefined,
          `S${currentEpi.season}·E${currentEpi.ep}`,
          currentEpi.title,
          actions
        );
        latestEpisode.push(episodeButton);
      }
      episodeIndex--;
    }
    const episodeCarrousel = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(latestEpisode, 'rectangle', 'contain')
    );
    seriesDetailMessages.push(episodeCarrousel);
    this.lineClient
      .replyMessage(replyToken, seriesDetailMessages)
      .catch(handleError);
  }
}

module.exports = Handler;
<commit_msg>update new line<commit_after>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const Vibrant = require('node-vibrant');
const stripTags = require('striptags');
const magnetUri = require('magnet-uri');

const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const DEFAULT_IMAGE = 'https://yts.am/assets/images/website/logo-YTS.svg';

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(clients) {
    this.lineClient = clients.lineClient;
    this.ytsClient = clients.ytsClient;
    this.firebaseClient = clients.firebaseClient;
    this.osClient = clients.osClient;
    this.serialClient = clients.serialClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId).then(profile => {
      this.firebaseClient.addUser(profile);
      const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.\n\nTry typing "search<space>your movie title" to getting started, or "series<space>tv series title" for streaming TV Series.\n\nHappy watching!`;
      this.lineClient.replyMessage(
        event.replyToken,
        messages.textMessage(greetingMessage)
      );
    });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Watch Now',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        ),
      ];
      const backgroundPalettePromise = Vibrant.from(movie.large_cover_image)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          movie.large_cover_image,
          _.truncate(movie.title, { length: 35, separator: /\W/ }),
          _.truncate(movie.summary, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(carouselMessage).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      case 'link':
        this.processTorrent(replyToken, source, {
          movie: term,
          image: '',
          title: term,
          qty: '',
          custom: true,
        });
        break;
      case 'series':
        this.serialClient.searchSeries(term).then(result => {
          return this.sendSeriesList(replyToken, result);
        });
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  sendSeriesList(replyToken, result) {
    const seriesList = _.slice(
      _.orderBy(result.serials, ['imdb_rating'], ['desc']),
      0,
      10
    ).map(serial => {
      const actionDetails = messages.actionPostbackTemplate(
        'Details',
        qs.stringify({ keyword: 'series-detail', id: serial.id })
      );
      const actionSubscribe = messages.actionPostbackTemplate(
        'Subscribe',
        qs.stringify({ keyword: 'series-subscribe', id: serial.id })
      );
      const actions = [actionDetails, actionSubscribe];
      const schedule =
        serial.airday && serial.airtime
          ? `every ${serial.airday} on ${serial.airtime}`
          : 'Ended';

      const mainText = `${serial.start} - ${serial.end}\n${schedule}`;

      const mainImage = serial.poster_id
        ? this.serialClient.getImageUrl(serial.poster.name)
        : DEFAULT_IMAGE;

      const backgroundPalettePromise = Vibrant.from(mainImage)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          mainImage,
          _.truncate(serial.title, { length: 35, separator: /\W/ }),
          _.truncate(mainText, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(seriesList).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  processTorrent(replyToken, source, params) {
    console.log(params);
    const contentString = params.custom
      ? 'your torrent'
      : `${params.title} (${params.qty})`;
    this.firebaseClient.addWatchSession(source.userId, params).then(result => {
      const unsubscribe = this.firebaseClient
        .getFirestore()
        .doc(`/session/${result.id}`)
        .onSnapshot(doc => {
          if (doc.data().status === 'ready') {
            const sessionData = doc.data();
            unsubscribe();
            let osParams = {
              episode: params.episode,
              season: params.season,
              filename: params.filename,
            };
            if (params.custom) {
              osParams = Object.assign(osParams, {
                filename: doc.data().title,
              });
            } else {
              osParams = Object.assign(osParams, {
                imdbid: params.imdb,
                filesize: params.size,
              });
            }
            this.osClient.getSubsLink(result.id, osParams).then(() => {
              this.lineClient.pushMessage(
                sessionData.userId,
                messages.textMessage(
                  `Watch ${contentString} here\n${result.url}`
                )
              );
            });
          }
        });
    });
    this.lineClient.replyMessage(
      replyToken,
      messages.textMessage(
        `Preparing ${contentString}...\nWe will notify you once the movie is ready`
      )
    );
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', '),
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movieId: movie.id,
                  movie: torr.url,
                  qty: torr.quality,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

      case 'watchlink':
        this.ytsClient.getMovie(parsedData.movieId).then(result => {
          const movie = result.movie;
          const movieData = {
            movie: parsedData.movie,
            image: movie.large_cover_image,
            title: movie.title,
            qty: parsedData.qty,
            imdb: movie.imdb_code,
            size: parsedData.size,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      case 'series-detail':
        this.serialClient
          .seriesDetails(parsedData.id)
          .then(result => this.sendSeriesDetails(replyToken, result.serial));
        break;
      case 'series-watch-link':
        this.serialClient.seriesDetails(parsedData.id).then(result => {
          const series = result.serial;
          const movieData = {
            movie: parsedData.url,
            size: parsedData.size,
            title: `${series.title} S${parsedData.season}·E${parsedData.ep}`,
            season: parsedData.season,
            episode: parsedData.ep,
            imdb: series.imdb_id.split('/').pop(),
            image: series.poster_id
              ? this.serialClient.getImageUrl(series.poster.name)
              : DEFAULT_IMAGE,
            qty: parsedData.qty,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      default:
        break;
    }
  }

  sendSeriesDetails(replyToken, series) {
    const seriesDetailMessages = [];
    if (series.poster_id) {
      seriesDetailMessages.push(
        messages.imageMessage(
          this.serialClient.getImageUrl(series.poster.name),
          this.serialClient.getImageUrl(series.poster.name)
        )
      );
    }
    const textDetails = [];
    if (series.genre) {
      const allGenre = series.genre.map(genre => genre.name).join(', ');
      textDetails.push(`Genre : ${allGenre}`);
    }
    if (series.network) {
      const allNetwork = series.network.map(network => network.name).join(', ');
      textDetails.push(`Network : ${allNetwork}`);
    }
    if (series.description.length > 0) {
      textDetails.push('', stripTags(series.description[0].body));
    }
    seriesDetailMessages.push(messages.textMessage(textDetails.join('\n')));
    const latestEpisode = [];
    let episodeIndex = series.ep.length - 1;
    while (latestEpisode.length < 10) {
      const currentEpi = series.ep[episodeIndex];
      if (currentEpi.torrent && currentEpi.torrent.length > 0) {
        const quality = {};
        _.orderBy(currentEpi.torrent, ['seed']).forEach(torr => {
          if (!quality[torr.quality.name]) {
            quality[torr.quality.name] = { url: torr.value, size: torr.size };
          }
        });
        const actions = _.slice(Object.keys(quality), 0, 3).map(qty => {
          const parsedMagnet = magnetUri.decode(quality[qty].url);
          const trimmedUrlObject = Object.assign(parsedMagnet, {
            announce: [],
            tr: [],
          });
          return messages.actionPostbackTemplate(
            `Watch in ${qty}`,
            qs.stringify({
              keyword: 'series-watch-link',
              id: series.id,
              url: magnetUri.encode(trimmedUrlObject),
              size: quality[qty].size,
              season: currentEpi.season,
              ep: currentEpi.ep,
              qty,
            })
          );
        });
        const episodeButton = messages.carouselColumnTemplate(
          undefined,
          `S${currentEpi.season}·E${currentEpi.ep}`,
          currentEpi.title,
          actions
        );
        latestEpisode.push(episodeButton);
      }
      episodeIndex--;
    }
    const episodeCarrousel = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(latestEpisode, 'rectangle', 'contain')
    );
    seriesDetailMessages.push(episodeCarrousel);
    this.lineClient
      .replyMessage(replyToken, seriesDetailMessages)
      .catch(handleError);
  }
}

module.exports = Handler;
<|endoftext|>


<commit_before>/**
 * This is the interactive test drive application for JSONCache.
 * It allows the user to have a play around with the features and
 * settings offered by the library.
 *
 * @preserve JSONCache version #VERSION#
 *
 * Author: Jarno Rantanen (first.last@futurice.com)
 *
 * See README.rst at https://github.com/kpuputti/JSONCache
 * for requirements and usage.
 */
/*global $, JSONCache, window */

$(function() {

	"use strict"; // trigger ECMAScript 5 Strict Mode

	var $console = $('#jc-console ol');
	var $clear = $('#jc-clear');
	var $resetConsole = $('#jc-resetConsole');
	var $inputs = $('#jc-settings input[type=range]');
	var consoleContentHeight = 0;

	if (!Modernizr.inputtypes.range) // range input not supported by UA
		$(document.body).addClass('jc-noRangeInput');

	// Left-pads the given string with zeroes to the given length.
	var pad = function(str, length) {
		str = '' + str;
		length = length || 2;
		while (str.length < length)
			str = '0' + str;
		return str;
	};

	// Adds an entry to the "console" on the page.
	var log = function(message, type) {

		var d = new Date();

		var $li = $('
');
		$li.addClass('jc-' + (type || 'neutral'));

		var $ts = $('');
		$ts.text(pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + pad(d.getMilliseconds(), 3) + ' ');

		var $msg = $('');
		$msg.text(message);

		$console.append($li.append($ts, $msg));

		consoleContentHeight += $li.outerHeight();
		$console.scrollTop(Math.max(0, consoleContentHeight - $console.height()));

	};

	// Overrides the default $.ajax delegation to allow fiddling with the responses.
	JSONCache._getJSONProxy = function (url, options) {
		if (Math.random() > JSONCache.settings.successProb)
			window.setTimeout(function() {
				options.error(null, 'error', null);
			}, 1);
		else
			$.ajax(url + '?' + new Date().getTime(), options); // the timestamp in the query string will bypass any browser caches
    };

	// Fetches content from the server using JSONCache.getCachedJSON().
	var fetch = function(url) {

		var date = new Date();

		JSONCache.getCachedJSON(url, {
			success: function(data) {
				var timeDelta = new Date().getTime() - date.getTime();
				log(timeDelta + ' ms => "' + url + '"', 'success');
			},
			ontry: function(tryNumber) {
				log('Fetching, try #' + tryNumber + ' for "' + url + '"');
			},
			onerror: function(jqXHR, textStatus, errorThrown, tryNumber) {
				log('Error on try #' + tryNumber, 'error');
			}
		});

	};

	// Triggers the small fetch.
	$('#jc-fetchSmall').click(function() {
		fetch('small.json');
	});

	// Triggers the large fetch.
	$('#jc-fetchBig').click(function() {
		fetch('big.json');
	});

	// Clears the entire cache.
	$clear.click(function() {

		JSONCache.clear();

		log('JSONCache cleared');

	});

	// Resets the "console" on the page.
	$resetConsole.click(function() {

		$console.html('');
		consoleContentHeight = 0;

		log('Console reset');

	});

	var outputs = {};

	// Synchronize the inputs with their related outputs and the actual settings.
	$inputs.each(function() {

		if (this.name in JSONCache.settings)
			this.value = JSONCache.settings[this.name];

		outputs[this.id] = $('#jc-settings output[for=' + this.id + ']');

		if (!Modernizr.inputtypes.range) // range input not supported by UA
			this.type = 'number';

	}).change(function() {

		outputs[this.id].val(Math.round(this.value * 10) / 10);

		JSONCache.settings[this.name] = parseFloat(this.value, 10);

	}).change();

	// Tell the user we're ready.
	log('Welcome to the JSONCache Test Drive App!');

});<commit_msg>Made the demo UI report on calls to ongiveup as well as to other hooks.<commit_after>/**
 * This is the interactive test drive application for JSONCache.
 * It allows the user to have a play around with the features and
 * settings offered by the library.
 *
 * @preserve JSONCache version #VERSION#
 *
 * Author: Jarno Rantanen (first.last@futurice.com)
 *
 * See README.rst at https://github.com/kpuputti/JSONCache
 * for requirements and usage.
 */
/*global $, JSONCache, window */

$(function() {

	"use strict"; // trigger ECMAScript 5 Strict Mode

	var $console = $('#jc-console ol');
	var $clear = $('#jc-clear');
	var $resetConsole = $('#jc-resetConsole');
	var $inputs = $('#jc-settings input[type=range]');
	var consoleContentHeight = 0;

	if (!Modernizr.inputtypes.range) // range input not supported by UA
		$(document.body).addClass('jc-noRangeInput');

	// Left-pads the given string with zeroes to the given length.
	var pad = function(str, length) {
		str = '' + str;
		length = length || 2;
		while (str.length < length)
			str = '0' + str;
		return str;
	};

	// Adds an entry to the "console" on the page.
	var log = function(message, type) {

		var d = new Date();

		var $li = $('
');
		$li.addClass('jc-' + (type || 'neutral'));

		var $ts = $('');
		$ts.text(pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + pad(d.getMilliseconds(), 3) + ' ');

		var $msg = $('');
		$msg.text(message);

		$console.append($li.append($ts, $msg));

		consoleContentHeight += $li.outerHeight();
		$console.scrollTop(Math.max(0, consoleContentHeight - $console.height()));

	};

	// Overrides the default $.ajax delegation to allow fiddling with the responses.
	JSONCache._getJSONProxy = function (url, options) {
		if (Math.random() > JSONCache.settings.successProb)
			window.setTimeout(function() {
				options.error(null, 'error', null);
			}, 1);
		else
			$.ajax(url + '?' + new Date().getTime(), options); // the timestamp in the query string will bypass any browser caches
    };

	// Fetches content from the server using JSONCache.getCachedJSON().
	var fetch = function(url) {

		var date = new Date();

		JSONCache.getCachedJSON(url, {
			success: function(data) {
				var timeDelta = new Date().getTime() - date.getTime();
				log(timeDelta + ' ms => "' + url + '"', 'success');
			},
			ontry: function(tryNumber) {
				log('Fetching, try #' + tryNumber + ' for "' + url + '"');
			},
			onerror: function(jqXHR, textStatus, errorThrown, tryNumber) {
				log('Error, on try #' + tryNumber);
			},
			ongiveup: function(textStatus) {
				log('Giving up: ' + textStatus, 'error');
			}
		});

	};

	// Triggers the small fetch.
	$('#jc-fetchSmall').click(function() {
		fetch('small.json');
	});

	// Triggers the large fetch.
	$('#jc-fetchBig').click(function() {
		fetch('big.json');
	});

	// Clears the entire cache.
	$clear.click(function() {

		JSONCache.clear();

		log('JSONCache cleared');

	});

	// Resets the "console" on the page.
	$resetConsole.click(function() {

		$console.html('');
		consoleContentHeight = 0;

		log('Console reset');

	});

	var outputs = {};

	// Synchronize the inputs with their related outputs and the actual settings.
	$inputs.each(function() {

		if (this.name in JSONCache.settings)
			this.value = JSONCache.settings[this.name];

		outputs[this.id] = $('#jc-settings output[for=' + this.id + ']');

		if (!Modernizr.inputtypes.range) // range input not supported by UA
			this.type = 'number';

	}).change(function() {

		outputs[this.id].val(Math.round(this.value * 10) / 10);

		JSONCache.settings[this.name] = parseFloat(this.value, 10);

	}).change();

	// Tell the user we're ready.
	log('Welcome to the JSONCache Test Drive App!');

});<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

// ============================================================================
// Starting up...
// ============================================================================

var path = require("path");
var exec = require('child_process').exec;
var fs = require('fs');
var im = require('imagemagick-native');
var mime = require('mime');
var mmm = require('mmmagic');
var Magic = mmm.Magic;
var sourceDir = __dirname;
var outputDir = path.join(__dirname, "res");
var config = require("./_config.js");
var values = require("./_values.js");

var mode = "job";

var USAGE = [
	"",
	"**************************************",
	"densities.js",
	"**************************************",
	"",
	"USAGE:",
	"1) Generate a job file to run:",
	"",
	"\t./job_builder.js <SOURE IMAGES DIRECTORY> <IMAGES OUTPUT DIRECTORY> <OUTPUT JOB FILE>",
	"",
	"2) Make any tweaks to job file prior to running the job.",
	"",
	"3) Then run the job file:",
	"\t./densities.js <JOB FILE>",
	"",
	// "X) Feeling reckless? Want to use it on-the-fly? Do this:",
	// "\t./densities.js <SOURCE DIRECTORY> <OUTPUT DIRECTORY>",
	//"",
	"All output files will go into a <output dir>/<density>/filename.jpg pattern.",
	"E.g.:",
	"/res/drawable-mdpi/filename.png",
	"/res/drawable-hdpi/filename.png",
	"/res/drawable-xhdpi/filename.png",
	"/res/drawable-xxhdpi/filename.png",
	"/res/drawable-xxxhdpi/filename.png",
	""
].join("\n");

console.log(USAGE);
return;

// Setup source and output directories
setupDirectories();

// ============================================================================
// Running the application...
// ============================================================================

if (!(typeof config.files === "undefined") && config.files.length > 0) {
	config.files.forEach(function(item) {
		processImage2(item, path.join(sourceDir, item.file));
	});
} else {
	var message = [
		"",
		"",
		"************************************************************",
		"No files to process!",
		"Please check _config.js and make sure you have files listed!",
		"************************************************************",
		"",
		"",
	].join("\n");
	console.log(message)
	return;
}

// ============================================================================
// Utility/Functions...
// ============================================================================

/**
 * Processes an actual image file.
 */
function processImage(item, filepath) {
	var image = fs.readFileSync(filepath);
	var identify = im.identify({srcData: image, ignoreWarnings: 1});
	var width = identify.width;
	var height = identify.height;
	var quality = 90;

	// Work with any options being set...
	if (fileHasOptions(item)) {
		// If we have specified output dimensions
		if (optionExists("dimensions", item.options)) {
			width = item.options.dimensions[0];
			height = item.options.dimensions[1];
		}
		// If the intended density of the source image has been set
		if (optionExists("sourceDensity", item.options)) {
			for (var density in values.densities) {
				if (density == item.options.sourceDensity.toLowerCase()) {
					width /= values.densities[density];
					height /= values.densities[density];
				}
			}
		}
		// By default the quality is 90, unless specified in the config file
		if (optionExists("quality", item.options)) {
			quality = item.options.quality;
		}
	}

	// Do a little sanitization to the filenames. Also, if the original file
	// is in SVG format, change the file extension to PNG (since we convert)
	// all SVGs to PNG.
	var newFormat = identify.format;
	var outputFilename = item.file.replace(/([\-]|\s+)/, "_");
	if (newFormat == "SVG" || newFormat == "MVG") {
		newFormat = "PNG24";
		outputFilename = outputFilename.replace(path.extname(outputFilename), ".png");
	}

	// Loop through the densities and output the appropriate files...
	for (var density in values.densities) {
		console.log("Generating ", density, "...");
		var newDims = scaleDimensions([width, height], values.densities[density]);
		console.log("Dimensions: ", newDims);
		var convertData = {
			srcData: image,
			width: newDims[0],
			height: newDims[1],
			quality: quality,
			format: newFormat,
			ignoreWarnings: 1
		};
		var resizedImage = im.convert(convertData);
		var outputFile = path.join(outputDir, density, outputFilename);
		console.log("Writing ", outputFile, "...");
		fs.writeFileSync(outputFile, resizedImage, 'binary');
	}
}

/**
 * Checks for source/destination directories and creates the output directories if necessary.
 */
function setupDirectories() {
	if (process.argv.length > 2) {
		var tmpSourcDir = process.argv[2];
		if (fs.existsSync(tmpSourcDir)) {
			sourceDir = tmpSourcDir;
		} else {
			if (!fs.existsSync(sourceDir)) {
				sourceDir = __dirname;
			}
			console.log("ERROR! Specified source directory does not exist! Defaulting to " + __dirname);
		}
	}
	if (process.argv.length > 3) {
		var outputDir = process.argv[3];
	}
	if (!outputDirectoriesExist(outputDir)) {
		var mkdirp = require('mkdirp');
		console.log("Creating output directory structure in: " + outputDir);
		mkdirp.sync(outputDir);
		for (var density in values.densities) {
			fs.mkdirSync(path.join(outputDir, "drawable-"+density), "0777");
		}
	}
}

/**
 * Scales dimensions and returns matrix [width, height] of the new sizes.
 */
function scaleDimensions(dims, scale) {
	var newDims = dims;
	newDims[0] = parseInt(dims[0] * scale);
	newDims[1] = parseInt(dims[1] * scale);
	return newDims;
}

/**
 * Returns whether or not all output directories have been created or not
 */
function outputDirectoriesExist(outputDir) {
	if (!fs.existsSync(outputDir)) {
		return false;
	}
	for (var density in values.densities) {
		if (!fs.existsSync(path.join(outputDir, "drawable-"+density))) {
			return false;
		}
	}
	return true;
}

/**
 * Returns whether the file has any options set.
 */
function fileHasOptions(file) {
	if (typeof file !== "undefined") {
		return file.hasOwnProperty("options");
	}
	return false;
}

function optionExists(property, options) {
	if (typeof options !== "undefined" && options.hasOwnProperty(property)) {
		return true;
	}
	return false;
}

/**
 * Returns whether or not the file should be "trimmed" prior to processing.
 */
function optionsHaveTrim(options) {
	if (typeof options !== "undefined") {
		return options.hasOwnProperty("trim");
	}
}<commit_msg>Added a comment in.<commit_after>#!/usr/bin/env node
"use strict";

// ============================================================================
// Starting up...
// ============================================================================

var path = require("path");
var exec = require('child_process').exec;
var fs = require('fs');
var im = require('imagemagick-native');
var mime = require('mime');
var mmm = require('mmmagic');
var Magic = mmm.Magic;
var sourceDir = __dirname;
var outputDir = path.join(__dirname, "res");
var config = require("./_config.js");
var values = require("./_values.js");

var mode = "job";

var USAGE = [
	"",
	"**************************************",
	"densities.js",
	"**************************************",
	"",
	"USAGE:",
	"1) Generate a job file to run:",
	"",
	"\t./job_builder.js <SOURE IMAGES DIRECTORY> <IMAGES OUTPUT DIRECTORY> <OUTPUT JOB FILE>",
	"",
	"2) Make any tweaks to job file prior to running the job.",
	"",
	"3) Then run the job file:",
	"\t./densities.js <JOB FILE>",
	"",
	// "X) Feeling reckless? Want to use it on-the-fly? Do this:",
	// "\t./densities.js <SOURCE DIRECTORY> <OUTPUT DIRECTORY>",
	//"",
	"All output files will go into a <output dir>/<density>/filename.jpg pattern.",
	"E.g.:",
	"/res/drawable-mdpi/filename.png",
	"/res/drawable-hdpi/filename.png",
	"/res/drawable-xhdpi/filename.png",
	"/res/drawable-xxhdpi/filename.png",
	"/res/drawable-xxxhdpi/filename.png",
	""
].join("\n");

console.log(USAGE);
return;

// Setup source and output directories
setupDirectories();

// ============================================================================
// Running the application...
// ============================================================================

if (!(typeof config.files === "undefined") && config.files.length > 0) {
	config.files.forEach(function(item) {
		processImage2(item, path.join(sourceDir, item.file));
	});
} else {
	var message = [
		"",
		"",
		"************************************************************",
		"No files to process!",
		"Please check _config.js and make sure you have files listed!",
		"************************************************************",
		"",
		"",
	].join("\n");
	console.log(message)
	return;
}

// ============================================================================
// Utility/Functions...
// ============================================================================

/**
 * Processes an actual image file.
 */
function processImage(item, filepath) {
	var image = fs.readFileSync(filepath);
	var identify = im.identify({srcData: image, ignoreWarnings: 1});
	var width = identify.width;
	var height = identify.height;
	var quality = 90;

	// Work with any options being set...
	if (fileHasOptions(item)) {
		// If we have specified output dimensions
		if (optionExists("dimensions", item.options)) {
			width = item.options.dimensions[0];
			height = item.options.dimensions[1];
		}
		// If the intended density of the source image has been set
		if (optionExists("sourceDensity", item.options)) {
			for (var density in values.densities) {
				if (density == item.options.sourceDensity.toLowerCase()) {
					width /= values.densities[density];
					height /= values.densities[density];
				}
			}
		}
		// By default the quality is 90, unless specified in the config file
		if (optionExists("quality", item.options)) {
			quality = item.options.quality;
		}
	}

	// Do a little sanitization to the filenames. Also, if the original file
	// is in SVG format, change the file extension to PNG (since we convert)
	// all SVGs to PNG.
	var newFormat = identify.format;
	var outputFilename = item.file.replace(/([\-]|\s+)/, "_");
	if (newFormat == "SVG" || newFormat == "MVG") {
		newFormat = "PNG24";
		outputFilename = outputFilename.replace(path.extname(outputFilename), ".png");
	}

	// Loop through the densities and output the appropriate files...
	for (var density in values.densities) {
		console.log("Generating ", density, "...");
		var newDims = scaleDimensions([width, height], values.densities[density]);
		console.log("Dimensions: ", newDims);
		var convertData = {
			srcData: image,
			width: newDims[0],
			height: newDims[1],
			quality: quality,
			format: newFormat,
			ignoreWarnings: 1
		};
		var resizedImage = im.convert(convertData);
		var outputFile = path.join(outputDir, density, outputFilename);
		console.log("Writing ", outputFile, "...");
		fs.writeFileSync(outputFile, resizedImage, 'binary');
	}
}

/**
 * Checks for source/destination directories and creates the output directories if necessary.
 */
function setupDirectories() {
	if (process.argv.length > 2) {
		var tmpSourcDir = process.argv[2];
		if (fs.existsSync(tmpSourcDir)) {
			sourceDir = tmpSourcDir;
		} else {
			if (!fs.existsSync(sourceDir)) {
				sourceDir = __dirname;
			}
			console.log("ERROR! Specified source directory does not exist! Defaulting to " + __dirname);
		}
	}
	if (process.argv.length > 3) {
		var outputDir = process.argv[3];
	}
	if (!outputDirectoriesExist(outputDir)) {
		var mkdirp = require('mkdirp');
		console.log("Creating output directory structure in: " + outputDir);
		mkdirp.sync(outputDir);
		for (var density in values.densities) {
			fs.mkdirSync(path.join(outputDir, "drawable-"+density), "0777");
		}
	}
}

/**
 * Scales dimensions and returns matrix [width, height] of the new sizes.
 */
function scaleDimensions(dims, scale) {
	var newDims = dims;
	newDims[0] = parseInt(dims[0] * scale);
	newDims[1] = parseInt(dims[1] * scale);
	return newDims;
}

/**
 * Returns whether or not all output directories have been created or not
 */
function outputDirectoriesExist(outputDir) {
	if (!fs.existsSync(outputDir)) {
		return false;
	}
	for (var density in values.densities) {
		if (!fs.existsSync(path.join(outputDir, "drawable-"+density))) {
			return false;
		}
	}
	return true;
}

/**
 * Returns whether the file has any options set.
 */
function fileHasOptions(file) {
	if (typeof file !== "undefined") {
		return file.hasOwnProperty("options");
	}
	return false;
}

/**
 * Checks if a particular option exists within a file's array of options.
 */
function optionExists(property, options) {
	if (typeof options !== "undefined" && options.hasOwnProperty(property)) {
		return true;
	}
	return false;
}

/**
 * Returns whether or not the file should be "trimmed" prior to processing.
 */
function optionsHaveTrim(options) {
	if (typeof options !== "undefined") {
		return options.hasOwnProperty("trim");
	}
}<|endoftext|>


<commit_before>var QUERY_PATH = 'http://reiseauskunft.insa.de/bin/query.exe/dny?performLocating=2&tpl=stop2json&look_maxno=20';

var request = require('request');
var async = require('async');

var get_departure_times = function (long, lat, callback) {

  // The geolocations are requiered in a specific format
  // without points and only 8 digits
  var formLat = lat.replace('.', '');

  while ( formLat.length < 8 ) {
    formLat += '0';
  }

  // cut of last
  formLat = formLat.substring(0, 8);

  var formLong = long.replace('.', '');

  while (formLong.length < 8) {
    formLong += '0';
  }

  formLong = formLong.substring(0, 8);

  // Build path
  var path = QUERY_PATH + '&look_x=' + formLong + '&look_y=' + formLat;

  request(path, {encoding: null},
    function (err, resp, body) {
      if (err) {
        callback(err, null);
      } else {

        var jsonBody = JSON.parse(body);
        var stations = jsonBody.stops;

        var stationsWithId = [];

        for(i = 0; i < stations.length; i++) {

          // Filter out stations for IC/RB/RE/S-Bahn/U-Bahn
          if ( parseInt(stations[i].prodclass) > 31 ) {
            stationsWithId.push( {id: stations[i].extId, name: stations[i].name} );
          }
        }

        // look for journeys of each stations
        async.map(stationsWithId,
          // for each id
          get_journeys,
          // results saved here
          function(err, times){
            callback(null, times);
          });
      }
  });
};

// get journeys of one station
// productFilter= masking out ICE/IC/RE/RB/S/U

var QUERY_JOURNEYS_PATH = 'http://reiseauskunft.insa.de/bin/stboard.exe/dn?L=.vs_stb&L=.vs_stb.vs_stb&boardType=dep&selectDate=today&productsFilter=0000011111&additionalTime=0&start=yes&requestType=0&outputMode=undefined&maxJourneys=50';

var get_journeys = function (station, callback) {

  var id = station.id;
  var name = station.name;

  // building path with parameters
  var current_date = new Date();
  var string_date = current_date.getHours() + ':' + current_date.getMinutes();

  var path = QUERY_JOURNEYS_PATH + '&input='+ id + '&time=' + string_date;

  // console.log('Path of Request');

  request(path, {encoding: null}, function (err, resp, body) {
    if (err) {
      callback(err, null);
    } else {

      // parse to string so we can work on it
      var bodyString = body.toString('utf-8');

      // FUCK THIS ENCODING SHIT
      bodyString = decodeHtmlEntity(bodyString);

      // remove the object in front of the json
      bodyString = bodyString.replace('journeysObj = ','');

      // get JSON object
      var jsonBody = JSON.parse(bodyString);

      times = [];

      // return if no journeys are present
      if( !jsonBody.hasOwnProperty('journey') )
      {
        callback(null, times);
      }
      else
      {
        var journeys = jsonBody.journey;
        for (i = 0; i < journeys.length; i++) {
          var row = journeys[i];

          times[i] = {
            "line": row.pr.replace(/\s+/g, ' '),
            "direction": row.st,
            "departure": row.ti,
            "delay": row.rt
          };
        }

        callback(null, { station_info: name, departure_times: times });
      }
    }
  });
};

var decodeHtmlEntity = function(str) {
  return str.replace(/&#(\d+);/g, function(match, dec) {
    return String.fromCharCode(dec);
  });
};

exports.get_departure_times = get_departure_times;
<commit_msg>removing journeys that are incomplete<commit_after>var QUERY_PATH = 'http://reiseauskunft.insa.de/bin/query.exe/dny?performLocating=2&tpl=stop2json&look_maxno=20';

var request = require('request');
var async = require('async');

var get_departure_times = function (long, lat, callback) {

  // The geolocations are requiered in a specific format
  // without points and only 8 digits
  var formLat = lat.replace('.', '');

  while ( formLat.length < 8 ) {
    formLat += '0';
  }

  // cut of last
  formLat = formLat.substring(0, 8);

  var formLong = long.replace('.', '');

  while (formLong.length < 8) {
    formLong += '0';
  }

  formLong = formLong.substring(0, 8);

  // Build path
  var path = QUERY_PATH + '&look_x=' + formLong + '&look_y=' + formLat;

  request(path, {encoding: null},
    function (err, resp, body) {
      if (err) {
        callback(err, null);
      } else {

        var jsonBody = JSON.parse(body);
        var stations = jsonBody.stops;

        var stationsWithId = [];

        for(i = 0; i < stations.length; i++) {

          // Filter out stations for IC/RB/RE/S-Bahn/U-Bahn
          if ( parseInt(stations[i].prodclass) > 31 ) {
            stationsWithId.push( {id: stations[i].extId, name: stations[i].name} );
          }
        }

        // look for journeys of each stations
        async.map(stationsWithId,
          // for each id
          get_journeys,
          // results saved here
          function(err, times){
            callback(null, times);
          });
      }
  });
};

// get journeys of one station
// productFilter= masking out ICE/IC/RE/RB/S/U

var QUERY_JOURNEYS_PATH = 'http://reiseauskunft.insa.de/bin/stboard.exe/dn?L=.vs_stb&L=.vs_stb.vs_stb&boardType=dep&selectDate=today&productsFilter=0000011111&additionalTime=0&start=yes&requestType=0&outputMode=undefined&maxJourneys=50';

var get_journeys = function (station, callback) {

  var id = station.id;
  var name = station.name;

  // building path with parameters
  var current_date = new Date();
  var string_date = current_date.getHours() + ':' + current_date.getMinutes();

  var path = QUERY_JOURNEYS_PATH + '&input='+ id + '&time=' + string_date;

  // console.log('Path of Request');

  request(path, {encoding: null}, function (err, resp, body) {
    if (err) {
      callback(err, null);
    } else {

      // parse to string so we can work on it
      var bodyString = body.toString('utf-8');

      // FUCK THIS ENCODING SHIT
      bodyString = decodeHtmlEntity(bodyString);

      // remove the object in front of the json
      bodyString = bodyString.replace('journeysObj = ','');

      // get JSON object
      var jsonBody = JSON.parse(bodyString);

      times = [];

      // return if no journeys are present
      if( !jsonBody.hasOwnProperty('journey') )
      {
        callback(null, times);
      }
      else
      {
        var journeys = jsonBody.journey;
        for (i = 0; i < journeys.length; i++) {
          var row = journeys[i];

          // taking care of wrongly inserted journeys
          if (row.pr !== "" && row.st !== "" && row.ti !== "") {
            times.push( {
              "line": row.pr,
              "direction": row.st,
              "departure": row.ti,
              "delay": row.rt
            } );
          }
        }

        callback(null, { station_info: name, departure_times: times });
      }
    }
  });
};

var decodeHtmlEntity = function(str) {
  return str.replace(/&#(\d+);/g, function(match, dec) {
    return String.fromCharCode(dec);
  });
};

exports.get_departure_times = get_departure_times;
<|endoftext|>


<commit_before>define([
  'lodash',
  'app/core/table_model',
],
function (_, TableModel) {
  'use strict';

  function InfluxSeries(options) {
    this.series = options.series;
    this.alias = options.alias;
    this.annotation = options.annotation;
  }

  var p = InfluxSeries.prototype;

  p.getTimeSeries = function() {
    var output = [];
    var self = this;
    var i, j;

    if (self.series.length === 0) {
      return output;
    }

    _.each(self.series, function(series) {
      var columns = series.columns.length;
      var tags = _.map(series.tags, function(value, key) {
        return key + ': ' + value;
      });

      for (j = 1; j < columns; j++) {
        var seriesName = series.name;
        var columnName = series.columns[j];
        if (columnName !== 'value') {
          seriesName = seriesName + '.' + columnName;
        }

        if (self.alias) {
          seriesName = self._getSeriesName(series, j);
        } else if (series.tags) {
          seriesName = seriesName + ' {' + tags.join(', ') + '}';
        }

        var datapoints = [];
        if (series.values) {
          for (i = 0; i < series.values.length; i++) {
            datapoints[i] = [series.values[i][j], series.values[i][0]];
          }
        }

        output.push({ target: seriesName, datapoints: datapoints});
      }
    });

    return output;
  };

  p._getSeriesName = function(series, index) {
    var regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g;
    var segments = series.name.split('.');

    return this.alias.replace(regex, function(match, g1, g2) {
      var group = g1 || g2;
      var segIndex = parseInt(group, 10);

      if (group === 'm' || group === 'measurement') { return series.name; }
      if (group === 'col') { return series.columns[index]; }
      if (!isNaN(segIndex)) { return segments[segIndex]; }
      if (group.indexOf('tag_') !== 0) { return match; }

      var tag = group.replace('tag_', '');
      if (!series.tags) { return match; }
      return series.tags[tag];
    });
  };

  p.getAnnotations = function () {
    var list = [];
    var self = this;

    _.each(this.series, function (series) {
      var titleCol = null;
      var timeCol = null;
      var tagsCol = null;
      var textCol = null;

      _.each(series.columns, function(column, index) {
        if (column === 'time') { timeCol = index; return; }
        if (column === 'sequence_number') { return; }
        if (!titleCol) { titleCol = index; }
        if (column === self.annotation.titleColumn) { titleCol = index; return; }
        if (column === self.annotation.tagsColumn) { tagsCol = index; return; }
        if (column === self.annotation.textColumn) { textCol = index; return; }
      });

      _.each(series.values, function (value) {
        var data = {
          annotation: self.annotation,
          time: + new Date(value[timeCol]),
          title: value[titleCol],
          tags: value[tagsCol],
          text: value[textCol]
        };

        list.push(data);
      });
    });

    return list;
  };

  p.getTable = function() {
    var table = new TableModel.default();
    var self = this;
    var i, j;

    if (self.series.length === 0) {
      return table;
    }

    _.each(self.series, function(series, seriesIndex) {

      if (seriesIndex === 0) {
        table.columns.push({text: 'Time', type: 'time'});
        _.each(_.keys(series.tags), function(key) {
          table.columns.push({text: key});
        });
        for (j = 1; j < series.columns.length; j++) {
          table.columns.push({text: series.columns[j]});
        }
      }

      if (series.values) {
        for (i = 0; i < series.values.length; i++) {
          var values = series.values[i];
          var reordered = [values[0]];
          if (series.tags) {
            for (var key in series.tags) {
              if (series.tags.hasOwnProperty(key)) {
                reordered.push(series.tags[key]);
              }
            }
          }
          for (j = 1; j < values.length; j++) {
            reordered.push(values[j]);
          }
          table.rows.push(reordered);
        }
      }
    });

    return table;
  };

  return InfluxSeries;
});
<commit_msg>Allow to show multiple tags with annotations with InfluxDB datasource<commit_after>define([
  'lodash',
  'app/core/table_model',
],
function (_, TableModel) {
  'use strict';

  function InfluxSeries(options) {
    this.series = options.series;
    this.alias = options.alias;
    this.annotation = options.annotation;
  }

  var p = InfluxSeries.prototype;

  p.getTimeSeries = function() {
    var output = [];
    var self = this;
    var i, j;

    if (self.series.length === 0) {
      return output;
    }

    _.each(self.series, function(series) {
      var columns = series.columns.length;
      var tags = _.map(series.tags, function(value, key) {
        return key + ': ' + value;
      });

      for (j = 1; j < columns; j++) {
        var seriesName = series.name;
        var columnName = series.columns[j];
        if (columnName !== 'value') {
          seriesName = seriesName + '.' + columnName;
        }

        if (self.alias) {
          seriesName = self._getSeriesName(series, j);
        } else if (series.tags) {
          seriesName = seriesName + ' {' + tags.join(', ') + '}';
        }

        var datapoints = [];
        if (series.values) {
          for (i = 0; i < series.values.length; i++) {
            datapoints[i] = [series.values[i][j], series.values[i][0]];
          }
        }

        output.push({ target: seriesName, datapoints: datapoints});
      }
    });

    return output;
  };

  p._getSeriesName = function(series, index) {
    var regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g;
    var segments = series.name.split('.');

    return this.alias.replace(regex, function(match, g1, g2) {
      var group = g1 || g2;
      var segIndex = parseInt(group, 10);

      if (group === 'm' || group === 'measurement') { return series.name; }
      if (group === 'col') { return series.columns[index]; }
      if (!isNaN(segIndex)) { return segments[segIndex]; }
      if (group.indexOf('tag_') !== 0) { return match; }

      var tag = group.replace('tag_', '');
      if (!series.tags) { return match; }
      return series.tags[tag];
    });
  };

  p.getAnnotations = function () {
    var list = [];
    var self = this;

    _.each(this.series, function (series) {
      var titleCol = null;
      var timeCol = null;
      var tagsCol = [];
      var textCol = null;

      _.each(series.columns, function(column, index) {
        if (column === 'time') { timeCol = index; return; }
        if (column === 'sequence_number') { return; }
        if (!titleCol) { titleCol = index; }
        if (column === self.annotation.titleColumn) { titleCol = index; return; }
        if (self.annotation.tagsColumn.includes(column)) { tagsCol.push(index); return; }
        if (column === self.annotation.textColumn) { textCol = index; return; }
      });

      _.each(series.values, function (value) {
        var data = {
          annotation: self.annotation,
          time: + new Date(value[timeCol]),
          title: value[titleCol],
          tags: tagsCol.map(function(t) { return value[t]; }),
          text: value[textCol]
        };

        list.push(data);
      });
    });

    return list;
  };

  p.getTable = function() {
    var table = new TableModel.default();
    var self = this;
    var i, j;

    if (self.series.length === 0) {
      return table;
    }

    _.each(self.series, function(series, seriesIndex) {

      if (seriesIndex === 0) {
        table.columns.push({text: 'Time', type: 'time'});
        _.each(_.keys(series.tags), function(key) {
          table.columns.push({text: key});
        });
        for (j = 1; j < series.columns.length; j++) {
          table.columns.push({text: series.columns[j]});
        }
      }

      if (series.values) {
        for (i = 0; i < series.values.length; i++) {
          var values = series.values[i];
          var reordered = [values[0]];
          if (series.tags) {
            for (var key in series.tags) {
              if (series.tags.hasOwnProperty(key)) {
                reordered.push(series.tags[key]);
              }
            }
          }
          for (j = 1; j < values.length; j++) {
            reordered.push(values[j]);
          }
          table.rows.push(reordered);
        }
      }
    });

    return table;
  };

  return InfluxSeries;
});
<|endoftext|>


<commit_before>const gep = require('./dev.js')

const scope = {
  radius: 3,
  unit: 'm²',
}
const global = {
  square: function (n) {
    return n * n
  },
  fixed: function (numObj, num) {
    return numObj.toFixed(num)
  },
}

const expr = '_.fixed(Math.PI + _.square(radius), 2) + unit'
const func = gep(expr)
console.log(func.toString())
const res = func(scope, global)
console.log(res)
<commit_msg>Update demo<commit_after>const Gep = require('./dev.js')
const gep = new Gep()

const scope = {
  radius: 3,
  unit: 'm²',
}
const global = {
  square (n) {
    return n * n
  },
  fixed (numObj, num) {
    return numObj.toFixed(num)
  },
}

const expr = '_.fixed(Math.PI + _.square(radius), 2) + unit'
const parsed = gep.parse(expr)
console.log(parsed)
const func = gep.make(parsed)
console.log(func.toString())
const res = func(scope, global)
console.log(res)
<|endoftext|>


<commit_before>
var Yo;

Yo = function(ns) {
  // namespace.loadedState.tooltip.{
  //    loaded: boolean
  //    loadedFunc: function
  //    dependedBy: [string],
  //    dependencies: [string]
  // }
  ns.loadedState = {};
  ns.scripts = {};

  var add = function() {

    // Main params
    var scriptName;
    var scriptDependencies = [];
    var scriptCallback;

    var hasNoDependencies = true;

    var pushFunction = function() {
      ns.loadedState[scriptName].loaded = true;
      ns.loadedState[scriptName].loadedFunc = function() {
        console.log('already loaded');
      };
      ns.scripts[scriptName] = scriptCallback.apply(null, scriptDependencies.map(function(_scriptName) {
        return ns.scripts[_scriptName];
      }));
    };

    // Check and match the argument length
    // 3: String, Array, Function
    // 2: String, Function
    if(arguments && arguments.length > 2) {
      scriptName = arguments[0].toLowerCase();
      scriptDependencies = arguments[1];
      scriptCallback = arguments[2];
      hasNoDependencies = scriptDependencies.length < 1;
    }
    else if(typeof arguments[0] === 'string' && typeof arguments[1] === 'function') {
      scriptName = arguments[0].toLowerCase();
      scriptCallback = arguments[1];
    }
    else {
      console.log('incorrect params added', arguments);
      return false;
    }

    if (hasNoDependencies) {
      // Save the script straight away
      ns.scripts[scriptName] = scriptCallback();
      // ns.loadedState[scriptName] = {};
      // 1. save to ns.loadedState if not done already
      if(!ns.loadedState[scriptName]) {
        ns.loadedState[scriptName] = {
          loaded: true,
          dependedBy: [],
          dependencies: []
        }
      }
      // 2. if ns.loadedState exists
      else {
        //    a) set loaded = true
        //    b) loop dependedBy entries and remove itself from each one
        //    c) if it is the last dependBy then run it's activate function

        ns.loadedState[scriptName].loaded = true;

        ns.loadedState[scriptName].dependedBy.forEach(function(dependByScriptName) {
          // remove script from dependedBy
          for(var i = 0; i < ns.loadedState[scriptName].dependedBy.length; i++) {
            if (ns.loadedState[dependByScriptName].dependencies[i] === scriptName) {
              ns.loadedState[dependByScriptName].dependencies.splice(i, 1);
              ns.loadedState[scriptName].dependedBy.splice(i, 1);
              break;
            }
          }

          if (ns.loadedState[dependByScriptName].dependencies.length < 1) {
            ns.loadedState[dependByScriptName].loadedFunc();
          }
        });
      }
    }
    else {
      // if has dependencies
      // 1. save to ns.loadedState {
      //    loaded: false,
      //    loadedFunc: activateFunction,
      //    dependencies: [string]
      // }
      if(!ns.loadedState[scriptName]) {
        ns.loadedState[scriptName] = {
          loaded: false,
          loadedFunc: pushFunction,
          dependedBy: [],
          dependencies: scriptDependencies
        }
      }
      else {
        ns.loadedState[scriptName].loadedFunc = pushFunction;
        ns.loadedState[scriptName].dependencies = scriptDependencies;
      }

      // 2. check if each dependency exists and either
      //    a) check if is loaded
      scriptDependencies.forEach(function(dependencyScriptName) {
        if(!ns.loadedState[dependencyScriptName]) {
          ns.loadedState[dependencyScriptName] = {
            loaded: false,
            loadedFunc: function(){},
            dependedBy: [],
            dependencies: []
          }
        }

        // Check if dependency is dependent on this script
        // Circular dependency
        for (var i = 0; i < ns.loadedState[dependencyScriptName].dependencies.length; i++) {
          if(ns.loadedState[dependencyScriptName].dependencies[i] === scriptName) {
            console.log('ERROR: You have 1 or more scripts with circular dependencies');
            console.log('SCRIPT: ' + scriptName + ', DEPENDENCY: ' + dependencyScriptName);
            break;
          }
        }

        if(!ns.loadedState[dependencyScriptName].loaded) {
          ns.loadedState[dependencyScriptName].dependedBy.push(scriptName);
        }
        else {
          ns.loadedState[scriptName].loadedFunc();
        }
      });

      // 3. if all dependencies are loaded then
      //    a) save the script straight away
      //    b) save
    }
  };

  return {
    add: add
  }
}(namespace);<commit_msg>changed comment for already loaded console.log<commit_after>
var Yo;

Yo = function(ns) {
  // namespace.loadedState.tooltip.{
  //    loaded: boolean
  //    loadedFunc: function
  //    dependedBy: [string],
  //    dependencies: [string]
  // }
  ns.loadedState = {};
  ns.scripts = {};

  var add = function() {

    // Main params
    var scriptName;
    var scriptDependencies = [];
    var scriptCallback;

    var hasNoDependencies = true;

    var pushFunction = function() {
      ns.loadedState[scriptName].loaded = true;
      ns.loadedState[scriptName].loadedFunc = function() {
        console.log(scriptName + ' called and already loaded,  this needs cleaning up really :-S');
      };
      ns.scripts[scriptName] = scriptCallback.apply(null, scriptDependencies.map(function(_scriptName) {
        return ns.scripts[_scriptName];
      }));
    };

    // Check and match the argument length
    // 3: String, Array, Function
    // 2: String, Function
    if(arguments && arguments.length > 2) {
      scriptName = arguments[0].toLowerCase();
      scriptDependencies = arguments[1];
      scriptCallback = arguments[2];
      hasNoDependencies = scriptDependencies.length < 1;
    }
    else if(typeof arguments[0] === 'string' && typeof arguments[1] === 'function') {
      scriptName = arguments[0].toLowerCase();
      scriptCallback = arguments[1];
    }
    else {
      console.log('incorrect params added', arguments);
      return false;
    }

    if (hasNoDependencies) {
      // Save the script straight away
      ns.scripts[scriptName] = scriptCallback();
      // ns.loadedState[scriptName] = {};
      // 1. save to ns.loadedState if not done already
      if(!ns.loadedState[scriptName]) {
        ns.loadedState[scriptName] = {
          loaded: true,
          dependedBy: [],
          dependencies: []
        }
      }
      // 2. if ns.loadedState exists
      else {
        //    a) set loaded = true
        //    b) loop dependedBy entries and remove itself from each one
        //    c) if it is the last dependBy then run it's activate function

        ns.loadedState[scriptName].loaded = true;

        ns.loadedState[scriptName].dependedBy.forEach(function(dependByScriptName) {
          // remove script from dependedBy
          for(var i = 0; i < ns.loadedState[scriptName].dependedBy.length; i++) {
            if (ns.loadedState[dependByScriptName].dependencies[i] === scriptName) {
              ns.loadedState[dependByScriptName].dependencies.splice(i, 1);
              ns.loadedState[scriptName].dependedBy.splice(i, 1);
              break;
            }
          }

          if (ns.loadedState[dependByScriptName].dependencies.length < 1) {
            ns.loadedState[dependByScriptName].loadedFunc();
          }
        });
      }
    }
    else {
      // if has dependencies
      // 1. save to ns.loadedState {
      //    loaded: false,
      //    loadedFunc: activateFunction,
      //    dependencies: [string]
      // }
      if(!ns.loadedState[scriptName]) {
        ns.loadedState[scriptName] = {
          loaded: false,
          loadedFunc: pushFunction,
          dependedBy: [],
          dependencies: scriptDependencies
        }
      }
      else {
        ns.loadedState[scriptName].loadedFunc = pushFunction;
        ns.loadedState[scriptName].dependencies = scriptDependencies;
      }

      // 2. check if each dependency exists and either
      //    a) check if is loaded
      scriptDependencies.forEach(function(dependencyScriptName) {
        if(!ns.loadedState[dependencyScriptName]) {
          ns.loadedState[dependencyScriptName] = {
            loaded: false,
            loadedFunc: function(){},
            dependedBy: [],
            dependencies: []
          }
        }

        // Check if dependency is dependent on this script
        // Circular dependency
        for (var i = 0; i < ns.loadedState[dependencyScriptName].dependencies.length; i++) {
          if(ns.loadedState[dependencyScriptName].dependencies[i] === scriptName) {
            console.log('ERROR: You have 1 or more scripts with circular dependencies');
            console.log('SCRIPT: ' + scriptName + ', DEPENDENCY: ' + dependencyScriptName);
            break;
          }
        }

        if(!ns.loadedState[dependencyScriptName].loaded) {
          ns.loadedState[dependencyScriptName].dependedBy.push(scriptName);
        }
        else {
          // 3. if all dependencies are loaded then
          ns.loadedState[scriptName].loadedFunc();
        }
      });
    }
  };

  return {
    add: add
  }
}(namespace);<|endoftext|>


<commit_before>/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var async = require('async');
var running = require('is-running');

var findRootId = function(system) {
  var r = _.find(system.topology.containers, function(c) { return c.type === 'blank-container'; });
  return r ? r.id : '10';
};

var readPidList = function(cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var pidList = [];

  fs.readdir(dir, function(err, files) {
    if (err) { return cb(err); }
    async.filter(files, function(file, callback) { callback(file.indexOf('.pid') > 0); }, function(pidFiles) {
      _.each(pidFiles, function(pidFile) {
        pidList.push(pidFile.replace('.pid', ''));
      });
      cb(null, pidList);
    });
  });
};

var purgeDeadProcesses = function (pidList, cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var livePids = [];

  if (pidList.length === 0) {
    return cb(null, livePids);
  }

  async.eachSeries(pidList, function(pid, next) {
    if (running(parseInt(pid, 10))) {
      livePids.push(pid);
    }
    else {
      try {
        fs.unlinkSync(path.join(dir, pid + '.pid'));
      }
      catch (e) {
      }
    }
    next();
  }, function(err) {
    cb(err, livePids);
  });
};

/**
 * the pid file contains JSON of the following format
 * {
 *   pid: '',
 *   containerId: '',
 *   containerDefinitionId: ''
 * }
 */
var readDetails = function(pidList, cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var details = [];

  if (pidList.length === 0) {
    return cb(null, details);
  }

  async.eachSeries(pidList, function(pid, next) {
    fs.readFile(path.join(dir, pid + '.pid'), 'utf8', function(err, data) {
      try {
        details.push(JSON.parse(data));
        next();
      }
      catch (err) {
        next(err);
      }
    });
  }, function(err) {
    cb(err, details);
  });
};

var getDeployedDetails = function(cb) {
  readPidList(function(err, pidListFull) {
    if (err) { return cb(err); }
    purgeDeadProcesses(pidListFull, function(err, pidList) {
      if (err) { return cb(err); }
      readDetails(pidList, function(err, details) {
        cb(err, details);
      });
    });
  });
};

module.exports = function processContainersAnalyze(config, original, result, cb) {
  var rootId = findRootId(original);
  debugger;

  getDeployedDetails(function(err, details) {
    debugger;
    if (err) { return cb(err); }
    _.each(details, function(detail) {
      if (original.topology.containers[detail.containerId]) {
        result.topology.containers[detail.containerId] = _.cloneDeep(original.topology.containers[detail.containerId]);
        result.topology.containers[rootId].contains.push(detail.containerId);
        result.topology.containers[detail.containerId].containedBy = rootId;

        var def = _.find(original.containerDefinitions, function(cdef) { return cdef.id === detail.containerDefinitionId; });
        result.containerDefinitions.push(_.cloneDeep(def));
      }
    });
    cb(null, result);
  });
};

<commit_msg>added pid to analysis for process containers<commit_after>/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var async = require('async');
var running = require('is-running');

var findRootId = function(system) {
  var r = _.find(system.topology.containers, function(c) { return c.type === 'blank-container'; });
  return r ? r.id : '10';
};

var readPidList = function(cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var pidList = [];

  fs.readdir(dir, function(err, files) {
    if (err) { return cb(err); }
    async.filter(files, function(file, callback) { callback(file.indexOf('.pid') > 0); }, function(pidFiles) {
      _.each(pidFiles, function(pidFile) {
        pidList.push(pidFile.replace('.pid', ''));
      });
      cb(null, pidList);
    });
  });
};

var purgeDeadProcesses = function (pidList, cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var livePids = [];

  if (pidList.length === 0) {
    return cb(null, livePids);
  }

  async.eachSeries(pidList, function(pid, next) {
    if (running(parseInt(pid, 10))) {
      livePids.push(pid);
    }
    else {
      try {
        fs.unlinkSync(path.join(dir, pid + '.pid'));
      }
      catch (e) {
      }
    }
    next();
  }, function(err) {
    cb(err, livePids);
  });
};

/**
 * the pid file contains JSON of the following format
 * {
 *   pid: '',
 *   containerId: '',
 *   containerDefinitionId: ''
 * }
 */
var readDetails = function(pidList, cb) {
  var dir = path.join(process.env.HOME, '/.nscale/data');
  var details = [];

  if (pidList.length === 0) {
    return cb(null, details);
  }

  async.eachSeries(pidList, function(pid, next) {
    fs.readFile(path.join(dir, pid + '.pid'), 'utf8', function(err, data) {
      try {
        details.push(JSON.parse(data));
        next();
      }
      catch (err) {
        next(err);
      }
    });
  }, function(err) {
    cb(err, details);
  });
};

var getDeployedDetails = function(cb) {
  readPidList(function(err, pidListFull) {
    if (err) { return cb(err); }
    purgeDeadProcesses(pidListFull, function(err, pidList) {
      if (err) { return cb(err); }
      readDetails(pidList, function(err, details) {
        cb(err, details);
      });
    });
  });
};

module.exports = function processContainersAnalyze(config, original, result, cb) {
  var rootId = findRootId(original);
  debugger;

  getDeployedDetails(function(err, details) {
    debugger;
    if (err) { return cb(err); }
    _.each(details, function(detail) {
      if (original.topology.containers[detail.containerId]) {
        result.topology.containers[detail.containerId] = _.cloneDeep(original.topology.containers[detail.containerId]);
        result.topology.containers[rootId].contains.push(detail.containerId);
        result.topology.containers[detail.containerId].containedBy = rootId;
        result.topology.containers[detail.containerId].pid = detail.pid;

        var def = _.find(original.containerDefinitions, function(cdef) { return cdef.id === detail.containerDefinitionId; });
        result.containerDefinitions.push(_.cloneDeep(def));
      }
    });
    cb(null, result);
  });
};

<|endoftext|>


<commit_before>var SqlString = exports;

SqlString.escape = function(val, stringifyObjects, timeZone) {
  if (val === undefined || val === null) {
    return 'NULL';
  }

  switch (typeof val) {
    case 'boolean': return (val) ? 'true' : 'false';
    case 'number': return val+'';
  }

  if (val instanceof Date) {
    val = SqlString.dateToString(val, timeZone || "Z");
  }

  if (Buffer.isBuffer(val)) {
    return SqlString.bufferToString(val);
  }

  if (Array.isArray(val)) {
    return SqlString.arrayToList(val, timeZone);
  }

  if (typeof val === 'object') {
    if (stringifyObjects) {
      val = val.toString();
    } else {
      return SqlString.objectToValues(val, timeZone);
    }
  }

  val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
    switch(s) {
      case "\0": return "\\0";
      case "\n": return "\\n";
      case "\r": return "\\r";
      case "\b": return "\\b";
      case "\t": return "\\t";
      case "\x1a": return "\\Z";
      default: return "\\"+s;
    }
  });
  return "'"+val+"'";
};

SqlString.arrayToList = function(array, timeZone) {
  return array.map(function(v) {
    if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')';
    return SqlString.escape(v, true, timeZone);
  }).join(', ');
};

SqlString.format = function(sql, values, timeZone) {
  values = [].concat(values);

  return sql.replace(/\?/g, function(match) {
    if (!values.length) {
      return match;
    }

    return SqlString.escape(values.shift(), false, timeZone);
  });
};

SqlString.dateToString = function(date, timeZone) {
  var dt = new Date(date);

  if (timeZone != 'local') {
    tz = convertTimezone(timeZone);

    dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
    if (tz !== false) {
      dt.setTime(dt.getTime() + (tz * 60000));
    }
  }

  var year   = dt.getFullYear();
  var month  = zeroPad(dt.getMonth() + 1);
  var day    = zeroPad(dt.getDate());
  var hour   = zeroPad(dt.getHours());
  var minute = zeroPad(dt.getMinutes());
  var second = zeroPad(dt.getSeconds());

  return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
};

SqlString.bufferToString = function(buffer) {
  var hex = '';
  try {
    hex = buffer.toString('hex');
  } catch (err) {
    // node v0.4.x does not support hex / throws unknown encoding error
    for (var i = 0; i < buffer.length; i++) {
      var byte = buffer[i];
      hex += zeroPad(byte.toString(16));
    }
  }

  return "X'" + hex+ "'";
};

SqlString.objectToValues = function(object, timeZone) {
  var values = [];
  for (var key in object) {
    var value = object[key];
    if(typeof value === 'function') {
      continue;
    }

    values.push('`' + key + '` = ' + SqlString.escape(value, true, timeZone));
  }

  return values.join(', ');
};

function zeroPad(number) {
  return (number < 10) ? '0' + number : number;
}

function convertTimezone(tz) {
  if (tz == "Z") return 0;

  var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
  if (m) {
    return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
  }
  return false;
}
<commit_msg>Add mysql.escapeId to escape identifiers (closes #342)<commit_after>var SqlString = exports;

SqlString.escapeId = function (val) {
  return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
};

SqlString.escape = function(val, stringifyObjects, timeZone) {
  if (val === undefined || val === null) {
    return 'NULL';
  }

  switch (typeof val) {
    case 'boolean': return (val) ? 'true' : 'false';
    case 'number': return val+'';
  }

  if (val instanceof Date) {
    val = SqlString.dateToString(val, timeZone || "Z");
  }

  if (Buffer.isBuffer(val)) {
    return SqlString.bufferToString(val);
  }

  if (Array.isArray(val)) {
    return SqlString.arrayToList(val, timeZone);
  }

  if (typeof val === 'object') {
    if (stringifyObjects) {
      val = val.toString();
    } else {
      return SqlString.objectToValues(val, timeZone);
    }
  }

  val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
    switch(s) {
      case "\0": return "\\0";
      case "\n": return "\\n";
      case "\r": return "\\r";
      case "\b": return "\\b";
      case "\t": return "\\t";
      case "\x1a": return "\\Z";
      default: return "\\"+s;
    }
  });
  return "'"+val+"'";
};

SqlString.arrayToList = function(array, timeZone) {
  return array.map(function(v) {
    if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')';
    return SqlString.escape(v, true, timeZone);
  }).join(', ');
};

SqlString.format = function(sql, values, timeZone) {
  values = [].concat(values);

  return sql.replace(/\?/g, function(match) {
    if (!values.length) {
      return match;
    }

    return SqlString.escape(values.shift(), false, timeZone);
  });
};

SqlString.dateToString = function(date, timeZone) {
  var dt = new Date(date);

  if (timeZone != 'local') {
    tz = convertTimezone(timeZone);

    dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
    if (tz !== false) {
      dt.setTime(dt.getTime() + (tz * 60000));
    }
  }

  var year   = dt.getFullYear();
  var month  = zeroPad(dt.getMonth() + 1);
  var day    = zeroPad(dt.getDate());
  var hour   = zeroPad(dt.getHours());
  var minute = zeroPad(dt.getMinutes());
  var second = zeroPad(dt.getSeconds());

  return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
};

SqlString.bufferToString = function(buffer) {
  var hex = '';
  try {
    hex = buffer.toString('hex');
  } catch (err) {
    // node v0.4.x does not support hex / throws unknown encoding error
    for (var i = 0; i < buffer.length; i++) {
      var byte = buffer[i];
      hex += zeroPad(byte.toString(16));
    }
  }

  return "X'" + hex+ "'";
};

SqlString.objectToValues = function(object, timeZone) {
  var values = [];
  for (var key in object) {
    var value = object[key];
    if(typeof value === 'function') {
      continue;
    }

    values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone));
  }

  return values.join(', ');
};

function zeroPad(number) {
  return (number < 10) ? '0' + number : number;
}

function convertTimezone(tz) {
  if (tz == "Z") return 0;

  var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
  if (m) {
    return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
  }
  return false;
}
<|endoftext|>