Ticket #3433: 3433_replay_cache_v1.8.patch

File 3433_replay_cache_v1.8.patch, 16.7 KB (added by Imarok, 7 years ago)

new functions ReadCacheFileand StoreCacheFile, assert JS_GetArrayLength and JS_GetArrayLength won't fail by checking if the cache is an array, check if the cache file is valid json

  • binaries/data/mods/public/gui/replaymenu/replay_actions.js

     
    127127    });
    128128}
    129129
     130function reloadCache()
     131{
     132    let selected = Engine.GetGUIObjectByName("replaySelection").selected;
     133    loadReplays(selected > 0 ? createReplaySelectionData(g_ReplaysFiltered[selected].directory) : "", true);
     134}
     135
    130136/**
    131137 * Callback.
    132138 */
  • binaries/data/mods/public/gui/replaymenu/replay_menu.js

     
    5959        return;
    6060    }
    6161
    62     loadReplays(data && data.replaySelectionData);
     62    loadReplays(data && data.replaySelectionData, false);
    6363
    6464    if (!g_Replays)
    6565    {
     
    7676 * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions.
    7777 * Restore selected filters and item.
    7878 */
    79 function loadReplays(replaySelectionData)
     79function loadReplays(replaySelectionData, reload)
    8080{
    81     g_Replays = Engine.GetReplays();
     81    g_Replays = Engine.GetReplays(reload);
    8282
    8383    if (!g_Replays)
    8484        return;
  • binaries/data/mods/public/gui/replaymenu/replay_menu.xml

     
    250250                <translatableAttribute id="caption">Delete</translatableAttribute>
    251251                <action on="Press">deleteReplayButtonPressed();</action>
    252252            </object>
     253           
     254            <!-- Reload Cache Button -->
     255            <object type="button" style="StoneButton" size="40%+25 0 57%+25 100%">
     256                <translatableAttribute id="caption">Reload Cache</translatableAttribute>
     257                <action on="Press">reloadCache();</action>
     258            </object>
    253259
    254260            <!-- Summary Button -->
    255261            <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%">
  • binaries/data/mods/public/gui/session/session.js

     
    583583
    584584    Engine.EndGame();
    585585
     586    // After the replay file was closed in EndGame
     587    // Done here to keep EndGame small
     588    if (!g_IsReplay)
     589        Engine.AddReplayToCache(replayDirectory);
     590
    586591    if (g_IsController && Engine.HasXmppClient())
    587592        Engine.SendUnregisterGame();
    588593
  • source/ps/scripting/JSInterface_VisualReplay.cpp

     
    3333    return VisualReplay::DeleteReplay(replayFile);
    3434}
    3535
    36 JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate)
     36JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload)
    3737{
    38     return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface));
     38    return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface), reload);
    3939}
    4040
    4141JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
     
    5353    return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName);
    5454}
    5555
     56void JSI_VisualReplay::AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
     57{
     58    VisualReplay::AddReplayToCache(*(pCxPrivate->pScriptInterface), directoryName);
     59}
     60
    5661void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface)
    5762{
    58     scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");
     63    scriptInterface.RegisterFunction<JS::Value, bool, &GetReplays>("GetReplays");
    5964    scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay");
    6065    scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay");
    6166    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes");
    6267    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata");
    6368    scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata");
     69    scriptInterface.RegisterFunction<void, CStrW, &AddReplayToCache>("AddReplayToCache");
    6470}
  • source/ps/scripting/JSInterface_VisualReplay.h

     
    2525{
    2626    void StartVisualReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory);
    2727    bool DeleteReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& replayFile);
    28     JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate);
     28    JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload);
    2929    JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3030    bool HasReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3131    JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
     32    void AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3233    void RegisterScriptFunctions(ScriptInterface& scriptInterface);
    3334}
    3435
  • source/ps/VisualReplay.cpp

     
    6161    g_Game->StartVisualReplay(replayFile.string8());
    6262}
    6363
     64bool VisualReplay::ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject)
     65{
     66    TIMER(L"ReadCacheFile");
     67    JSContext* cx = scriptInterface.GetContext();
     68    JSAutoRequest rq(cx);
     69
     70    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     71    if (!FileExists(cacheFileName))
     72        return false;
     73
     74    // Open cache file
     75    std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     76
     77    // Read file into chacheStr
     78    CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     79    SAFE_DELETE(cacheStream);
     80
     81    // Create empty JS object and parse the context of the cache into it
     82    JS::RootedValue cachedReplays(cx);
     83    if (!scriptInterface.ParseJSON(cacheStr, &cachedReplays))
     84    {
     85        LOGERROR("The replay cache file is corrupted, it will be deleted");
     86        wunlink(cacheFileName);
     87        return false;
     88    }
     89
     90    cachedReplaysObject.set(&cachedReplays.toObject());
     91    if (!JS_IsArrayObject(cx, cachedReplaysObject))
     92    {
     93        LOGERROR("The replay cache file is corrupted, it will be deleted");
     94        wunlink(cacheFileName);
     95        return false;
     96    }
     97
     98    return true;
     99}
     100
     101void VisualReplay::StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays)
     102{
     103    TIMER(L"StoreCacheFile");
     104    JSContext* cx = scriptInterface.GetContext();
     105    JSAutoRequest rq(cx);
     106
     107    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     108    const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json";
     109
     110    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     111    std::ofstream cacheStream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     112    cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
     113    cacheStream.close();
     114
     115    wunlink(cacheFileName);
     116    if (wrename(tempCacheFileName, cacheFileName))
     117        LOGERROR("Could not store the replay cache");
     118}
     119
    64120/**
    65  * Load all replays found in the directory.
    66  *
    67  * Since files are spread across the harddisk,
    68  * loading hundreds of them can consume a lot of time.
    69  */
    70 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface)
     121* Load the replay cache and check if there are new/deleted ones
     122* If so, update their data.
     123*/
     124JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize)
    71125{
    72     TIMER(L"GetReplays");
     126    TIMER(L"ReloadReplayCache");
    73127    JSContext* cx = scriptInterface.GetContext();
    74128    JSAutoRequest rq(cx);
     129    // Maps the filename onto the index and size
     130    std::map<CStr, std::pair<u32, u32>> fileList;
    75131
    76     u32 i = 0;
     132    JS::RootedObject cachedReplaysObject(cx);
     133
     134   
     135    if (ReadCacheFile(scriptInterface, &cachedReplaysObject))
     136    {
     137        TIMER(L"ReloadReplayCache: create fileList");
     138        // Create list of files included in the cache
     139        u32 cacheLength = 0;
     140        JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     141        for (u32 j = 0; j < cacheLength; ++j)
     142        {
     143            JS::RootedValue replay(cx);
     144            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     145
     146            JS::RootedValue file(cx);
     147            CStr fileName;
     148            u32 fileSize;
     149            scriptInterface.GetProperty(replay, "directory", fileName);
     150            scriptInterface.GetProperty(replay, "fileSize", fileSize);
     151
     152            fileList.emplace(fileName, std::make_pair(j, fileSize));
     153        }
     154    }
     155
     156    JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    77157    DirectoryNames directories;
    78     JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    79158
    80     if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
    81         for (OsPath& directory : directories)
     159    if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
     160        return replays;
     161
     162    bool newReplays = false;
     163    std::vector<u32> copyFromOldCache;
     164    // Specifies where the next replay data should go in replays
     165    u32 i = 0;
     166    {
     167        TIMER(L"ReloadReplayCache: Load data");
     168        for (const OsPath& directory : directories)
    82169        {
    83170            if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     171                break;
    85172
    86             JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
    87             if (!replayData.isNull())
     173            bool isNew = true;
     174            std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8());
     175            // directory is in fileList
     176            if (it != fileList.end())
     177            {
     178                if (compareFileSize)
     179                {
     180                    CFileInfo fileInfo;
     181                    GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     182                    if ((u32)fileInfo.Size() == it->second.second)
     183                        isNew = false;
     184                }
     185                else
     186                    isNew = false;
     187            }
     188
     189            if (isNew)
     190            {
     191                JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
     192                if (replayData.isNull())
     193                {
     194                    CFileInfo fileInfo;
     195                    GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     196                    scriptInterface.Eval("({})", &replayData);
     197                    scriptInterface.SetProperty(replayData, "directory", directory);
     198                    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size());
     199                }
    88200                JS_SetElement(cx, replays, i++, replayData);
     201                newReplays = true;
     202            }
     203            else
     204                copyFromOldCache.push_back(it->second.first);
    89205        }
    90     return JS::ObjectValue(*replays);
     206    }
     207
     208    debug_printf("old not deleted replays: %d \n", copyFromOldCache.size());
     209    debug_printf("old deleted replays:: %d \n", fileList.size() - copyFromOldCache.size());
     210    debug_printf("new replays: %d \n", i);
     211
     212    if (!newReplays && fileList.empty())
     213        return replays;
     214    // No replay was changed, so just return the cache
     215    if (!newReplays && fileList.size() == copyFromOldCache.size())
     216        return cachedReplaysObject;
     217
     218    {
     219        TIMER(L"ReloadReplayCache: copy from old cache");
     220        // Copy the replays from the old cache that are not deleted
     221        if (!copyFromOldCache.empty())
     222            for (u32 j : copyFromOldCache)
     223            {
     224                JS::RootedValue replay(cx);
     225                JS_GetElement(cx, cachedReplaysObject, j, &replay);
     226                JS_SetElement(cx, replays, i++, replay);
     227            }
     228    }
     229    StoreCacheFile(scriptInterface, replays);
     230    return replays;
    91231}
    92232
     233JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload)
     234{
     235    TIMER(L"GetReplays");
     236    JSContext* cx = scriptInterface.GetContext();
     237    JSAutoRequest rq(cx);
     238    // Acquire replays
     239    JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload));
     240    // Remove entries without data
     241    JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0));
     242    u32 replaysLength = 0;
     243    u32 i = 0;
     244    JS_GetArrayLength(cx, replays, &replaysLength);
     245    for (u32 j = 0; j < replaysLength; ++j)
     246    {
     247        JS::RootedValue replay(cx);
     248        JS_GetElement(cx, replays, j, &replay);
     249        if (scriptInterface.HasProperty(replay, "attribs"))
     250            JS_SetElement(cx, replaysWithoutNullEntries, i++, replay);
     251    }
     252    return JS::ObjectValue(*replaysWithoutNullEntries);
     253}
     254
    93255/**
    94256 * Move the cursor backwards until a newline was read or the beginning of the file was found.
    95257 * Either way the cursor points to the beginning of a newline.
     
    173335    return -1;
    174336}
    175337
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     338JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177339{
     340    TIMER(L"LoadReplayData");
    178341    // The directory argument must not be constant, otherwise concatenating will fail
    179342    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
    180343
     
    251414    scriptInterface.Eval("({})", &replayData);
    252415    scriptInterface.SetProperty(replayData, "file", replayFile);
    253416    scriptInterface.SetProperty(replayData, "directory", directory);
     417    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254418    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255419    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256420    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266430    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267431}
    268432
    269 
    270433JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271434{
    272435    // Create empty JS object
     
    292455    return attribs;
    293456}
    294457
     458void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
     459{
     460    TIMER(L"AddReplayToCache");
     461    JSContext* cx = scriptInterface.GetContext();
     462    JSAutoRequest rq(cx);
     463
     464    JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
     465    if (replayData.isNull())
     466        return;
     467
     468    JS::RootedObject cachedReplaysObject(cx);
     469    if (!ReadCacheFile(scriptInterface, &cachedReplaysObject))
     470        cachedReplaysObject = JS_NewArrayObject(cx, 0);
     471
     472    u32 cacheLength = 0;
     473    JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     474    JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
     475
     476    StoreCacheFile(scriptInterface, cachedReplaysObject);
     477}
     478
    295479void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296480{
    297481    JSContext* cx = scriptInterface->GetContext();
  • source/ps/VisualReplay.h

     
    4242void StartVisualReplay(const CStrW& directory);
    4343
    4444/**
     45 * Reads the replay Cache file and parses it into a jsObject
     46 *
     47 * @param scriptInterface the ScriptInterface in which to create the return data.
     48 * @param cachedReplaysObject the cached replays
     49 * @return true on succes; false otherwise
     50 */
     51bool ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject);
     52
     53/**
     54 * Stores the replay list in the replay cache file
     55 *
     56 * @param scriptInterface the ScriptInterface in which to create the return data.
     57 * @param replays teh replay list to store
     58 */
     59void StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays);
     60
     61/**
     62 * Reloads the replay cache
     63 *
     64 * @param scriptInterface the ScriptInterface in which to create the return data.
     65 * @param compareFileSize compare the directory name and the FileSize of the replays and the cache
     66 * @return cache entries
     67 */
     68JS::HandleObject ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize = false);
     69
     70/**
    4571 * Get a list of replays to display in the GUI.
    4672 *
    4773 * @param scriptInterface the ScriptInterface in which to create the return data.
    4874 * @return array of objects containing replay data
    4975 */
    50 JS::Value GetReplays(ScriptInterface& scriptInterface);
     76JS::Value GetReplays(ScriptInterface& scriptInterface, bool reload);
    5177
    5278/**
    5379 * Parses a commands.txt file and extracts metadata.
    5480 * Works similarly to CGame::LoadReplayData().
    5581 */
    56 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
     82JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory);
    5783
    5884/**
    5985 * Permanently deletes the visual replay (including the parent directory)
     
    83109 */
    84110void SaveReplayMetadata(ScriptInterface* scriptInterface);
    85111
     112/**
     113* Adds a replay to the replayCache
     114*/
     115void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
    86116}
    87117
    88118#endif