Skip to content

Commit 49cfa86

Browse files
committed
Merge pull request #38 from hindessm/more-s3-nodes
More s3 nodes
2 parents f120d3b + e819829 commit 49cfa86

File tree

4 files changed

+228
-5
lines changed

4 files changed

+228
-5
lines changed

aws/icons/amazon.png

506 Bytes
Loading

aws/s3.html

+92
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,96 @@
1414
limitations under the License.
1515
-->
1616

17+
<script type="text/x-red" data-template-name="amazon s3 in">
18+
<div class="form-row">
19+
<label for="node-input-aws"><i class="fa fa-user"></i> AWS</label>
20+
<input type="text" id="node-input-aws">
21+
</div>
22+
<div class="form-row">
23+
<label for="node-input-bucket"><i class="fa fa-folder"></i> Bucket</label>
24+
<input type="text" id="node-input-bucket" placeholder="bucket name">
25+
</div>
26+
<div class="form-row node-input-filepattern">
27+
<label for="node-input-filepattern"><i class="fa fa-file"></i> Filename Pattern</label>
28+
<input type="text" id="node-input-filepattern" placeholder="Filepattern">
29+
</div>
30+
<div class="form-row">
31+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
32+
<input type="text" id="node-input-name" placeholder="Name">
33+
</div>
34+
</script>
35+
36+
<script type="text/x-red" data-help-name="amazon s3 in">
37+
<p>Amazon S3 watch node. Watches for file events on Box. By default all
38+
file events are reported, but the filename pattern can be supplied
39+
to limit the events to files which have full filenames that match
40+
the glob pattern. The event messages consist of the full filename
41+
in <b>msg.payload</b> property, the filename in <b>msg.file</b>,
42+
the event type in <b>msg.event</b>.</p>
43+
</script>
44+
45+
<script type="text/javascript">
46+
RED.nodes.registerType('amazon s3 in',{
47+
category: 'storage-input',
48+
color:"#C0DEED",
49+
defaults: {
50+
aws: {type:"aws-config",required:true},
51+
bucket: {required:true},
52+
filepattern: {value:""},
53+
name: {value:""},
54+
},
55+
inputs:0,
56+
outputs:1,
57+
icon: "amazon.png",
58+
label: function() {
59+
return this.bucket ? "s3 "+this.bucket : "s3";
60+
}
61+
});
62+
</script>
63+
64+
<script type="text/x-red" data-template-name="amazon s3">
65+
<div class="form-row">
66+
<label for="node-input-aws"><i class="fa fa-user"></i> AWS</label>
67+
<input type="text" id="node-input-aws">
68+
</div>
69+
<div class="form-row">
70+
<label for="node-input-bucket"><i class="fa fa-folder"></i> Bucket</label>
71+
<input type="text" id="node-input-bucket" placeholder="bucket name">
72+
</div>
73+
<div class="form-row node-input-filename">
74+
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
75+
<input type="text" id="node-input-filename" placeholder="Filename">
76+
</div>
77+
<div class="form-row">
78+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
79+
<input type="text" id="node-input-name" placeholder="Name">
80+
</div>
81+
</script>
82+
83+
<script type="text/x-red" data-help-name="amazon s3">
84+
<p>Amazon S3 input node. Downloads content from an Amazon S3 bucket. The bucket name can be specified in the node <b>bucket</b> property or in the <b>msg.bucket</b> property. The name of the file to download is taken from the node <b>filename</b> property or the <b>msg.filename</b> property. The downloaded content is sent as <b>msg.payload</b> property. If the download fails <b>msg.error</b> will contain an error object.</p>
85+
</script>
86+
87+
<script type="text/javascript">
88+
RED.nodes.registerType('amazon s3',{
89+
category: 'storage-output',
90+
color:"#C0DEED",
91+
defaults: {
92+
aws: {type:"aws-config",required:true},
93+
bucket: {required:true},
94+
filename: {value:""},
95+
name: {value:""},
96+
},
97+
inputs:1,
98+
outputs:1,
99+
icon: "amazon.png",
100+
align: "right",
101+
label: function() {
102+
return this.bucket ? "s3 "+this.bucket : "s3";
103+
}
104+
});
105+
</script>
106+
17107
<script type="text/x-red" data-template-name="amazon s3 out">
18108
<div class="form-row">
19109
<label for="node-input-aws"><i class="fa fa-user"></i> AWS</label>
@@ -50,9 +140,11 @@
50140
bucket: {required:true},
51141
filename: {value:""},
52142
localFilename: {value:""},
143+
name: {value:""},
53144
},
54145
inputs:1,
55146
outputs:0,
147+
icon: "amazon.png",
56148
align: "right",
57149
label: function() {
58150
return this.bucket ? "s3 "+this.bucket : "s3";

aws/s3.js

+135-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,132 @@
1717
module.exports = function(RED) {
1818
"use strict";
1919
var fs = require('fs');
20+
var minimatch = require("minimatch");
21+
22+
function AmazonS3InNode(n) {
23+
RED.nodes.createNode(this,n);
24+
this.awsConfig = RED.nodes.getNode(n.aws);
25+
this.region = n.region;
26+
this.bucket = n.bucket;
27+
this.filepattern = n.filepattern || "";
28+
var node = this;
29+
var AWS = this.awsConfig ? this.awsConfig.AWS : null;
30+
if (!AWS) {
31+
node.warn("Missing AWS credentials");
32+
return;
33+
}
34+
var s3 = new AWS.S3();
35+
node.status({fill:"blue",shape:"dot",text:"initializing"});
36+
s3.listObjects({ Bucket: node.bucket }, function(err, data) {
37+
if (err) {
38+
node.error("failed to fetch S3 state: " + err);
39+
node.status({fill:"red",shape:"ring",text:"error"});
40+
return;
41+
}
42+
node.state = data.Contents.filter(function (e) {
43+
return !node.filepattern || minimatch(e, node.filepattern);
44+
}).map(function (e) {
45+
return e.Key;
46+
});
47+
node.status({});
48+
node.on("input", function(msg) {
49+
node.status({fill:"blue",shape:"dot",text:"checking for changes"});
50+
s3.listObjects({ Bucket: node.bucket }, function(err, data) {
51+
if (err) {
52+
node.warn("failed to fetch S3 state: " + err);
53+
node.status({});
54+
return;
55+
}
56+
node.status({});
57+
var newState = data.Contents.filter(function (e) {
58+
return !node.filepattern ||
59+
minimatch(e, node.filepattern);
60+
}).map(function (e) {
61+
return e.Key;
62+
});
63+
var seen = {};
64+
var i;
65+
for (i = 0; i < node.state.length; i++) {
66+
seen[node.state[i]] = true;
67+
}
68+
for (i = 0; i < newState.length; i++) {
69+
if (seen[newState[i]]) {
70+
delete seen[newState[i]];
71+
} else {
72+
msg.payload = newState[i];
73+
msg.file = newState[i].substring(newState[i].lastIndexOf('/') + 1);
74+
msg.event = 'add';
75+
node.send(msg);
76+
}
77+
}
78+
for (var k in seen) {
79+
if (seen.hasOwnProperty(k)) {
80+
msg.payload = k;
81+
msg.file = k.substring(k.lastIndexOf('/') + 1);
82+
msg.event = 'delete';
83+
node.send(msg);
84+
}
85+
}
86+
node.state = newState;
87+
});
88+
});
89+
var interval = setInterval(function() {
90+
node.emit("input", {});
91+
}, 900000); // 15 minutes
92+
node.on("close", function() {
93+
if (interval !== null) {
94+
clearInterval(interval);
95+
}
96+
});
97+
});
98+
}
99+
RED.nodes.registerType("amazon s3 in", AmazonS3InNode);
100+
101+
function AmazonS3QueryNode(n) {
102+
RED.nodes.createNode(this,n);
103+
this.awsConfig = RED.nodes.getNode(n.aws);
104+
this.region = n.region;
105+
this.bucket = n.bucket;
106+
this.filename = n.filename || "";
107+
var node = this;
108+
var AWS = this.awsConfig ? this.awsConfig.AWS : null;
109+
if (!AWS) {
110+
node.warn("Missing AWS credentials");
111+
return;
112+
}
113+
var s3 = new AWS.S3();
114+
node.on("input", function(msg) {
115+
var bucket = node.bucket || msg.bucket;
116+
if (bucket === "") {
117+
node.warn("No bucket specified");
118+
return;
119+
}
120+
var filename = node.filename || msg.filename;
121+
if (filename === "") {
122+
node.warn("No filename specified");
123+
return;
124+
}
125+
msg.bucket = bucket;
126+
msg.filename = filename;
127+
node.status({fill:"blue",shape:"dot",text:"downloading"});
128+
s3.getObject({
129+
Bucket: bucket,
130+
Key: filename,
131+
}, function(err, data) {
132+
if (err) {
133+
node.warn("download failed " + err.toString());
134+
delete msg.payload;
135+
msg.error = err;
136+
} else {
137+
msg.payload = data.Body;
138+
delete msg.error;
139+
}
140+
node.status({});
141+
node.send(msg);
142+
});
143+
});
144+
}
145+
RED.nodes.registerType("amazon s3", AmazonS3QueryNode);
20146

21147
function AmazonS3OutNode(n) {
22148
RED.nodes.createNode(this,n);
@@ -26,29 +152,33 @@ module.exports = function(RED) {
26152
this.filename = n.filename || "";
27153
this.localFilename = n.localFilename || "";
28154
var node = this;
29-
var AWS = this.awsConfig.AWS;
155+
var AWS = this.awsConfig ? this.awsConfig.AWS : null;
156+
if (!AWS) {
157+
node.warn("Missing AWS credentials");
158+
return;
159+
}
30160
if (AWS) {
31161
var s3 = new AWS.S3();
32162
node.status({fill:"blue",shape:"dot",text:"checking credentials"});
33-
s3.listObjects({ Bucket: this.bucket }, function(err) {
163+
s3.listObjects({ Bucket: node.bucket }, function(err) {
34164
if (err) {
35165
node.error("AWS S3 error: " + err);
36166
node.status({fill:"red",shape:"ring",text:"error"});
37167
return;
38168
}
39169
node.status({});
40170
node.on("input", function(msg) {
41-
var bucket = this.bucket || msg.bucket;
171+
var bucket = node.bucket || msg.bucket;
42172
if (bucket === "") {
43173
node.warn("No bucket specified");
44174
return;
45175
}
46-
var filename = this.filename || msg.filename;
176+
var filename = node.filename || msg.filename;
47177
if (filename === "") {
48178
node.warn("No filename specified");
49179
return;
50180
}
51-
var localFilename = this.localFilename || msg.localFilename;
181+
var localFilename = node.localFilename || msg.localFilename;
52182
if (localFilename) {
53183
// TODO: use chunked upload for large files
54184
node.status({fill:"blue",shape:"dot",text:"uploading"});

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"dependencies" : {
66
"aws-sdk": "^2.0.15",
77
"dropbox":"^0.10.3",
8+
"minimatch": "^1.0.0",
89
"oauth":"~0.9.11",
910
"request":"~2.40.0",
1011
"instagram-node":"0.5.1",

0 commit comments

Comments
 (0)