aboutsummaryrefslogtreecommitdiff
path: root/xbmc/platform/darwin/tvos/TVOSTopShelf.mm
blob: fb4770f5cd87760d37d1b20edbf22d0e6a68db30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*
 *  Copyright (C) 2010-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#import "TVOSTopShelf.h"

#include "DatabaseManager.h"
#include "FileItem.h"
#include "FileItemList.h"
#include "ServiceBroker.h"
#include "application/Application.h"
#include "filesystem/File.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "messaging/ApplicationMessenger.h"
#include "tvosShared.h"
#include "utils/Base64.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include "video/VideoDatabase.h"
#include "video/VideoInfoTag.h"
#include "video/VideoThumbLoader.h"
#include "video/dialogs/GUIDialogVideoInfo.h"
#include "video/windows/GUIWindowVideoBase.h"
#include "video/windows/GUIWindowVideoNav.h"

#include "platform/darwin/ios-common/DarwinEmbedUtils.h"

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <mach/mach_host.h>
#import <sys/sysctl.h>

static const int MaxItems = 5;

std::string CTVOSTopShelf::m_url;
bool CTVOSTopShelf::m_handleUrl;

CTVOSTopShelf& CTVOSTopShelf::GetInstance()
{
  static CTVOSTopShelf sTopShelf;
  return sTopShelf;
}

void CTVOSTopShelf::SetTopShelfItems(CFileItemList& items, TVOSTopShelfItemsCategory category)
{
  @autoreleasepool
  {
    // Retrieve store URL
    static const auto isSandboxed = CDarwinEmbedUtils::IsIosSandboxed();
    static const auto storeUrl = [[tvosShared getSharedURL] URLByAppendingPathComponent:@"RA" isDirectory:YES];
    CLog::Log(LOGDEBUG, "[TopShelf] (sandboxed: {}) Use storeUrl: {}", isSandboxed ? "yes" : "no",
              storeUrl.path.UTF8String);

    auto fileManager = NSFileManager.defaultManager;

    // Retrieve TopShelf current categories
    auto sharedSandboxDict = [[NSUserDefaults alloc] initWithSuiteName:[tvosShared getSharedID]];
    static const auto topshelfKey = @"topshelfCategories";
    static const auto sharedJailbreakUrl = [storeUrl URLByAppendingPathComponent:@"topshelf.dict"
                                                                     isDirectory:NO];
    NSMutableDictionary* topshelfCategories;
    if (isSandboxed)
    {
      if ([sharedSandboxDict objectForKey:topshelfKey])
        topshelfCategories = [[sharedSandboxDict objectForKey:topshelfKey] mutableCopy];
      else
        topshelfCategories = [NSMutableDictionary dictionaryWithCapacity:2];
    }
    else
    {
      if ([[NSFileManager defaultManager] fileExistsAtPath:sharedJailbreakUrl.path])
        topshelfCategories = [NSMutableDictionary dictionaryWithContentsOfURL:sharedJailbreakUrl];
      else
        topshelfCategories = [NSMutableDictionary dictionaryWithCapacity:2];
    }

    // Function used to add category items in TopShelf dict
    CVideoThumbLoader thumbLoader;
    auto fillSharedDicts =
        [&](CFileItemList& items, NSString* categoryKey, NSString* categoryTitle,
            const std::function<std::string(CFileItemPtr videoItem)>& getThumbnailForItem,
            const std::function<std::string(CFileItemPtr videoItem)>& getTitleForItem) {
          // Store all old thumbs names of this category in array
          const auto thumbsPath = [storeUrl URLByAppendingPathComponent:categoryKey isDirectory:YES];
          auto thumbsToRemove = [NSMutableSet setWithArray:[fileManager contentsOfDirectoryAtPath:thumbsPath.path error:nil]];
          
          if (items.Size() <= 0)
          {
            // If there is no item in this category, remove it from topshelfCategories
            [topshelfCategories removeObjectForKey:categoryKey];
          }
          else
          {
            // Create dict for this category
            auto categoryDict = [NSMutableDictionary dictionaryWithCapacity:2];

            // Save category title in dict
            categoryDict[@"categoryTitle"] = categoryTitle;

            // Create an array to store each category item
            const int categorySize = std::min(items.Size(), MaxItems);
            auto categoryItems = [NSMutableArray arrayWithCapacity:categorySize];
            for (int i = 0; i < categorySize; ++i)
            {
              @autoreleasepool
              {
                // Get item thumb and title
                auto item = items.Get(i);
                if (!item->HasArt("thumb"))
                  thumbLoader.LoadItem(item.get());

                auto thumbPathSrc = getThumbnailForItem(item);
                auto itemThumb = std::to_string(item->GetVideoInfoTag()->m_iDbId) +
                                URIUtils::GetFileName(thumbPathSrc);
                auto thumbPathDst = URIUtils::AddFileToFolder(thumbsPath.path.UTF8String, itemThumb);
                if (!XFILE::CFile::Exists(thumbPathDst))
                  XFILE::CFile::Copy(thumbPathSrc, thumbPathDst);
                else
                {
                  // Remove from thumbsToRemove array so it doesn't get deleted at the end
                  [thumbsToRemove removeObject:@(itemThumb.c_str())];
                }

                auto itemTitle = getTitleForItem(item);
                
                // Add item object in categoryItems
                CLog::Log(LOGDEBUG, "[TopShelf] Adding item '{}' in category '{}'", itemTitle,
                          categoryKey.UTF8String);
                [categoryItems addObject:@{
                  @"title" : @(itemTitle.c_str()),
                  @"thumb" : @(itemThumb.c_str()),
                  @"url" : @(Base64::Encode(item->GetVideoInfoTag()->GetPath()).c_str())
                }];
              }
            }

            // Store category items array in category dict
            categoryDict[@"categoryItems"] = categoryItems;

            // Store category dict in topshelfCategories
            topshelfCategories[categoryKey] = categoryDict;
          }
          
          // Remove unused thumbs of this TopShelf category from thumbsPath folder
          for (NSString* thumbFileName in thumbsToRemove)
            [fileManager removeItemAtURL:[thumbsPath URLByAppendingPathComponent:thumbFileName isDirectory:NO] error:nil];
        };


    // Based on category type, add items in TopShelf shared dict
    switch (category)
    {
      case TVOSTopShelfItemsCategory::MOVIES:
        fillSharedDicts(
            items, @"movies", @(g_localizeStrings.Get(20386).c_str()),
            [](const CFileItemPtr& videoItem) {
              if (videoItem->HasArt("poster"))
                return videoItem->GetArt("poster");
              else
                return videoItem->GetArt("thumb");
            },
            [](const CFileItemPtr& videoItem) { return videoItem->GetLabel(); });
        break;
      case TVOSTopShelfItemsCategory::TV_SHOWS:
        CVideoDatabase videoDb;
        videoDb.Open();
        fillSharedDicts(
            items, @"tvshows", @(g_localizeStrings.Get(20387).c_str()),
            [&videoDb](const CFileItemPtr& videoItem)
            {
              int season = videoItem->GetVideoInfoTag()->m_iIdSeason;
              return season > 0 ? videoDb.GetArtForItem(season, MediaTypeSeason, "poster")
                                : std::string{};
            },
            [](const CFileItemPtr& videoItem)
            {
              return StringUtils::Format("{} s{:02}e{:02}",
                                         videoItem->GetVideoInfoTag()->m_strShowTitle,
                                         videoItem->GetVideoInfoTag()->m_iSeason,
                                         videoItem->GetVideoInfoTag()->m_iEpisode);
            });
        videoDb.Close();
        break;
    }

    // Set TopShelf new categories
    if (isSandboxed)
    {
      [sharedSandboxDict setObject:topshelfCategories forKey:topshelfKey];
      [sharedSandboxDict synchronize];
    }
    else
      [topshelfCategories writeToURL:sharedJailbreakUrl atomically:YES];
  }
}

void CTVOSTopShelf::RunTopShelf()
{
  if (!m_handleUrl)
    return;

  m_handleUrl = false;
  std::vector<std::string> split = StringUtils::Split(m_url, "/");
  std::string url = Base64::Decode(split[4]);

  //!@Todo handle tvcontentitem.displayurl. Should show topshelf item video info
  //  check split[2] for url type (display or play)

  // its a bit ugly, but only way to get resume window to show
  std::string cmd = StringUtils::Format("PlayMedia({})", StringUtils::Paramify(url));
  CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
}

void CTVOSTopShelf::HandleTopShelfUrl(const std::string& url, const bool run)
{
  m_url = url;
  m_handleUrl = run;
}