amatriain/feedbunch

View on GitHub
FeedBunch-app/app/assets/javascripts/ng-services/ng-LoadFeedsSvc.js.coffee.erb

Summary

Maintainability
Test Coverage
########################################################
# AngularJS service to load feeds
########################################################

angular.module('feedbunch').service 'loadFeedsSvc',
['$rootScope', 'feedsPaginationSvc', 'favicoSvc', 'animationsSvc', 'timerFlagSvc', 'cleanupSvc', 'findSvc',
'csrfTokenSvc',
($rootScope, feedsPaginationSvc, favicoSvc, animationsSvc, timerFlagSvc, cleanupSvc, findSvc,
csrfTokenSvc)->

  # Maximum number of feeds in each page.
  # This MUST match the feeds page size set in the server!
  feeds_page_size = 25

  # Constants for the different operations the web worker can perform
  LOAD_FEED = 'load_feed'
  LOAD_FEEDS = 'load_feeds'
  LOAD_FOLDER_FEEDS = 'load_folder_feeds'

  # CSRF token
  token = csrfTokenSvc.get_token()

  # Web worker to load feeds
  worker = new Worker '<%= asset_path 'workers/load_feeds_worker' %>'
  worker.onmessage = (e) ->
    if e.data.status == 200 || e.data.status == 304
      if e.data.operation == LOAD_FEED
        feed_loaded e.data.params, e.data.response
      else if e.data.operation == LOAD_FEEDS
        feeds_page_loaded e.data.params, e.data.response
      else if e.data.operation == LOAD_FOLDER_FEEDS
        folder_feeds_loaded e.data.params, e.data.response
    else if e.data.status == 401 || e.data.status == 422
      $window.location.href = '/login'
    else
      if e.data.operation == LOAD_FEED
        feed_loading_error e.data.params, e.data.status
      else if e.data.operation == LOAD_FEEDS
        feeds_page_loading_error e.data.params, e.data.status
      else if e.data.operation == LOAD_FOLDER_FEEDS
        folder_feeds_loading_error e.data.params, e.data.status
    $rootScope.$digest()

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after a single feed has been loaded
  #--------------------------------------------
  feed_loaded = (params, response)->
    feed_id =  params.feed_id
    # Remove the flag indicating that this feed is loading
    delete $rootScope.loading_single_feed[feed_id]
    add_feed response
    favicoSvc.update_unread_badge()

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after loading a single feed finishes with an error
  #--------------------------------------------
  feed_loading_error = (params, status)->
    id = params.feed_id
    delete $rootScope.loading_single_feed[id]
    if status == 404
      cleanupSvc.remove_feed id
    else
      timerFlagSvc.start 'error_loading_feeds'

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after a page of feeds has been loaded
  #--------------------------------------------
  feeds_page_loaded = (params, response)->
    feedsPaginationSvc.load_feeds_page page, response.slice()
    feedsPaginationSvc.set_busy false

    # Load the next page of feeds until no more feeds are available
    if response.length < feeds_page_size
      # there are no more pages of feeds to retrieve
      $rootScope.feeds_loaded = true
      feedsPaginationSvc.pagination_finished()
      favicoSvc.update_unread_badge()
      animationsSvc.show_stats()
    else
      # There is probably at least one more page of feeds available
      page = params.page
      load_feeds page

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after loading a page of feeds finishes with an error
  #--------------------------------------------
  feeds_page_loading_error = (params, status)->
    $rootScope.feeds_loaded = true
    feedsPaginationSvc.set_busy false
    if status == 404
      # if a 404 is returned for the first page, there are no feeds at all. Set all unread counts to zero.
      if params.page == 1
        if $rootScope.feeds && $rootScope.feeds?.length > 0
          for feed in $rootScope.feeds
            feed.unread_entries = 0
      # If a 404 is returned in a page >1, there are no more feeds and this is the last page.
      else
        feedsPaginationSvc.pagination_finished()
      favicoSvc.update_unread_badge()
      animationsSvc.show_stats()
    else
      timerFlagSvc.start 'error_loading_feeds'

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after feeds in a folder have been loaded
  #--------------------------------------------
  folder_feeds_loaded = (params, response)->
    delete $rootScope.loading_single_folder_feeds[params.folder_id]
    # Update unread counts with the received feeds. Set the unread counter for any feed in the folder but
    # not in the received JSON to zero.
    folder = findSvc.find_folder params.folder_id
    update_folder_feeds folder, response.slice()

  #--------------------------------------------
  # PRIVATE FUNCTION: Operations after loading feeds in a folder finishes with an error
  #--------------------------------------------
  folder_feeds_loading_error = (params, status)->
    id = params.folder_id
    delete $rootScope.loading_single_folder_feeds[id]
    if status == 404
      # If the server returns a 404, there are no feeds to return; set unread count to zero for all feeds in the folder.
      folder = findSvc.find_folder id
      update_folder_feeds folder, null
    else
      timerFlagSvc.start 'error_loading_folders'

  #--------------------------------------------
  # PRIVATE FUNCTION: Load feeds. Depending on the value of the flag "show_read" it
  # will load all feeds (if true) or only feeds with unread entries (if false).
  #--------------------------------------------
  load_feeds = (page=0)->
    # If busy, do nothing
    return if feedsPaginationSvc.is_busy()

    # Indicate that AJAX request/response cycle is busy so no more calls are done until finished
    feedsPaginationSvc.set_busy true

    page += 1
    worker.postMessage {operation: LOAD_FEEDS, token: token, show_read: $rootScope.show_read, page: page}

  #--------------------------------------------
  # PRIVATE FUNCTION: Load a single feed. Receives its id as argument.
  #--------------------------------------------
  load_feed = (id)->
    # If feed pagination is busy, do nothing
    # This keeps from trying to load a single feed while the list of feeds is loading.
    return if feedsPaginationSvc.is_busy()

    # If this feed is already being loaded, do nothing
    $rootScope.loading_single_feed ||= {}
    return if $rootScope.loading_single_feed[id]

    $rootScope.loading_single_feed[id] = true
    worker.postMessage {operation: LOAD_FEED, token: token, feed_id: id}

  #---------------------------------------------
  # PRIVATE FUNCTION: Push a feed in the feeds array if it isn't already present there.
  #
  # If the feeds array has not been created in the root scope, create it.
  #
  # If the feed is already in the feeds array, its unread_entries attribute is updated instead of
  # pushing it in the array again.
  #---------------------------------------------
  add_feed = (feed)->
    if !$rootScope.feeds || $rootScope.feeds?.length == 0
      $rootScope.feeds = [feed]
    else
      feed_old = findSvc.find_feed feed.id
      if feed_old?
        feed_old.unread_entries = feed.unread_entries
      else
        $rootScope.feeds.push feed

  #--------------------------------------------
  # PRIVATE FUNCTION: Load feeds inside a single folder. Receives the folder as argument.
  #--------------------------------------------
  load_folder_feeds = (folder)->
    # If feeds in this folder are already being loaded, do nothing
    $rootScope.loading_single_folder_feeds ||= {}
    return if $rootScope.loading_single_folder_feeds[folder.id]

    $rootScope.loading_single_folder_feeds[folder.id] = true
    worker.postMessage {operation: LOAD_FOLDER_FEEDS, token: token, folder_id: folder.id, show_read: $rootScope.show_read}

  #---------------------------------------------
  # PRIVATE FUNCTION: Update the feeds and their unread counts, for feeds in a folder.
  #
  # Receives as arguments the folder and an array of feeds.
  #
  # Operations in the scope:
  # The unread_count for each feed passed in the array is updated with the value passed in the array.
  # Those feeds in the folder which are not present in the passed array will have their unread_count set to zero.
  #
  # NOTE.- If a null is passed in the feeds argument, all feeds in the folder will have their unread counts set to zero.
  #---------------------------------------------
  update_folder_feeds = (folder, feeds)->
    # Set unread count for all feeds in the folder to zero, then set the actual received value for each feed.
    # Those feeds not present in the received JSON will be set to zero.
    feeds_in_folder = findSvc.find_folder_feeds folder
    for feed in feeds_in_folder
      feed.unread_entries = 0
    if feeds? && feeds?.length > 0
      for feed in feeds
        add_feed feed

  service =

    #---------------------------------------------
    # Load a single feed
    #---------------------------------------------
    load_feed: load_feed

    #---------------------------------------------
    # Load all feeds
    #---------------------------------------------
    load_feeds: load_feeds

    #---------------------------------------------
    # Load feeds in a single folder via AJAX into the root scope.
    #---------------------------------------------
    load_folder_feeds: (folder)->
      # If passed folder is "all", load all feeds in a paginated fashion.
      if folder=='all' || folder?.id == 'all'
        load_feeds()
      # If any other folder is passed, load feeds in that folder only (not paginated)
      else
        load_folder_feeds folder

  return service
]