{"action":"created","comment":{"url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/comments/42497501","id":42497501,"diff_hunk":"@@ -0,0 +1,182 @@\n+/**\n+ * FileWriter -- fast non-streaming file appender for accepting buffered data\n+ *\n+ * Locks the file when appending to be safe with multiple writers.\n+ * Also, it reopens the file periodically, to make it work with logrotate.\n+ *\n+ * Copyright (C) 2014-2015 Andras Radics\n+ * Licensed under the Apache License, Version 2.0\n+ */\n+\n+var fs = require('fs');\n+var fse = require('./fs-ext.js');\n+\n+var flock = fse.flock;\n+var flockSync = function( fd, mode ) {\n+    try { fse.flockSync(fd, mode); return true }\n+    catch (err) { return false }\n+}\n+\n+fse.SEEK_SET = 0;\n+fse.SEEK_CUR = 1;\n+fse.SEEK_END = 2;\n+\n+module.exports = FileWriter;\n+\n+function FileWriter( filename, openmode ) {\n+    'use strict';\n+\n+    if (!(this instanceof FileWriter)) return new FileWriter(filename, openmode);\n+    if (!filename) throw new Error(\"missing filename\");\n+\n+    this._isFileWriter = true;\n+    this.filename = filename;\n+    this.openmode = openmode || \"a\";\n+    // do not overwrite contents when reopening file, use a instead of w\n+    this.reopenmode = this.openmode[0] !== 'w' ? openmode : ((openmode[1] === '+' || openmode[1] === 'r') ? 'a+' : 'a');\n+    this.reopenInterval = 50;   // do not reuse for more than .05 seconds\n+    this.reopenTime = 0;\n+    this.isFirstOpen = true;\n+    this.fd = undefined;\n+\n+    this._reopenFd = function(callback) {\n+        if (this.fd !== undefined) {\n+            fs.closeSync(this.fd);\n+            this.fd = undefined;\n+        }\n+\n+        var self = this;\n+        var mode = this.isFirstOpen ? this.openmode : this.reopenmode;\n+        // setTimeout would be faster than Date.now, but must guarantee .05 sec\n+        this.reopenTime = Date.now() + this.reopenInterval;\n+        fs.open(this.filename, mode, function(err, fd) {\n+            if (err) {\n+                if (err.message.indexOf(\"ENOENT, \") === 0 &&\n+                    self.openmode[0] !== 'r' &&\n+                    !self.isFirstOpen)\n+                {\n+                    // if file was removed since the last open, and ok to create, create it anew\n+                    self.isFirstOpen = true;\n+                    return self._reopenFd(callback);\n+                }\n+                return callback(err);\n+            }\n+            self.isFirstOpen = false;\n+            self.fd = fd;\n+            callback(null, fd);\n+        });\n+    };\n+\n+    this._getFd = function(callback) {\n+        if (this.fd !== undefined) return callback(null, this.fd);\n+        else return this._reopenFd(callback);\n+    };\n+\n+    this._getLockedFd = function(callback) {\n+        var self = this;\n+        this._getFd(function(err, fd) {\n+            if (err) return callback(err);\n+            flock(fd, \"ex\", function(err) {\n+                if (err) return callback(err);\n+                if (Date.now() < self.reopenTime) {\n+                    // grab and lock the fd and only then test reopenTime, since\n+                    // the expectation is that most writes will be bunched\n+                    // NOTE: this is also needed for correct renameFile\n+                    return callback(null, fd);\n+                }\n+                else {\n+                    flockSync(fd, \"un\");\n+                    self._reopenFd(function(err, fd) {\n+                        if (err) return callback(err);\n+                        self._getLockedFd(callback);\n+                    });\n+                }\n+            });\n+        })\n+    };\n+\n+    // atomically append the string to the file\n+    this.write = function(str, cb) {\n+        var self = this;\n+        this._getLockedFd(function(err, fd) {\n+            if (err) return cb(err);\n+            // not much benefit to reusing a buffer for the writes\n+            var buf = new Buffer(str);\n+            fs.write(fd, buf, 0, buf.length, null, function(err, nb) {\n+                flockSync(fd, \"un\");\n+                cb(err, nb);\n+            });\n+        });\n+    };\n+\n+    // close the file\n+    this.close = function( ) {\n+        try { if (this.fd) fs.closeSync(this.fd); }\n+        catch (err) { }\n+        this.fd = undefined;\n+    };\n+\n+    // expose class method on instance\n+    this.mutexTimeout = FileWriter.mutexTimeout;\n+    this.renameFile = FileWriter.renameFile;\n+}\n+\n+\n+// rename the file and wait for write activity to cease\n+FileWriter.mutexTimeout = 5000;\n+FileWriter.renameFile = function renameFile( oldName, newName, waitMs, cb ) {\n+    if (!cb && typeof waitMs === 'function') {\n+        cb = waitMs\n+        waitMs = 50\n+    }\n+    // Wait for all pending writes to finish.  After waitMs no writer will reuse the old fd,\n+    // and once we`ve paused and have the mutex we know that no more writes will occur.\n+    var fd = null, mutexTimeout = (this && this.mutexTimeout) ? this.mutexTimeout : FileWriter.mutexTimeout;\n+    function waitForWritesToFinish( ) {\n+        var guard, error = null\n+        function finish(err) {","path":"lib/filewriter.js","position":137,"original_position":137,"commit_id":"f549afdecc633e1fb24b60c2bd7557d74a0b2c2d","original_commit_id":"f549afdecc633e1fb24b60c2bd7557d74a0b2c2d","user":{"login":"mjsalinger","id":2452620,"avatar_url":"https://avatars.githubusercontent.com/u/2452620?v=3","gravatar_id":"","url":"https://api.github.com/users/mjsalinger","html_url":"https://github.com/mjsalinger","followers_url":"https://api.github.com/users/mjsalinger/followers","following_url":"https://api.github.com/users/mjsalinger/following{/other_user}","gists_url":"https://api.github.com/users/mjsalinger/gists{/gist_id}","starred_url":"https://api.github.com/users/mjsalinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mjsalinger/subscriptions","organizations_url":"https://api.github.com/users/mjsalinger/orgs","repos_url":"https://api.github.com/users/mjsalinger/repos","events_url":"https://api.github.com/users/mjsalinger/events{/privacy}","received_events_url":"https://api.github.com/users/mjsalinger/received_events","type":"User","site_admin":false},"body":"The use of a function-level `error` and a callback `err` with a finish function seems to be a little strange logically. Can we flatten this a bit to make it more readable?  Using an async function (async.series or async.waterfall possibly) would make this code easier to follow and better capture the error handling. ","created_at":"2015-10-20T13:54:35Z","updated_at":"2015-10-20T13:54:35Z","html_url":"https://github.com/andrasq/node-qfputs/pull/3#discussion_r42497501","pull_request_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3","_links":{"self":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/comments/42497501"},"html":{"href":"https://github.com/andrasq/node-qfputs/pull/3#discussion_r42497501"},"pull_request":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3"}}},"pull_request":{"url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3","id":48183569,"html_url":"https://github.com/andrasq/node-qfputs/pull/3","diff_url":"https://github.com/andrasq/node-qfputs/pull/3.diff","patch_url":"https://github.com/andrasq/node-qfputs/pull/3.patch","issue_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/3","number":3,"state":"open","locked":false,"title":"Review2","user":{"login":"mjsalinger","id":2452620,"avatar_url":"https://avatars.githubusercontent.com/u/2452620?v=3","gravatar_id":"","url":"https://api.github.com/users/mjsalinger","html_url":"https://github.com/mjsalinger","followers_url":"https://api.github.com/users/mjsalinger/followers","following_url":"https://api.github.com/users/mjsalinger/following{/other_user}","gists_url":"https://api.github.com/users/mjsalinger/gists{/gist_id}","starred_url":"https://api.github.com/users/mjsalinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mjsalinger/subscriptions","organizations_url":"https://api.github.com/users/mjsalinger/orgs","repos_url":"https://api.github.com/users/mjsalinger/repos","events_url":"https://api.github.com/users/mjsalinger/events{/privacy}","received_events_url":"https://api.github.com/users/mjsalinger/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-10-20T10:43:47Z","updated_at":"2015-10-20T13:54:35Z","closed_at":null,"merged_at":null,"merge_commit_sha":"404878bd9283cb904557741bf530ec91bc64609a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3/commits","review_comments_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3/comments","review_comment_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/3/comments","statuses_url":"https://api.github.com/repos/andrasq/node-qfputs/statuses/f549afdecc633e1fb24b60c2bd7557d74a0b2c2d","head":{"label":"andrasq:review2","ref":"review2","sha":"f549afdecc633e1fb24b60c2bd7557d74a0b2c2d","user":{"login":"andrasq","id":5438027,"avatar_url":"https://avatars.githubusercontent.com/u/5438027?v=3","gravatar_id":"","url":"https://api.github.com/users/andrasq","html_url":"https://github.com/andrasq","followers_url":"https://api.github.com/users/andrasq/followers","following_url":"https://api.github.com/users/andrasq/following{/other_user}","gists_url":"https://api.github.com/users/andrasq/gists{/gist_id}","starred_url":"https://api.github.com/users/andrasq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrasq/subscriptions","organizations_url":"https://api.github.com/users/andrasq/orgs","repos_url":"https://api.github.com/users/andrasq/repos","events_url":"https://api.github.com/users/andrasq/events{/privacy}","received_events_url":"https://api.github.com/users/andrasq/received_events","type":"User","site_admin":false},"repo":{"id":24503173,"name":"node-qfputs","full_name":"andrasq/node-qfputs","owner":{"login":"andrasq","id":5438027,"avatar_url":"https://avatars.githubusercontent.com/u/5438027?v=3","gravatar_id":"","url":"https://api.github.com/users/andrasq","html_url":"https://github.com/andrasq","followers_url":"https://api.github.com/users/andrasq/followers","following_url":"https://api.github.com/users/andrasq/following{/other_user}","gists_url":"https://api.github.com/users/andrasq/gists{/gist_id}","starred_url":"https://api.github.com/users/andrasq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrasq/subscriptions","organizations_url":"https://api.github.com/users/andrasq/orgs","repos_url":"https://api.github.com/users/andrasq/repos","events_url":"https://api.github.com/users/andrasq/events{/privacy}","received_events_url":"https://api.github.com/users/andrasq/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/andrasq/node-qfputs","description":"fast buffered line-at-a-time output","fork":false,"url":"https://api.github.com/repos/andrasq/node-qfputs","forks_url":"https://api.github.com/repos/andrasq/node-qfputs/forks","keys_url":"https://api.github.com/repos/andrasq/node-qfputs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/andrasq/node-qfputs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/andrasq/node-qfputs/teams","hooks_url":"https://api.github.com/repos/andrasq/node-qfputs/hooks","issue_events_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/events{/number}","events_url":"https://api.github.com/repos/andrasq/node-qfputs/events","assignees_url":"https://api.github.com/repos/andrasq/node-qfputs/assignees{/user}","branches_url":"https://api.github.com/repos/andrasq/node-qfputs/branches{/branch}","tags_url":"https://api.github.com/repos/andrasq/node-qfputs/tags","blobs_url":"https://api.github.com/repos/andrasq/node-qfputs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/andrasq/node-qfputs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/andrasq/node-qfputs/git/refs{/sha}","trees_url":"https://api.github.com/repos/andrasq/node-qfputs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/andrasq/node-qfputs/statuses/{sha}","languages_url":"https://api.github.com/repos/andrasq/node-qfputs/languages","stargazers_url":"https://api.github.com/repos/andrasq/node-qfputs/stargazers","contributors_url":"https://api.github.com/repos/andrasq/node-qfputs/contributors","subscribers_url":"https://api.github.com/repos/andrasq/node-qfputs/subscribers","subscription_url":"https://api.github.com/repos/andrasq/node-qfputs/subscription","commits_url":"https://api.github.com/repos/andrasq/node-qfputs/commits{/sha}","git_commits_url":"https://api.github.com/repos/andrasq/node-qfputs/git/commits{/sha}","comments_url":"https://api.github.com/repos/andrasq/node-qfputs/comments{/number}","issue_comment_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/comments{/number}","contents_url":"https://api.github.com/repos/andrasq/node-qfputs/contents/{+path}","compare_url":"https://api.github.com/repos/andrasq/node-qfputs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/andrasq/node-qfputs/merges","archive_url":"https://api.github.com/repos/andrasq/node-qfputs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/andrasq/node-qfputs/downloads","issues_url":"https://api.github.com/repos/andrasq/node-qfputs/issues{/number}","pulls_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls{/number}","milestones_url":"https://api.github.com/repos/andrasq/node-qfputs/milestones{/number}","notifications_url":"https://api.github.com/repos/andrasq/node-qfputs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/andrasq/node-qfputs/labels{/name}","releases_url":"https://api.github.com/repos/andrasq/node-qfputs/releases{/id}","created_at":"2014-09-26T14:44:50Z","updated_at":"2015-04-29T15:07:54Z","pushed_at":"2015-10-20T10:43:48Z","git_url":"git://github.com/andrasq/node-qfputs.git","ssh_url":"git@github.com:andrasq/node-qfputs.git","clone_url":"https://github.com/andrasq/node-qfputs.git","svn_url":"https://github.com/andrasq/node-qfputs","homepage":null,"size":332,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"base":{"label":"andrasq:empty","ref":"empty","sha":"12905befa7b9541dbe26a7edb4161722266843a4","user":{"login":"andrasq","id":5438027,"avatar_url":"https://avatars.githubusercontent.com/u/5438027?v=3","gravatar_id":"","url":"https://api.github.com/users/andrasq","html_url":"https://github.com/andrasq","followers_url":"https://api.github.com/users/andrasq/followers","following_url":"https://api.github.com/users/andrasq/following{/other_user}","gists_url":"https://api.github.com/users/andrasq/gists{/gist_id}","starred_url":"https://api.github.com/users/andrasq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrasq/subscriptions","organizations_url":"https://api.github.com/users/andrasq/orgs","repos_url":"https://api.github.com/users/andrasq/repos","events_url":"https://api.github.com/users/andrasq/events{/privacy}","received_events_url":"https://api.github.com/users/andrasq/received_events","type":"User","site_admin":false},"repo":{"id":24503173,"name":"node-qfputs","full_name":"andrasq/node-qfputs","owner":{"login":"andrasq","id":5438027,"avatar_url":"https://avatars.githubusercontent.com/u/5438027?v=3","gravatar_id":"","url":"https://api.github.com/users/andrasq","html_url":"https://github.com/andrasq","followers_url":"https://api.github.com/users/andrasq/followers","following_url":"https://api.github.com/users/andrasq/following{/other_user}","gists_url":"https://api.github.com/users/andrasq/gists{/gist_id}","starred_url":"https://api.github.com/users/andrasq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrasq/subscriptions","organizations_url":"https://api.github.com/users/andrasq/orgs","repos_url":"https://api.github.com/users/andrasq/repos","events_url":"https://api.github.com/users/andrasq/events{/privacy}","received_events_url":"https://api.github.com/users/andrasq/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/andrasq/node-qfputs","description":"fast buffered line-at-a-time output","fork":false,"url":"https://api.github.com/repos/andrasq/node-qfputs","forks_url":"https://api.github.com/repos/andrasq/node-qfputs/forks","keys_url":"https://api.github.com/repos/andrasq/node-qfputs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/andrasq/node-qfputs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/andrasq/node-qfputs/teams","hooks_url":"https://api.github.com/repos/andrasq/node-qfputs/hooks","issue_events_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/events{/number}","events_url":"https://api.github.com/repos/andrasq/node-qfputs/events","assignees_url":"https://api.github.com/repos/andrasq/node-qfputs/assignees{/user}","branches_url":"https://api.github.com/repos/andrasq/node-qfputs/branches{/branch}","tags_url":"https://api.github.com/repos/andrasq/node-qfputs/tags","blobs_url":"https://api.github.com/repos/andrasq/node-qfputs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/andrasq/node-qfputs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/andrasq/node-qfputs/git/refs{/sha}","trees_url":"https://api.github.com/repos/andrasq/node-qfputs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/andrasq/node-qfputs/statuses/{sha}","languages_url":"https://api.github.com/repos/andrasq/node-qfputs/languages","stargazers_url":"https://api.github.com/repos/andrasq/node-qfputs/stargazers","contributors_url":"https://api.github.com/repos/andrasq/node-qfputs/contributors","subscribers_url":"https://api.github.com/repos/andrasq/node-qfputs/subscribers","subscription_url":"https://api.github.com/repos/andrasq/node-qfputs/subscription","commits_url":"https://api.github.com/repos/andrasq/node-qfputs/commits{/sha}","git_commits_url":"https://api.github.com/repos/andrasq/node-qfputs/git/commits{/sha}","comments_url":"https://api.github.com/repos/andrasq/node-qfputs/comments{/number}","issue_comment_url":"https://api.github.com/repos/andrasq/node-qfputs/issues/comments{/number}","contents_url":"https://api.github.com/repos/andrasq/node-qfputs/contents/{+path}","compare_url":"https://api.github.com/repos/andrasq/node-qfputs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/andrasq/node-qfputs/merges","archive_url":"https://api.github.com/repos/andrasq/node-qfputs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/andrasq/node-qfputs/downloads","issues_url":"https://api.github.com/repos/andrasq/node-qfputs/issues{/number}","pulls_url":"https://api.github.com/repos/andrasq/node-qfputs/pulls{/number}","milestones_url":"https://api.github.com/repos/andrasq/node-qfputs/milestones{/number}","notifications_url":"https://api.github.com/repos/andrasq/node-qfputs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/andrasq/node-qfputs/labels{/name}","releases_url":"https://api.github.com/repos/andrasq/node-qfputs/releases{/id}","created_at":"2014-09-26T14:44:50Z","updated_at":"2015-04-29T15:07:54Z","pushed_at":"2015-10-20T10:43:48Z","git_url":"git://github.com/andrasq/node-qfputs.git","ssh_url":"git@github.com:andrasq/node-qfputs.git","clone_url":"https://github.com/andrasq/node-qfputs.git","svn_url":"https://github.com/andrasq/node-qfputs","homepage":null,"size":332,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3"},"html":{"href":"https://github.com/andrasq/node-qfputs/pull/3"},"issue":{"href":"https://api.github.com/repos/andrasq/node-qfputs/issues/3"},"comments":{"href":"https://api.github.com/repos/andrasq/node-qfputs/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/andrasq/node-qfputs/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/andrasq/node-qfputs/statuses/f549afdecc633e1fb24b60c2bd7557d74a0b2c2d"}}}}