Skip to content

Commit 2581b58

Browse files
committed
use .nb.html over local cache if newer (e.g. from vcs)
1 parent ffa6a7c commit 2581b58

File tree

3 files changed

+92
-23
lines changed

3 files changed

+92
-23
lines changed

src/cpp/session/modules/SessionRmdNotebook.R

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@
1414
#
1515
assign(".rs.notebookVersion", envir = .rs.toolsEnv(), "1.0")
1616

17-
.rs.addFunction("extractRmdFromNotebook", function(input, output)
18-
{
19-
if (Encoding(input) == "unknown") Encoding(input) <- "UTF-8"
20-
if (Encoding(output) == "unknown") Encoding(output) <- "UTF-8"
21-
17+
.rs.addFunction("parseRmdFromNotebook", function(input, output) {
2218
# extract rmd contents and populate file
2319
contents <- "# Failed to parse notebook"
2420
parsed <- try(rmarkdown::parse_html_notebook(input), silent = TRUE)
@@ -28,6 +24,27 @@ assign(".rs.notebookVersion", envir = .rs.toolsEnv(), "1.0")
2824
contents <- parsed$rmd
2925
}
3026
cat(contents, file = output, sep = "\n")
27+
})
28+
29+
.rs.addFunction("testNotebookRmdMatches", function(rmd, notebook) {
30+
# parse the notebook to get the text of the contained R markdown doc
31+
parsed <- try(rmarkdown::parse_html_notebook(notebook), silent = TRUE)
32+
if (inherits(parsed, "try-error") || is.null(parsed$rmd)) {
33+
return(.rs.scalar(FALSE))
34+
}
35+
36+
# compare with the contents of the R markdown file
37+
rmdContents <- readLines(rmd)
38+
return(identical(parsed$rmd, rmdContents))
39+
})
40+
41+
.rs.addFunction("extractRmdFromNotebook", function(input, output)
42+
{
43+
if (Encoding(input) == "unknown") Encoding(input) <- "UTF-8"
44+
if (Encoding(output) == "unknown") Encoding(output) <- "UTF-8"
45+
46+
# parse R Markdown from notebook and write it to the requested file
47+
.rs.parseRmdFromNotebook(input, output)
3148

3249
# extract and populate cache
3350
status <- try(.rs.hydrateCacheFromNotebook(input), silent = TRUE)

src/cpp/session/modules/rmarkdown/NotebookCache.cpp

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -236,31 +236,73 @@ void onDocAdded(const std::string& id)
236236
if (docPath.extensionLowerCase() != ".rmd")
237237
return;
238238

239-
FilePath cachePath = chunkCacheFolder(path, id);
239+
// find the cache (test for saved)
240+
FilePath cachePath = chunkCacheFolder(path, id, notebookCtxId());
241+
if (!cachePath.exists())
242+
cachePath = chunkCacheFolder(path, id, kSavedCtx);
243+
240244
FilePath notebookPath = docPath.parent().complete(docPath.stem() +
241245
kNotebookExt);
242246

243-
// clean up incompatible cache versions (as we're about to invalidate them
244-
// by mutating the document without updating them)
245-
if (cachePath.exists())
246-
{
247-
std::vector<FilePath> versions;
248-
cachePath.children(&versions);
249-
BOOST_FOREACH(const FilePath& version, versions)
250-
{
251-
if (version.isDirectory() && version.filename() != kCacheVersion)
252-
{
253-
error = version.remove();
254-
if (error)
255-
LOG_ERROR(error);
256-
}
257-
}
258-
}
259-
260247
// if the cache doesn't exist but we have a notebook file, hydrate from that
261248
// file
262249
if (!cachePath.exists() && notebookPath.exists())
263250
{
251+
error = r::exec::RFunction(".rs.hydrateCacheFromNotebook",
252+
notebookPath.absolutePath()).call();
253+
if (error)
254+
LOG_ERROR(error);
255+
return;
256+
}
257+
258+
// if both the local cache and the notebook cache exist, we need to decide
259+
// which to load
260+
if (cachePath.exists() && notebookPath.exists())
261+
{
262+
// get the dates first--in no case will we use the notebook over the
263+
// local cache if the local cache is newer (this will be the case most
264+
// of the time). we want to check this first because it's cheap; further
265+
// tests will require us to test the caches for compatibility, which is
266+
// more expensive.
267+
268+
// find the chunk definition file
269+
FilePath chunkDefs = cachePath.complete(kNotebookChunkDefFilename);
270+
if (!chunkDefs.exists())
271+
return;
272+
273+
std::time_t localCacheTime = chunkDefs.lastWriteTime();
274+
std::time_t nbCacheTime = notebookPath.lastWriteTime();
275+
276+
if (localCacheTime >= nbCacheTime)
277+
return;
278+
279+
// if we got this far, it means that the notebook cache looks newer than
280+
// our cache -- test to see whether it's compatible
281+
bool matches = false;
282+
error = r::exec::RFunction(".rs.testNotebookRmdMatches",
283+
docPath.absolutePath(), notebookPath.absolutePath()).call(&matches);
284+
if (error)
285+
{
286+
LOG_ERROR(error);
287+
return;
288+
}
289+
290+
if (!matches)
291+
{
292+
// the notebook cache looks newer but it isn't aligned with the .Rmd,
293+
// so we can't use it
294+
return;
295+
}
296+
297+
// if we got this far, we have matching newer notebook cache. blow away
298+
// our stale local cache and rehydrate from the notebook cache.
299+
error = cachePath.remove();
300+
if (error)
301+
{
302+
LOG_ERROR(error);
303+
return;
304+
}
305+
264306
error = r::exec::RFunction(".rs.hydrateCacheFromNotebook",
265307
notebookPath.absolutePath()).call();
266308
if (error)

src/cpp/session/modules/rmarkdown/SessionRmdNotebook.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,16 @@ Error createNotebookFromCache(const json::JsonRpcRequest& request,
238238
LOG_ERROR(error);
239239
return error;
240240
}
241+
242+
// bump the write time on our local chunk definition file so that it matches
243+
// the notebook file; this prevents us from thinking that the .nb.html file
244+
// we just wrote is ahead of the local cache.
245+
FilePath outputFile = module_context::resolveAliasedPath(outputPath);
246+
FilePath chunkDefsFile = chunkDefinitionsPath(
247+
module_context::resolveAliasedPath(rmdPath), kSavedCtx);
248+
if (chunkDefsFile.exists() &&
249+
chunkDefsFile.lastWriteTime() < outputFile.lastWriteTime())
250+
chunkDefsFile.setLastWriteTime(outputFile.lastWriteTime());
241251

242252
return Success();
243253
}

0 commit comments

Comments
 (0)