├── .gitattributes
├── COPYING
├── README.md
├── app
├── blockly
│ ├── LICENSE
│ ├── blockly.js
│ ├── en.js
│ ├── media
│ │ ├── 1x1.gif
│ │ ├── click.mp3
│ │ ├── click.ogg
│ │ ├── click.wav
│ │ ├── delete.mp3
│ │ ├── delete.ogg
│ │ ├── delete.wav
│ │ ├── disconnect.mp3
│ │ ├── disconnect.ogg
│ │ ├── disconnect.wav
│ │ ├── handclosed.cur
│ │ ├── handdelete.cur
│ │ ├── handopen.cur
│ │ ├── quote0.png
│ │ ├── quote1.png
│ │ ├── sprites.png
│ │ └── sprites.svg
│ └── pibakery.js
├── blocks.js
├── img
│ ├── export.png
│ ├── icon.icns
│ ├── icon.ico
│ ├── import.png
│ ├── load-sd.png
│ ├── settings.png
│ ├── success.png
│ ├── update-sd.png
│ ├── updating.gif
│ ├── write-to-sd.png
│ └── writing.gif
├── index.html
├── index.js
├── open-sans
│ ├── open-sans-bold-ext.woff2
│ ├── open-sans-bold.woff2
│ ├── open-sans-light-ext.woff2
│ ├── open-sans-light.woff2
│ ├── open-sans-normal-ext.woff2
│ ├── open-sans-normal.woff2
│ └── open-sans.css
└── styles.css
├── lib
├── blockloader.js
├── blocks.js
├── blocktransform.js
├── blockupdates.js
├── cmdline-parser.js
├── elevate.js
├── imgmanager.js
├── kpartx.js
├── multifs.js
├── rootwriter.js
├── sdcard.js
├── settings.js
├── ui.js
└── updates.js
├── main.js
├── package.json
├── pibakery-blocks
├── LICENSE
├── README.md
├── authorizekey
│ ├── authorizekey.json
│ ├── authorizekey.py
│ └── authorizekey.sh
├── bootbehaviour
│ ├── bootbehaviour.json
│ └── bootbehaviour.sh
├── categories.json
├── changepass
│ ├── changepass.json
│ └── changepass.py
├── changesshkeys
│ ├── changesshkeys.json
│ └── changesshkeys.sh
├── deletefile
│ ├── deleteFile.sh
│ └── deletefile.json
├── downloadfile
│ ├── downloadFile.sh
│ └── downloadfile.json
├── info.json
├── iqaudio
│ ├── iqaudio.json
│ └── iqaudio.sh
├── lampinstall
│ ├── lampInstall.sh
│ └── lampinstall.json
├── memsplit
│ ├── memsplit.json
│ └── memsplit.sh
├── newcronjob
│ ├── createjob.sh
│ ├── newcronjob.json
│ └── newcronjob.sh
├── otgether
│ ├── otgEther.sh
│ └── otgether.json
├── otgmassstorage
│ ├── otgMassStorage.sh
│ └── otgmassstorage.json
├── otgmidi
│ ├── otgMidi.sh
│ └── otgmidi.json
├── otgremove
│ ├── otgRemove.sh
│ └── otgremove.json
├── otgserial
│ ├── otgSerial.sh
│ └── otgserial.json
├── packageinstall
│ ├── packageInstall.sh
│ └── packageinstall.json
├── reboot
│ ├── reboot.json
│ └── reboot.sh
├── retropiesetup
│ ├── RetroPieInstall.sh
│ └── retropiesetup.json
├── runcommand
│ ├── runCommand.sh
│ └── runcommand.json
├── runpython
│ ├── runPython.sh
│ └── runpython.json
├── sethostname
│ ├── sethostname.json
│ └── sethostname.sh
├── seti2c
│ ├── seti2c.json
│ └── seti2c.sh
├── setspi
│ ├── setspi.json
│ └── setspi.sh
├── shutdown
│ ├── shutdown.json
│ └── shutdown.sh
├── uartconsole
│ ├── uartconsole.json
│ └── uartconsole.sh
├── updateupgrade
│ ├── updateupgrade.json
│ └── updateupgrade.sh
├── vncenable
│ ├── vncenable.json
│ └── vncenable.sh
├── waitfornetwork
│ ├── waitfornetwork.json
│ └── waitfornetwork.sh
└── wifisetup
│ ├── waitForNetwork.sh
│ ├── wifiConnect.py
│ └── wifisetup.json
├── pibakery-raspbian
├── README.md
├── etc
│ └── systemd
│ │ └── system
│ │ └── pibakery.service
├── images.json
├── info.json
├── lib
│ └── systemd
│ │ └── system
│ │ └── lightdm.service
└── opt
│ └── PiBakery
│ ├── console-lite.sh
│ ├── console.sh
│ ├── display.sh
│ ├── removeFirst.py
│ ├── removeNext.py
│ ├── runscripts.sh
│ ├── startup.sh
│ └── waitForNetwork.sh
├── resources
├── busybox
├── pibakery-install.sh
└── pibakery-mount.sh
├── settings.json
└── start.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # make sure shell script line endings are set to lf.
2 | # if set to crlf for resources/pibakery-mount.sh bad things happen (see #164)
3 |
4 | *.sh text eol=lf
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PiBakery
2 |
3 | [](https://david-dm.org/davidferguson/pibakery)
4 | [](https://david-dm.org/davidferguson/pibakery)
5 |
6 | The blocks based, easy to use setup tool for Raspberry Pi
7 |
8 | 
9 |
10 | PiBakery is a a blocks based drag and drop tool that allows you to customise and edit your Raspberry Pi without powering the Pi on. Simply insert your SD card into your computer, choose which features you want on your Pi, and hit **Write**. PiBakery will write the latest version of Raspbian to your SD card, with your customisations added too.
11 |
12 | For more information see www.PiBakery.org or follow [@PiBakery](http://twitter.com/PiBakery) on Twitter.
13 |
14 |
15 |
16 | # Installing from source
17 |
18 | While it is recommended to install PiBakery from one of the downloads on www.PiBakery.org/download.html, you can also install PiBakery from source if you want to see how it works, or edit PiBakery in any way.
19 |
20 | To install PiBakery from source, you'll need NodeJS and npm installed. Once you have them installed, clone the GitHub repository with
21 |
22 | `git clone https://github.com/davidferguson/pibakery.git`
23 |
24 | Change into the newly downloaded directory with
25 |
26 | `cd pibakery`
27 |
28 | And install the required node modules using
29 |
30 | `npm install`
31 |
32 | This will take a few minutes to complete.
33 |
34 | You can then run PiBakery using
35 |
36 | `npm start`
37 |
38 | # PiBakery on Linux
39 | PiBakery should run on Linux if you build from source, however you will need to have `kpartx` installed. Most distributions have these in package repositories, and in Debian/Ubuntu can be installed with
40 | `sudo apt-get install kpartx`
41 |
42 | ----
43 |
44 | # PiBakery v2
45 | The latest version of PiBakery, *PiBakery v2*, is a complete re-write of the original application, with many additional features, including:
46 |
47 | - PiBakery no longer bundles .img files in the installer/program. Instead, the user must supply their own Raspbian .img file. This means that any Raspbian-based .img can be used, with the possibility of other distros in the future
48 | - Ability to edit **any** Raspbian SD card, not just ones that have been written with PiBakery
49 | - More robust Linux support
50 | - Ability to add multiple block sources, so the user can maintain their own block repo with their own custom blocks
51 | - Importing of recipes (.xml files) created with older versions of PiBakery no longer fail, instead they are converted automatically into the new format
52 | - The entire program no longer runs as root/admin. Instead, just the writer process is elevated when needed to be
53 | - Modularised code to increase readability, and add option for command line mode in the future
54 |
55 | # Legal
56 |
57 | PiBakery is Copyright (c) 2018 David Ferguson, and is licenced under the GNU General Public License version 3 or later; please see the file `COPYING` for details.
58 |
59 | This project uses and distributes a binary of [Busybox](https://busybox.net) version 1.27.2 taken from Raspbian Stretch's [`busybox-static`](https://archive.raspbian.org/raspbian/pool/main/b/busybox/busybox-static_1.27.2-3_armhf.deb) package, the source code of which can be found [here](https://archive.raspbian.org/raspbian/pool/main/b/busybox/).
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/blockly/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2011
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/app/blockly/media/1x1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/1x1.gif
--------------------------------------------------------------------------------
/app/blockly/media/click.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/click.mp3
--------------------------------------------------------------------------------
/app/blockly/media/click.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/click.ogg
--------------------------------------------------------------------------------
/app/blockly/media/click.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/click.wav
--------------------------------------------------------------------------------
/app/blockly/media/delete.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/delete.mp3
--------------------------------------------------------------------------------
/app/blockly/media/delete.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/delete.ogg
--------------------------------------------------------------------------------
/app/blockly/media/delete.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/delete.wav
--------------------------------------------------------------------------------
/app/blockly/media/disconnect.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/disconnect.mp3
--------------------------------------------------------------------------------
/app/blockly/media/disconnect.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/disconnect.ogg
--------------------------------------------------------------------------------
/app/blockly/media/disconnect.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/disconnect.wav
--------------------------------------------------------------------------------
/app/blockly/media/handclosed.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/handclosed.cur
--------------------------------------------------------------------------------
/app/blockly/media/handdelete.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/handdelete.cur
--------------------------------------------------------------------------------
/app/blockly/media/handopen.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/handopen.cur
--------------------------------------------------------------------------------
/app/blockly/media/quote0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/quote0.png
--------------------------------------------------------------------------------
/app/blockly/media/quote1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/quote1.png
--------------------------------------------------------------------------------
/app/blockly/media/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/blockly/media/sprites.png
--------------------------------------------------------------------------------
/app/blockly/media/sprites.svg:
--------------------------------------------------------------------------------
1 |
2 |
75 |
--------------------------------------------------------------------------------
/app/blockly/pibakery.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | goog.provide('Blockly.PiBakery');
4 | goog.require('Blockly.Generator');
5 | Blockly.PiBakery = new Blockly.Generator('PiBakery');
6 |
7 |
8 | Blockly.PiBakery.addReservedWords();
9 |
10 | Blockly.PiBakery.init = function(workspace) {};
11 |
12 | Blockly.PiBakery.finish = function(code) {
13 | return code;
14 | };
15 |
16 |
17 | Blockly.PiBakery.scrubNakedValue = function(line) {
18 | return line;
19 | };
20 |
21 |
22 | Blockly.PiBakery.quote_ = function(string) {
23 | return string;
24 | };
25 |
26 | Blockly.PiBakery.scrub_ = function(block, code) {
27 | var nextBlock = block.nextConnection && block.nextConnection.targetBlock();
28 | var nextCode = Blockly.PiBakery.blockToCode(nextBlock);
29 | return code + nextCode;
30 | };
--------------------------------------------------------------------------------
/app/blocks.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | Blockly.Blocks['onboot'] = {
20 | init: function() {
21 | this.appendDummyInput()
22 | .appendField("On Every Boot");
23 | this.setNextStatement(true);
24 | this.setColour(20);
25 | this.setTooltip('');
26 | //this.setHelpUrl('http://www.example.com/');
27 | this.setHelpUrl('This is a very long description of how this block works, which has been placed into a space which is only meant for a URL. Hopefully Blockly allows this to be placed here, and doesn\'t check for a URL with regex or such. Anyway, onto the description of this block. This is the block that is used to select what other blocks run when the Raspberry Pi is first booted up. Lets see if we can fit some more words.');
28 | }
29 | };
30 |
31 | Blockly.Blocks['onfirstboot'] = {
32 | init: function() {
33 | this.appendDummyInput()
34 | .appendField("On First Boot");
35 | this.setNextStatement(true);
36 | this.setColour(20);
37 | this.setTooltip('');
38 | this.setHelpUrl('http://www.example.com/');
39 | }
40 | };
41 |
42 | Blockly.Blocks['onnextboot'] = {
43 | init: function() {
44 | this.appendDummyInput()
45 | .appendField("On Next Boot");
46 | this.setNextStatement(true);
47 | this.setColour(20);
48 | this.setTooltip('');
49 | this.setHelpUrl('http://www.example.com/');
50 | }
51 | };
52 |
53 | Blockly.PiBakery['onboot'] = function(block) {
54 | var code = '_pibakery-oneveryboot';
55 | return code;
56 | };
57 |
58 | Blockly.PiBakery['onfirstboot'] = function(block) {
59 | var code = '_pibakery-onfirstboot';
60 | return code;
61 | };
62 |
63 | Blockly.PiBakery['onnextboot'] = function(block) {
64 | var code = '_pibakery-onnextboot';
65 | return code;
66 | };
67 |
--------------------------------------------------------------------------------
/app/img/export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/export.png
--------------------------------------------------------------------------------
/app/img/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/icon.icns
--------------------------------------------------------------------------------
/app/img/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/icon.ico
--------------------------------------------------------------------------------
/app/img/import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/import.png
--------------------------------------------------------------------------------
/app/img/load-sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/load-sd.png
--------------------------------------------------------------------------------
/app/img/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/settings.png
--------------------------------------------------------------------------------
/app/img/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/success.png
--------------------------------------------------------------------------------
/app/img/update-sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/update-sd.png
--------------------------------------------------------------------------------
/app/img/updating.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/updating.gif
--------------------------------------------------------------------------------
/app/img/write-to-sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/write-to-sd.png
--------------------------------------------------------------------------------
/app/img/writing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/img/writing.gif
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | PiBakery
4 |
5 |
6 |
7 |
8 |
9 |
PiBakery
10 |
11 |
12 |
Write
13 |
14 |
15 |
16 |
Update
17 |
18 |
19 |
20 |
Load
21 |
22 |
23 |
24 |
25 |
Export
26 |
27 |
28 |
29 |
Import
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
www.PiBakery.org
46 |
@PiBakery
47 |
Version
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | var fs = require('fs-extra')
22 | var webFrame = require('electron').webFrame
23 | var dialog = require('electron').remote.dialog
24 | var path = require('path')
25 | var sdcard = require('../lib/sdcard.js')
26 | var ui = require('../lib/ui.js')
27 | var imgmanager = require('../lib/imgmanager.js')
28 | var blocks = require('../lib/blocks.js')
29 | var multifs = require('../lib/multifs.js')
30 |
31 | // disable zooming
32 | webFrame.setVisualZoomLevelLimits(1, 1)
33 | webFrame.setLayoutZoomLevelLimits(0, 0)
34 |
35 |
36 | /*
37 | * -------------------------------CODE FOR WRITING------------------------------
38 | */
39 | function writeClicked () {
40 | // show the chooser window, but don't populate it yet
41 | ui.showSdChooser(beginImageWrite)
42 |
43 | // keep updating the sd chooser while the sd chooser is still open
44 | updateSdChooser()
45 | }
46 |
47 |
48 | function updateSdChooser () {
49 | // do nothing if the SD chooser is closed
50 | if (!ui.sdChooserOpen) {
51 | return
52 | }
53 |
54 | // get available images
55 | imgmanager.getImages(function (error, images) {
56 | if (error) {
57 | console.error(error)
58 | return
59 | }
60 |
61 | if (ui.sdChooserOpen) {
62 | // update the select element
63 | ui.updateSdChooser(null, images)
64 |
65 | // get available sd cards
66 | updateSDChooserSDs()
67 | }
68 | })
69 | }
70 |
71 |
72 | function updateSDChooserSDs() {
73 | // get available sd cards
74 | sdcard.getSDs(function sdCallback (error, cards) {
75 | if (error) {
76 | console.error(error)
77 | return
78 | }
79 |
80 | if (!ui.sdChooserOpen) {
81 | return
82 | }
83 |
84 | // update the chooser and run this function again if the chooser is open
85 | ui.updateSdChooser(cards, null)
86 | setTimeout(updateSDChooserSDs, 5000)
87 | })
88 | }
89 |
90 |
91 | // callback when a new image is chosen by the user
92 | ui.imageChosenCallback = function (newImage) {
93 | // add this to the imgmanager list of images
94 | imgmanager.addImage(newImage)
95 | }
96 |
97 |
98 | function beginImageWrite (drive, os) {
99 | // show the writer window
100 | ui.showSdWriter('Writing to SD')
101 |
102 | // start the write
103 | sdcard.write(os.path, drive, {
104 | progress: writeImageProgress,
105 | error: function (error) {
106 | writeImageError(error, 'Write Error')
107 | },
108 | done: writeImageDone
109 | })
110 |
111 | ui.updateProgress(false, false, 'Waiting for Authentication...')
112 | }
113 |
114 |
115 | function writeImageDone (state, drive) {
116 | writeImageProgress({text:'Searching for remount path...'})
117 | // when the write is done, get the mountpoint of the /boot partition
118 | sdcard.getRemountPath(drive, function mountpointCallback (error, mountpoint) {
119 | if (error) {
120 | writeImageError(error, 'Write Error')
121 | return
122 | }
123 |
124 | installPiBakery(mountpoint, function () {
125 | // perform the cleanup
126 | writeImageProgress({text:'Cleaning up...'})
127 | sdcard.cleanup(drive, mountpoint, writeComplete)
128 | })
129 | })
130 | }
131 |
132 |
133 | function writeComplete (error, drive) {
134 | if (error) {
135 | writeImageError(error, 'Write Error')
136 | return
137 | }
138 |
139 | // sd image has been written, /boot has been mounted, script have been written, kpartx has been removed (if it was used)
140 | ui.showSdComplete('Write Complete')
141 | }
142 |
143 | /*
144 | * -----------------------------END CODE FOR WRITING----------------------------
145 | */
146 |
147 |
148 |
149 |
150 | /*
151 | * ------------------------------CODE FOR EDITING------------------------------
152 | */
153 |
154 | function updateClicked () {
155 | ui.showDriveChooser(beginImageUpdate, 'Update Existing SD Card', 'Update')
156 |
157 | // update the update chooser
158 | updateUpdateChooser()
159 | }
160 |
161 |
162 | function updateUpdateChooser () {
163 | // get available sd cards
164 | sdcard.getRaspbianSDs(function sdCallback (error, cards) {
165 | if (error) {
166 | console.error(error)
167 | return
168 | }
169 |
170 | // exit if the choose has been closed
171 | if (!ui.updateChooserOpen) {
172 | return
173 | }
174 |
175 | // update the chooser and run this function again if the chooser is open
176 | ui.updateUpdateChooser(cards)
177 | setTimeout(updateUpdateChooser, 5000)
178 | })
179 | }
180 |
181 | function beginImageUpdate (drive) {
182 | // show the updater window
183 | ui.showSdWriter('Updating SD')
184 |
185 | // get mountpoints
186 | var mountpoints = []
187 | for (var i = 0; i < drive.mountpoints.length; i++) {
188 | mountpoints.push(path.join(drive.mountpoints[i].path, 'bootcode.bin'))
189 | }
190 |
191 | // see which one(s) exist
192 | multifs.exists(mountpoints, function (error, exists) {
193 | if (error) {
194 | writeImageError(error, 'Update Error')
195 | return
196 | }
197 |
198 | if (exists.length === 0) {
199 | writeImageError(null, 'Update Error')
200 | return
201 | }
202 |
203 | // get the mountpoint, and write PiBakery to it
204 | var mountpoint = path.join(mountpoints[0], '../') // remove the 'bootcode.bin' part
205 | installPiBakery(mountpoint, updateComplete)
206 | })
207 | }
208 |
209 |
210 | function updateComplete (error) {
211 | if (error) {
212 | writeImageError(error, 'Update Error')
213 | return
214 | }
215 |
216 | // pibakery has been added to this sd card, scripts have veen written
217 | ui.showSdComplete('Update Complete')
218 | }
219 |
220 | /*
221 | * ----------------------------END CODE FOR EDITING----------------------------
222 | */
223 |
224 |
225 | /*
226 | * ------------------------------CODE FOR LOADING------------------------------
227 | */
228 |
229 | function loadClicked () {
230 | ui.showDriveChooser(loadFromSD, 'Load From SD Card', 'Load')
231 |
232 | // update the update chooser
233 | updateLoadChooser()
234 | }
235 |
236 |
237 | function updateLoadChooser () {
238 | // get available sd cards
239 | sdcard.getPiBakerySDs(function sdCallback (error, cards) {
240 | if (error) {
241 | console.error(error)
242 | return
243 | }
244 |
245 | // exit if the chooser has been closed
246 | if (!ui.updateChooserOpen) {
247 | return
248 | }
249 |
250 | // update the chooser and run this function again if the chooser is open
251 | ui.updateUpdateChooser(cards)
252 | setTimeout(updateLoadChooser, 5000)
253 | })
254 | }
255 |
256 | function loadFromSD (drive) {
257 | // show the updater window
258 |
259 | // get mountpoints
260 | var mountpoints = []
261 | for (var i = 0; i < drive.mountpoints.length; i++) {
262 | mountpoints.push(path.join(drive.mountpoints[i].path, 'PiBakery/blocks.xml'))
263 | }
264 |
265 | // see which one(s) exist
266 | multifs.exists(mountpoints, function (error, exists) {
267 | if (error) {
268 | ui.showSdWriter('Load Error')
269 | writeImageError(error, 'Load Error')
270 | throw error
271 | return
272 | }
273 |
274 | if (exists.length === 0) {
275 | ui.showSdWriter('Load Error')
276 | writeImageError(null, 'Load Error')
277 | return
278 | }
279 |
280 | // get the mountpoint, and write PiBakery to it
281 | var blockFile = exists[0]
282 |
283 | fs.readFile(blockFile, 'utf8', function (error, data) {
284 | if (error) {
285 | ui.showSdWriter('Load Error')
286 | writeImageError(null, 'Load Error')
287 | throw error
288 | }
289 |
290 | // load the blocks
291 | blocks.loadXml(data)
292 |
293 | // close the dialog
294 | ui.closeDialog()
295 | })
296 | })
297 | }
298 |
299 | /*
300 | * ----------------------------END CODE FOR LOADING----------------------------
301 | */
302 |
303 |
304 | function installPiBakery (mountpoint, cb) {
305 | // we have the mountpoint, now write the scripts and blocks
306 | writeImageProgress({text:'Generating scripts...'})
307 |
308 | // get scripts
309 | var data = blocks.generateScript()
310 |
311 | // get blocks.xml
312 | data.xml = blocks.getXml()
313 |
314 | // write this data to the sd card
315 | sdcard.installPiBakery(mountpoint, data, {
316 | error: function (error) {
317 | writeImageError(error, 'Write Error')
318 | },
319 | progress: writeImageProgress,
320 | done: cb
321 | })
322 | }
323 |
324 | function writeImageProgress (state) {
325 | // when the write progress changes, update the UI
326 | var value = false
327 | var max = false
328 | var text = false
329 |
330 | if ('transferred' in state && 'length' in state) {
331 | value = state.transferred
332 | max = state.length
333 | }
334 |
335 | if ('text' in state) {
336 | text = state.text
337 | }
338 |
339 | ui.updateProgress(value, max, text)
340 | }
341 |
342 |
343 | function writeImageError (error, title) {
344 | // if we get an error, show the error UI
345 | var msg = "Can't write to SD card, error code "
346 | msg = msg + error.name + ' :: ' + error.message
347 |
348 | ui.showSdError(title, msg)
349 |
350 | console.error(error)
351 | }
352 |
353 |
354 |
355 |
356 |
357 | function importRecipe () {
358 | var options = {
359 | title: 'Choose XML Recipe to Import',
360 | filters: [
361 | {name: 'Recipe Files (*.xml)', extensions: ['xml']}
362 | ],
363 | properties: [
364 | 'openFile'
365 | ]
366 | }
367 | var path = dialog.showOpenDialog(options)[0]
368 |
369 | fs.readFile(path, 'utf8', function (error, data) {
370 | if (error) {
371 | dialog.showErrorBox('XML Import Error', error.message)
372 | throw error
373 | }
374 |
375 | blocks.loadXml(data)
376 | })
377 | }
378 |
379 |
380 | function exportRecipe () {
381 | var options = {
382 | title: 'Export to XML Recipe',
383 | filters: [
384 | {name: 'Recipe Files (*.xml)', extensions: ['xml']}
385 | ]
386 | }
387 | var path = dialog.showSaveDialog(options)
388 | var xml = blocks.getXml()
389 |
390 | fs.writeFile(path, xml, function (error) {
391 | if (error) {
392 | dialog.showErrorBox('XML Export Error', error.message)
393 | throw error
394 | }
395 | })
396 | }
397 |
398 |
399 |
400 |
401 |
402 |
403 | /* BEGIN IMPORTING CODE HERE */
404 |
405 | // setup blockly and import the blocks
406 | blocks.inject(ui.elements.editor, ui.elements.toolbox)
407 | blocks.loadBlocks(ui.elements.toolbox, finishLoad)
408 |
409 | // called when blocks have finished loading, removes intro and shows workspace
410 | function finishLoad (error) {
411 | if (error) {
412 | console.error(error)
413 | return
414 | }
415 |
416 | // add handlers to buttons
417 | ui.elements.writeBtn.addEventListener('click', writeClicked)
418 | ui.elements.updateBtn.addEventListener('click', updateClicked)
419 | ui.elements.loadBtn.addEventListener('click', loadClicked)
420 | ui.elements.importBtn.addEventListener('click', importRecipe)
421 | ui.elements.exportBtn.addEventListener('click', exportRecipe)
422 | // TODO - add import and export buttons
423 |
424 | // remove intro message and show blockly workspace
425 | ui.removeIntro()
426 | ui.showWorkspace()
427 | }
428 |
--------------------------------------------------------------------------------
/app/open-sans/open-sans-bold-ext.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-bold-ext.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-bold.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans-light-ext.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-light-ext.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans-light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-light.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans-normal-ext.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-normal-ext.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidferguson/pibakery/db16e37719dc854be2d8820fe68db8e0d3589fcf/app/open-sans/open-sans-normal.woff2
--------------------------------------------------------------------------------
/app/open-sans/open-sans.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-style: normal;
4 | font-weight: 300;
5 | src: local('Open Sans Light'), local('OpenSans-Light'), url(./open-sans-light-ext.woff) format('woff2');
6 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
7 | }
8 |
9 |
10 | @font-face {
11 | font-family: 'Open Sans';
12 | font-style: normal;
13 | font-weight: 300;
14 | src: local('Open Sans Light'), local('OpenSans-Light'), url(./open-sans-light.woff) format('woff2');
15 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
16 | }
17 |
18 | @font-face {
19 | font-family: 'Open Sans';
20 | font-style: normal;
21 | font-weight: 400;
22 | src: local('Open Sans Regular'), local('OpenSans-Regular'), url(./open-sans-normal-ext.woff) format('woff2');
23 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
24 | }
25 |
26 |
27 | @font-face {
28 | font-family: 'Open Sans';
29 | font-style: normal;
30 | font-weight: 400;
31 | src: local('Open Sans Regular'), local('OpenSans-Regular'), url(./open-sans-normal.woff) format('woff2');
32 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
33 | }
34 |
35 | @font-face {
36 | font-family: 'Open Sans';
37 | font-style: normal;
38 | font-weight: 700;
39 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(./open-sans-bold-ext.woff) format('woff2');
40 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
41 | }
42 |
43 | @font-face {
44 | font-family: 'Open Sans';
45 | font-style: normal;
46 | font-weight: 700;
47 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(./open-sans-bold.woff) format('woff2');
48 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
49 | }
50 |
--------------------------------------------------------------------------------
/app/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | overflow: hidden;
3 | height: 100%;
4 | }
5 |
6 | body {
7 | font-family: 'Open Sans';
8 | font-size: 16pt;
9 | background: white;
10 | margin: 0px;
11 | height: 100%;
12 | overflow: auto;
13 | }
14 |
15 | #header {
16 | background-color: #c6c6c6;
17 | width: 100%;
18 | height: 70px;
19 | }
20 |
21 | #credits {
22 | text-align: center;
23 | width: 100%;
24 | margin-top: 90px;
25 | }
26 |
27 | #blockly {
28 | border:0px solid #CCC;
29 | padding-left:0;
30 | height: 100%;
31 | }
32 |
33 | #blockly_editor {
34 | width:100%;
35 | height: calc(100% - 70px);
36 | display: none;
37 | }
38 |
39 | .blocklyToolboxDiv, #header {
40 | -webkit-user-select: none;
41 | }
42 |
43 | #description {
44 | width:49%;
45 | float:right;
46 | margin-top:0;
47 | }
48 |
49 | #button_run {
50 | margin-left:2%;
51 | }
52 |
53 | #logo {
54 | font-size: 48px;
55 | padding-left: 10px;
56 | padding-right: 10px;
57 | float:left;
58 | height:calc(100% - 3px);
59 | }
60 |
61 | .headerBtn {
62 | width: 40px;
63 | margin-left: 5px;
64 | margin-right: 5px;
65 | text-align: center;
66 | cursor: pointer;
67 | font-size: 13px;
68 | /*padding-right: 10px;
69 | padding-left: 10px;*/
70 | padding-top: 7px;
71 | float:right;
72 | height:calc(100% - 7px);
73 | -webkit-touch-callout: none;
74 | -webkit-user-select: none;
75 | -khtml-user-select: none;
76 | -moz-user-select: none;
77 | -ms-user-select: none;
78 | user-select: none;
79 | }
80 |
81 | .headerBtn.wide {
82 | width: 80px;
83 | }
84 |
85 | .wideBtn p {
86 | float: right;
87 | }
88 |
89 | .headerBtn p {
90 | margin-top: 4px;
91 | font-size: 11px;
92 | }
93 |
94 | .headerBtn img {
95 | margin-top:2px;
96 | }
97 |
98 | .btnDisabled {
99 | opacity: 0.4;
100 | -webkit-filter: grayscale(100%);
101 | -moz-filter: grayscale(100%);
102 | -o-filter: grayscale(100%);
103 | -ms-filter: grayscale(100%);
104 | filter: grayscale(100%);
105 | }
106 |
107 | #loadSD, #import {
108 | margin-left: 40px;
109 | }
110 |
111 | .sdImage {
112 | height:35px;
113 | cursor:pointer;
114 | }
115 |
116 | .slimImage {
117 | height:17px;
118 | cursor:pointer;
119 | }
120 |
121 | /* css styles for ui module begin here */
122 | #hider {
123 | width: 100%;
124 | height: 100%;
125 | position: absolute;
126 | top: 0px;
127 | left: 0px;
128 | background-color: rgba(100,100,100,0.5);
129 | }
130 |
131 | /*#sdSelector*/#dialog {
132 | width: 350px;
133 | height: 100px;
134 | background-color: white;
135 | position: absolute;
136 | top: 50%;
137 | left: 50%;
138 | margin-top: -40px;
139 | margin-left: -126px;
140 | border-radius: 7px;
141 | text-align: center;
142 | }
143 |
144 | /*#selectionTable*/#dialogTable {
145 | font-size: 13px;
146 | margin-top: 10px;
147 | margin-left: auto;
148 | margin-right: auto;
149 | }
150 |
151 | /*#sdChoice*/.dialogChoice {
152 | width: 215px;
153 | float: right;
154 | }
155 |
156 | /*#osChoice {
157 | width: 110px;
158 | float: right;
159 | }*/
160 |
161 | /*#writeButton*/.dialogButton {
162 | margin-top: 12px;
163 | margin-left: 5px;
164 | margin-right: 5px;
165 | }
166 |
167 | /*#writingMessage
168 | {
169 | width: 250px;
170 | height: 200px;
171 | background-color: white;
172 | position: absolute;
173 | top: 50%;
174 | left: 50%;
175 | margin-top: -100px;
176 | margin-left: -126px;
177 | border-radius: 7px;
178 | text-align: center;
179 | }*/
180 |
181 | /*#writeProgress {
182 | margin-top: 120px;
183 | }*/
184 |
185 | /*#writeProgressbar*/#dialogProgress {
186 | margin-top: 8px;
187 | }
188 |
189 | #dialogProgressText {
190 | margin-top: -1px;
191 | font-size: 11px;
192 | }
193 |
194 | /*#writeDetailedProgress
195 | {
196 | margin-top: 9px;
197 | font-size: 11px;
198 | }*/
199 |
200 | /*#writeAnimation*/#dialogImage {
201 | max-width: 160px;
202 | max-height: 90px;
203 | }
204 | /*.updateAnimation
205 | {
206 | margin-top: 10px;
207 | margin-bottom: 10px;
208 | }*/
209 | /*#closeBtn
210 | {
211 | margin-top: -7px;
212 | }*/
213 | /*.infoParagraph {
214 | font-size: 12px;
215 | margin-left: 6px;
216 | margin-right: 6px;
217 | }*/
218 | /*.blockInfo {
219 | font-size: 10px;
220 | padding-left: 10px;
221 | padding-right: 10px;
222 | }*/
223 |
224 | #dialogInfo {
225 | font-size: 10px;
226 | padding-left: 10px;
227 | padding-right: 10px;
228 | }
229 |
230 | .spinner {
231 | margin: 100px auto 0;
232 | width: 70px;
233 | text-align: center;
234 | }
235 | .spinner > div {
236 | width: 18px;
237 | height: 18px;
238 | background-color: black;
239 |
240 | border-radius: 100%;
241 | display: inline-block;
242 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
243 | animation: sk-bouncedelay 1.4s infinite ease-in-out both;
244 | }
245 |
246 | .spinner .bounce1 {
247 | -webkit-animation-delay: -0.32s;
248 | animation-delay: -0.32s;
249 | }
250 |
251 | .spinner .bounce2 {
252 | -webkit-animation-delay: -0.16s;
253 | animation-delay: -0.16s;
254 | }
255 |
256 | @-webkit-keyframes sk-bouncedelay {
257 | 0%, 80%, 100% { -webkit-transform: scale(0) }
258 | 40% { -webkit-transform: scale(1.0) }
259 | }
260 |
261 | @keyframes sk-bouncedelay {
262 | 0%, 80%, 100% {
263 | -webkit-transform: scale(0);
264 | transform: scale(0);
265 | } 40% {
266 | -webkit-transform: scale(1.0);
267 | transform: scale(1.0);
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/lib/blockloader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | load: load,
23 | validation: validation,
24 | blockMapper: blockMapper
25 | }
26 |
27 |
28 | var app = require('electron').app || require('electron').remote.app
29 | var fs = require('fs-extra')
30 | var path = require('path')
31 |
32 | var blockDirectory = path.join(app.getPath('appData'), 'PiBakery/blocks')
33 | var backupBlocks = path.join(__dirname, '../pibakery-blocks')
34 |
35 | var workspace = {}
36 | var blockMapper = {}
37 | var validation = []
38 | module.exports.blockMapper = blockMapper
39 | module.exports.validation = validation
40 |
41 |
42 | function load (workspace, toolbox, cb) {
43 | fs.stat(blockDirectory, function (error, stats) {
44 | // block directory doesn't exist - revert to backup blocks
45 | if (error) {
46 | loadFromDirectories([backupBlocks], workspace, toolbox, cb)
47 | return
48 | }
49 |
50 | // read the block directories and load them
51 | fs.readdir(blockDirectory, function (error, files) {
52 | if (error || files.length == 0) {
53 | // error or no directories - revert back to backup blocks
54 | loadFromDirectories([backupBlocks], workspace, toolbox, cb)
55 | return
56 | }
57 |
58 | for (var i = 0; i < files.length; i++) {
59 | files[i] = path.join(blockDirectory, files[i])
60 | }
61 |
62 | // load blocks from those directories
63 | loadFromDirectories(files, workspace, toolbox, function (error) {
64 | // either an error, or no blocks added
65 | if (error || Object.keys(blockMapper).length === 0) {
66 | loadFromDirectories([backupBlocks], workspace, toolbox, cb)
67 | return
68 | }
69 | cb(null)
70 | })
71 | })
72 | })
73 | }
74 |
75 |
76 | function loadFromDirectories (directories, workspace, toolbox, cb, count) {
77 | // first time calling? set count to 0
78 | if (typeof count === 'undefined') {
79 | count = 0
80 | }
81 |
82 | // done all directories? run callback
83 | if (count >= directories.length) {
84 | cb(null)
85 | return
86 | }
87 |
88 | var directory = directories[count]
89 | loadFromDirectory(directory, workspace, toolbox, function (error) {
90 | if (error) {
91 | // go onto next block
92 | loadFromDirectories(directories, workspace, toolbox, cb, (count + 1))
93 | return
94 | }
95 |
96 | loadFromDirectories(directories, workspace, toolbox, cb, (count + 1))
97 | })
98 | }
99 |
100 |
101 | function loadFromDirectory (directory, workspace, toolbox, cb) {
102 | // read the .json files from this directory
103 | readBlockFiles(directory, function (error, categoriesJSON, blocksJSON) {
104 | if (error) {
105 | cb(error)
106 | return
107 | }
108 |
109 | // load the categories from this directory
110 | loadCategories(categoriesJSON, toolbox)
111 |
112 | // load the blocks (async) from this directory
113 | loadBlocksAsync(blocksJSON, directory, toolbox, function (error) {
114 | if (error) {
115 | cb(error)
116 | return
117 | }
118 |
119 | // update the toolbox
120 | workspace.updateToolbox(toolbox)
121 | cb(null)
122 | })
123 | })
124 | }
125 |
126 |
127 | function readBlockFiles (directory, cb) {
128 | var blockFile = path.join(directory, 'info.json')
129 | var categoryFile = path.join(directory, 'categories.json')
130 |
131 | // read the block file
132 | fs.readFile(blockFile, 'utf8', function (error, blockInfo) {
133 | if (error) {
134 | cb(error)
135 | return
136 | }
137 |
138 | // also read the categories file
139 | fs.readFile(categoryFile, 'utf8', function (error, categoryInfo) {
140 | if (error) {
141 | cb(error)
142 | return
143 | }
144 |
145 | // get them both from JSON into javascript object
146 | try {
147 | var categoriesJSON = JSON.parse(categoryInfo).categories
148 | var blocksJSON = JSON.parse(blockInfo).loadOrder
149 | } catch (error) {
150 | cb(error)
151 | return
152 | }
153 |
154 | // return the JSON data
155 | cb(null, categoriesJSON, blocksJSON)
156 | })
157 | })
158 | }
159 |
160 |
161 | function loadCategories (categoriesJSON, toolbox) {
162 | // add in the custom categories
163 | for (var i = 0; i < categoriesJSON.length; i++) {
164 | categoriesJSON[categoriesJSON[i].name] = categoriesJSON[i].colour
165 | var newCategory = document.createElement('category')
166 | newCategory.setAttribute('name', categoriesJSON[i].display)
167 | newCategory.setAttribute('colour', categoriesJSON[i].colour)
168 | newCategory.setAttribute('id', categoriesJSON[i].name)
169 | toolbox.appendChild(newCategory)
170 | }
171 | }
172 |
173 |
174 | function loadBlocksAsync (blocks, directory, toolbox, cb, count) {
175 | if (typeof count === 'undefined') {
176 | count = 0
177 | }
178 |
179 | if (count >= blocks.length) {
180 | cb(null)
181 | return
182 | }
183 |
184 | var blockName = blocks[count]
185 | var jsonPath = path.join(directory, blockName, (blockName + '.json'))
186 |
187 | fs.readFile(jsonPath, 'utf8', function (error, data) {
188 | if (error) {
189 | cb(error)
190 | return
191 | }
192 |
193 | // parse the json
194 | try {
195 | var blockJSON = JSON.parse(data)
196 | } catch (error) {
197 | cb(error)
198 | return
199 | }
200 |
201 | // import this block
202 | importBlock(blockJSON, toolbox)
203 |
204 | // add this block to the path mapper
205 | var blockPath = path.join(directory, blockName)
206 | blockMapper[blockName] = blockPath
207 |
208 | // go load the next block
209 | loadBlocksAsync(blocks, directory, toolbox, cb, (count + 1))
210 | })
211 | }
212 |
213 |
214 | function importBlock (blockJSON, toolbox) {
215 | blockJSON.type = blockJSON.category
216 |
217 | // get the block properties
218 | var blockName = blockJSON.name
219 | var blockText = blockJSON.text
220 | var blockArgs = blockJSON.args
221 | var numArgs = blockArgs.length
222 | var supportedOperatingSystems = blockJSON.supportedOperatingSystems
223 |
224 | // get the block colour
225 | var blockColour = 0
226 | for (var i = 0; i < toolbox.children.length; i++) {
227 | if (toolbox.children[i].id === blockJSON.type) {
228 | blockColour = toolbox.children[i].getAttribute('colour')
229 | }
230 | }
231 |
232 | // create the blockly JSON object
233 | var blocklyBlock = {}
234 | blocklyBlock.id = blockName
235 | blocklyBlock.colour = blockColour
236 | blocklyBlock.helpUrl = blockJSON.longDescription
237 | blocklyBlock.tooltip = blockJSON.shortDescription
238 |
239 | // if the block has multiple lines, implement that in the blockly standard
240 | var currentCount = 0
241 | while (blockText.indexOf('\\n') !== -1) {
242 | blockText = blockText.replace('\\n', ('%' + (numArgs + 1 + currentCount)))
243 | currentCount++
244 | }
245 | blocklyBlock.message0 = blockText
246 |
247 | // loop through the arguments adding them to the blockly object
248 | blocklyBlock.args0 = []
249 | for (var i = 0; i < blockJSON.args.length; i++) {
250 | var newArg = {}
251 | var currentArg = blockJSON.args[i]
252 | if (currentArg.type === 'number' || currentArg.type === 'text') {
253 | newArg.type = 'field_input'
254 | newArg.name = i + 1
255 | newArg.text = currentArg.default
256 | validation.push({
257 | block: blockName,
258 | field: i + 1,
259 | max: currentArg.maxLength,
260 | type: currentArg.type
261 | })
262 | } else if (currentArg.type === 'menu') {
263 | newArg.type = 'field_dropdown'
264 | newArg.name = i + 1
265 | newArg.options = []
266 | for (var j = 0; j < currentArg.options.length; j++) {
267 | var currentOption = currentArg.options[j]
268 | var newOption = [currentOption, currentOption]
269 | newArg.options.push(newOption)
270 | }
271 | } else if (currentArg.type === 'check') {
272 | newArg.type = 'field_checkbox'
273 | newArg.name = i + 1
274 | newArg.checked = currentArg.default
275 | }
276 | blocklyBlock.args0.push(newArg)
277 | }
278 | for (var i = 0; i < currentCount; i++) {
279 | blocklyBlock.args0.push({type: 'input_dummy'})
280 | }
281 | blocklyBlock.previousStatement = true
282 | blocklyBlock.nextStatement = true
283 |
284 | // blocklyBlock has been created - now we need to add it into PiBakery
285 | Blockly.Blocks[blockName] = {
286 | init: function () {
287 | this.jsonInit(blocklyBlock)
288 | this.setNextStatement(blockJSON.continue)
289 | }
290 | }
291 |
292 | // That's us finished with blocklyBlock - now go and creat the code generator
293 | Blockly.PiBakery[blockName] = function (block) {
294 | var code = '\n\tchmod 755 /boot/PiBakery/blocks/' + blockName + '/' + blockJSON.script
295 | code = code + '\n\t/boot/PiBakery/blocks/' + blockName + '/' + blockJSON.script + ' '
296 | for (var i = 0; i < blockJSON.args.length; i++) {
297 | var currentArg = bashEscape(block.getFieldValue(i + 1))
298 | if (currentArg === '') {
299 | currentArg = '""'
300 | }
301 | code = code + currentArg + ' '
302 | }
303 | code = code.slice(0, -1)
304 | if (blockJSON.network) {
305 | code = code + '\n\tNETWORK=True'
306 | }
307 | return code
308 | }
309 |
310 | // And the last thing to to is to add that block to the toolbox
311 | var newBlock = document.createElement('block')
312 | newBlock.setAttribute('type', blockName)
313 | document.getElementById(blockJSON.type).appendChild(newBlock)
314 | }
315 |
316 |
317 | function bashEscape (arg) {
318 | // Thanks to creationix on GitHub
319 | var safePattern = /^[a-z0-9_\/\-.,?:@#%^+=\[\]]*$/i
320 | var safeishPattern = /^[a-z0-9_\/\-.,?:@#%^+=\[\]{}|&()<>; *']*$/i
321 | if (safePattern.test(arg)) {
322 | return arg
323 | }
324 | if (safeishPattern.test(arg)) {
325 | return '"' + arg + '"'
326 | }
327 | return "'" + arg.replace(/'+/g, function (val) {
328 | if (val.length < 3) {
329 | return "'" + val.replace(/'/g, "\\'") + "'"
330 | }
331 | return '\'"' + val + '"\''
332 | }) + "'"
333 | }
334 |
--------------------------------------------------------------------------------
/lib/blocks.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | inject: inject,
23 | loadBlocks: loadBlocks,
24 | validateField: validateField,
25 | generateScript: generateScript,
26 | getXml: getXml,
27 | loadXml: loadXml
28 | }
29 |
30 |
31 | var pd = require('pretty-data').pd
32 | var fs = require('fs-extra')
33 | var path = require('path')
34 | var blockloader = require('./blockloader.js')
35 | var blockupdates = require('./blockupdates.js')
36 | var blocktransform = require('./blocktransform.js')
37 |
38 |
39 | var workspace = {}
40 | var categories = {}
41 |
42 | var validation = blockloader.validation
43 | var blockMapper = blockloader.blockMapper
44 |
45 |
46 | function loadBlocks (toolbox, cb) {
47 | // check and perform update if necessary
48 | blockupdates.update(function (error) {
49 | if (error) {
50 | console.error(error)
51 | }
52 |
53 | // load the blocks
54 | blockloader.load(workspace, toolbox, cb)
55 | })
56 | }
57 |
58 |
59 | function inject (editor, toolbox) {
60 | //module.exports.workspace = Blockly.inject(editor, {toolbox: toolbox})
61 | workspace = Blockly.inject(editor, {toolbox: toolbox})
62 | //module.exports.workspace.addChangeListener(validateBlocks)
63 | workspace.addChangeListener(validateBlocks)
64 | }
65 |
66 |
67 | function bashEscape (arg) {
68 | // Thanks to creationix on GitHub
69 | var safePattern = /^[a-z0-9_\/\-.,?:@#%^+=\[\]]*$/i
70 | var safeishPattern = /^[a-z0-9_\/\-.,?:@#%^+=\[\]{}|&()<>; *']*$/i
71 | if (safePattern.test(arg)) {
72 | return arg
73 | }
74 | if (safeishPattern.test(arg)) {
75 | return '"' + arg + '"'
76 | }
77 | return "'" + arg.replace(/'+/g, function (val) {
78 | if (val.length < 3) {
79 | return "'" + val.replace(/'/g, "\\'") + "'"
80 | }
81 | return '\'"' + val + '"\''
82 | }) + "'"
83 | }
84 |
85 |
86 | /**
87 | * @desc called every time a block field is edited, checks to see if the input
88 | * to the block is valid for that block
89 | * @param object block - the block object that contains all the info about the
90 | * block that has been edited, including info about the block's fields
91 | * @param string value - the current value of the field being edited
92 | * @return null
93 | */
94 | function validateField (block, value) {
95 | for (var i = 0; i < block.inputList.length; i++) {
96 | for (var j = 0; j < block.inputList[i].fieldRow.length; j++) {
97 | if (block.inputList[i].fieldRow[j].text_ === value) {
98 | for (var k = 0; k < validation.length; k++) {
99 | if (validation[k].block === block.type && validation[k].field === block.inputList[i].fieldRow[j].name) {
100 | if (validation[k].type === 'number') {
101 | value = value.replace(/[^0-9]/g, '')
102 | }
103 | if (validation[k].max !== 0) {
104 | if (value.length > validation[k].max) {
105 | return value.substring(0, validation[k].max)
106 | }
107 | }
108 | }
109 | }
110 | }
111 | }
112 | }
113 | return value
114 | }
115 |
116 |
117 | /**
118 | * @desc called whenever blocks are moved around, checks to make sure the user
119 | * hasn't put the shutdown or reboot block with the oneveryboot startup
120 | * @param object event - passed from blockly
121 | * @return null
122 | */
123 | function validateBlocks (event) {
124 | // generate the code for the blocks
125 | //var code = window.Blockly.PiBakery.workspaceToCode(module.exports.workspace)
126 | var code = window.Blockly.PiBakery.workspaceToCode(workspace)
127 | code = code.split('\n')
128 |
129 | var everyBootCode = ''
130 | var codeType = ''
131 | var expectHat = true
132 |
133 | // move the code sections into their variables (firstboot, everyboot, etc.)
134 | for (var i = 0; i < code.length; i++) {
135 | var currentLine = code[i]
136 | if (currentLine.indexOf('\t') === 0 && expectHat === false) {
137 | if (codeType === 'everyBoot') {
138 | if (everyBootCode === '') {
139 | everyBootCode = currentLine.replace('\t', '')
140 | } else {
141 | everyBootCode = everyBootCode + '\n' + currentLine.replace(' ', '')
142 | }
143 | }
144 | } else if (currentLine === '_pibakery-oneveryboot') {
145 | codeType = 'everyBoot'
146 | expectHat = false
147 | } else if (currentLine === '_pibakery-onfirstboot') {
148 | codeType = 'firstBoot'
149 | expectHat = false
150 | } else if (currentLine === '_pibakery-onnextboot') {
151 | codeType = 'nextBoot'
152 | expectHat = false
153 | } else if (currentLine === '') {
154 | expectHat = true
155 | }
156 | }
157 |
158 | // check to see if the shutdown or reboot block has been used with everyboot
159 | if ((everyBootCode.split('\n')[everyBootCode.split('\n').length - 1] === '/boot/PiBakery/blocks/shutdown/shutdown.sh') ||
160 | (everyBootCode.split('\n')[everyBootCode.split('\n').length - 1] === '/boot/PiBakery/blocks/reboot/reboot.sh')) {
161 | //module.exports.workspace.getBlockById(event.blockId).unplug()
162 | workspace.getBlockById(event.blockId).unplug()
163 | //module.exports.workspace.getBlockById(event.blockId).bumpNeighbours_()
164 | workspace.getBlockById(event.blockId).bumpNeighbours_()
165 | // alert("You can't put that block there.")
166 | }
167 | }
168 |
169 |
170 | // generates the script of the blocks
171 | // returns array [everyBootCode, firstBootCode, nextBootCode, neededBlocks,
172 | // waitForNetwork] - the every boot script, the first boot script, the next
173 | // boot script, the list of blocks needed to be copied, and whether PiBakery
174 | // should wait for a network connection before running the scripts
175 | function generateScript () {
176 | //var code = window.Blockly.PiBakery.workspaceToCode(module.exports.workspace)
177 | var code = window.Blockly.PiBakery.workspaceToCode(workspace)
178 | code = code.split('\n')
179 |
180 | var firstBootCode = ''
181 | var everyBootCode = ''
182 | var nextBootCode = ''
183 |
184 | var firstBootCount = 0
185 | var everyBootCount = 0
186 | var nextBootCount = 0
187 |
188 | var codeType = ''
189 | var neededBlocks = []
190 | var expectHat = true
191 |
192 | var networkRequiredPosition = [-1, -1, -1]
193 | var wifiPosition = [-1, -1, -1]
194 | var waitForNetwork = [false, false, false]
195 |
196 | for ( var x = 0; x < code.length; x++) {
197 | var currentLine = code[x]
198 |
199 | if (currentLine.indexOf('\t') == 0 && expectHat == false) {
200 | if (currentLine != '\tNETWORK=True') {
201 | // add the blockname to our list
202 | var blockName = currentLine.split('/boot/PiBakery/blocks/')[1].split('/')[0]
203 | if (neededBlocks.indexOf(blockName) == -1) {
204 | neededBlocks.push(blockName)
205 | }
206 |
207 | // actually generate the code (with whiptail dialogs as well)
208 | if (codeType == 'everyBoot') {
209 | everyBootCount++
210 | everyBootCode = everyBootCode + '\n' + currentLine.replace('\t', '') + ' >>/boot/PiBakery/everyboot.log 2>&1 || true'
211 | // everyBootCode = everyBootCode + "\necho $(expr $PERCENTAGE \\* " + everyBootCount + " )"
212 | everyBootCode = everyBootCode + '\necho XXX\necho $(expr $PERCENTAGE \\* ' + everyBootCount + ' )\necho "\\nProcessing Every Boot Script\\n\\nRunning Block: ' + currentLine.split('/boot/PiBakery/blocks/')[1].split('/')[0] + '"\necho XXX'
213 | }
214 | else if (codeType == 'firstBoot') {
215 | firstBootCount++
216 | firstBootCode = firstBootCode + '\n' + currentLine.replace('\t', '') + ' >>/boot/PiBakery/firstboot.log 2>&1 || true'
217 | // firstBootCode = firstBootCode + "\necho $(expr $PERCENTAGE \\* " + firstBootCount + " )"
218 | firstBootCode = firstBootCode + '\necho XXX\necho $(expr $PERCENTAGE \\* ' + firstBootCount + ' )\necho "\\nProcessing First Boot Script\\n\\nRunning Block: ' + currentLine.split('/boot/PiBakery/blocks/')[1].split('/')[0] + '"\necho XXX'
219 | }
220 | else if (codeType == 'nextBoot') {
221 | nextBootCount++
222 | nextBootCode = nextBootCode + '\n' + currentLine.replace('\t', '') + ' >>/boot/PiBakery/nextboot.log 2>&1 || true'
223 | // nextBootCode = nextBootCode + "\necho $(expr $PERCENTAGE \\* " + nextBootCount + " )"
224 | nextBootCode = nextBootCode + '\necho XXX\necho $(expr $PERCENTAGE \\* ' + nextBootCount + ' )\necho "\\nProcessing Next Boot Script\\n\\nRunning Block: ' + currentLine.split('/boot/PiBakery/blocks/')[1].split('/')[0] + '"\necho XXX'
225 | }
226 |
227 | // handle the waitForNetwork stuff
228 | if (blockName == 'wifisetup') {
229 | if (codeType == 'everyBoot') {
230 | wifiPosition[0] = everyBootCount
231 | }
232 | else if (codeType == 'firstBoot') {
233 | wifiPosition[1] = firstBootCount
234 | }
235 | else if (codeType == 'nextBoot') {
236 | wifiPosition[2] = nextBootCount
237 | }
238 | }
239 | }
240 | else if (currentLine == '\tNETWORK=True') {
241 | if (codeType == 'everyBoot') {
242 | networkRequiredPosition[0] = everyBootCount
243 | }
244 | else if (codeType == 'firstBoot') {
245 | networkRequiredPosition[1] = firstBootCount
246 | }
247 | else if (codeType == 'nextBoot') {
248 | networkRequiredPosition[2] = nextBootCount
249 | }
250 | }
251 | }
252 | else if (currentLine == '_pibakery-oneveryboot') {
253 | codeType = 'everyBoot'
254 | expectHat = false
255 | }
256 | else if (currentLine == '_pibakery-onfirstboot') {
257 | codeType = 'firstBoot'
258 | expectHat = false
259 | }
260 | else if (currentLine == '_pibakery-onnextboot') {
261 | codeType = 'nextBoot'
262 | expectHat = false
263 | }
264 | else if (currentLine == '') {
265 | expectHat = true
266 | }
267 | }
268 |
269 | if (firstBootCode != '') {
270 | firstBootCode = '#!/bin/bash\n\nPERCENTAGE=' + Math.floor(100 / firstBootCount) + '\n\n{' + firstBootCode + '\necho 100\n} | whiptail --title "PiBakery" --gauge "\\nProcessing First Boot Script\\n\\n\\n" 11 40 0'
271 | }else {
272 | firstBootCode = '#!/bin/bash'
273 | }
274 |
275 | if (everyBootCode != '') {
276 | everyBootCode = '#!/bin/bash\n\nPERCENTAGE=' + Math.floor(100 / everyBootCount) + '\n\n{' + everyBootCode + '\necho 100\n} | whiptail --title "PiBakery" --gauge "\\nProcessing Every Boot Script\\n\\n\\n" 11 40 0'
277 | }else {
278 | everyBootCode = '#!/bin/bash'
279 | }
280 |
281 | if (nextBootCode != '') {
282 | nextBootCode = '#!/bin/bash\n\nPERCENTAGE=' + Math.floor(100 / nextBootCount) + '\n\n{' + nextBootCode + '\necho 100\n} | whiptail --title "PiBakery" --gauge "\\nProcessing Next Boot Script\\n\\n\\n" 11 40 0'
283 | }else {
284 | nextBootCode = '#!/bin/bash'
285 | }
286 |
287 | // if we do need a network connection, and (there is not wifi) or (there is wifi but it's after we need network)
288 | if ((networkRequiredPosition[0] != -1) && (wifiPosition[0] == -1 || (wifiPosition[0] != -1 && networkRequiredPosition[0] < wifiPosition[0]))) {
289 | waitForNetwork[0] = true
290 | }
291 |
292 | if ((networkRequiredPosition[1] != -1) && (wifiPosition[1] == -1 || (wifiPosition[1] != -1 && networkRequiredPosition[1] < wifiPosition[1]))) {
293 | waitForNetwork[1] = true
294 | }
295 |
296 | if ((networkRequiredPosition[2] != -1) && (wifiPosition[2] == -1 || (wifiPosition[2] != -1 && networkRequiredPosition[2] < wifiPosition[2]))) {
297 | waitForNetwork[2] = true
298 | }
299 |
300 | // create the proper paths for the needed blocks
301 | var blockPaths = []
302 | for (var i = 0; i < neededBlocks.length; i++) {
303 | blockPaths.push(blockMapper[neededBlocks[i]])
304 | }
305 |
306 | return {
307 | everyBoot: everyBootCode,
308 | firstBoot: firstBootCode,
309 | nextBoot: nextBootCode,
310 | blocks: neededBlocks,
311 | blockPaths: blockPaths,
312 | waitForNetwork: waitForNetwork
313 | }
314 | //return [everyBootCode, firstBootCode, nextBootCode, neededBlocks, waitForNetwork]
315 | }
316 |
317 |
318 | function getXml () {
319 | // get the XML text version of the boot type
320 |
321 | // convert the blocks to xml
322 | var blocksXml = Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(workspace))
323 |
324 | // add in the xml element representing the firstboot status - legacy reasons
325 | var xmlElement = (new window.DOMParser()).parseFromString(blocksXml, 'text/xml')
326 | var firstboot = xmlElement.createElement('firstboot')
327 | firstboot.appendChild(xmlElement.createTextNode('0'))
328 | xmlElement.getElementsByTagName('xml')[0].appendChild(firstboot)
329 |
330 | // serialise the xml element to string
331 | blocksXml = new XMLSerializer().serializeToString(xmlElement)
332 | var prettyXml = pd.xml(blocksXml)
333 | return prettyXml
334 | }
335 |
336 |
337 | function loadXml (data) {
338 | // perform transformations on the blocks
339 | var xml = blocktransform.transform(data)
340 |
341 | // load it into blockly
342 | Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace)
343 | }
344 |
--------------------------------------------------------------------------------
/lib/blocktransform.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | transform: transform
23 | }
24 |
25 |
26 | function transform (xml) {
27 | var parser = new DOMParser()
28 | xml = parser.parseFromString(xml, 'application/xml')
29 | var blocks = xml.getElementsByTagName('block')
30 |
31 | while (true) {
32 | var blocks = xml.getElementsByTagName('block')
33 |
34 | var madeTransformation = false
35 | for (var i = 0; i < blocks.length; i++) {
36 | var block = blocks[i]
37 |
38 | var transformed = transformBlock(block, xml)
39 | if (transformed) {
40 | madeTransformation = true
41 | break
42 | }
43 | }
44 |
45 | if (!madeTransformation) {
46 | break
47 | }
48 | }
49 |
50 | var string = new XMLSerializer().serializeToString(xml);
51 | return string
52 | }
53 |
54 |
55 | function transformBlock (block, xml) {
56 | if (isOldWiFi(block)) {
57 | transformOldWiFi(block)
58 | return true
59 | }
60 |
61 | if (isOldVncInstall(block)) {
62 | transformOldVncInstall(block)
63 | return true
64 | }
65 |
66 | if (isOldVncStart(block)) {
67 | transformOldVncStart(block)
68 | return true
69 | }
70 |
71 | return false
72 | }
73 |
74 |
75 | function isOldWiFi (block) {
76 | if (block.getAttribute('type') === 'wifisetup') {
77 | var fields = block.getElementsByTagName('field')
78 | for (var i = 0; i < fields.length; i++) {
79 | var field = fields[i]
80 | if (field.getAttribute('name') === '4') {
81 | return false
82 | }
83 | }
84 |
85 | return true
86 | }
87 |
88 | return false
89 | }
90 |
91 |
92 | function transformOldWiFi (block) {
93 | var countryCode = document.createElement('field')
94 | countryCode.setAttribute('name', '4')
95 | countryCode.innerText = 'GB'
96 | block.appendChild(countryCode)
97 | //return block
98 | return
99 | }
100 |
101 |
102 | function isOldVncInstall (block) {
103 | return (block.getAttribute('type') === 'vncserver')
104 | }
105 |
106 |
107 | function transformOldVncInstall (block) {
108 | // basically remove this block - vnc is already installed now
109 | var nexts = block.getElementsByTagName('next')
110 | var next = false
111 | for (var i = 0; i < nexts.length; i++) {
112 | if (nexts[i].parentNode === block) {
113 | next = nexts[i]
114 | break
115 | }
116 | }
117 |
118 | if (next === false) {
119 | // there is no block after this one, just remove it
120 | block.parentNode.parentNode.removeChild(block.parentNode)
121 | //return block
122 | return
123 | }
124 |
125 | // there is another block after this, replace this block with that one
126 | //block.parentNode.innerHTML = next.innerHTML
127 | block.parentNode.parentNode.appendChild(next)
128 | block.parentNode.parentNode.removeChild(block.parentNode)
129 | }
130 |
131 |
132 | function isOldVncStart (block) {
133 | return (block.getAttribute('type') === 'vncstart')
134 | }
135 |
136 |
137 | function transformOldVncStart (block) {
138 | block.setAttribute('type', 'vncenable')
139 | var field = block.getElementsByTagName('field')[0]
140 | field.innerText = 'Enable'
141 |
142 | return
143 | }
144 |
--------------------------------------------------------------------------------
/lib/blockupdates.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | update: update
23 | }
24 |
25 |
26 | var app = require('electron').app || require('electron').remote.app
27 | var fs = require('fs-extra')
28 | var path = require('path')
29 | var request = require('request')
30 | var unzipper = require('unzipper')
31 | var digestStream = require('digest-stream')
32 | var settings = require('./settings.js')
33 |
34 | var downloadCorrect = undefined
35 | var blockDirectory = path.join(app.getPath('appData'), 'PiBakery/blocks')
36 |
37 |
38 | function update (cb) {
39 | fs.mkdir(blockDirectory, function (error) {
40 | if (error && error.code !== 'EEXIST') {
41 | cb(error)
42 | return
43 | }
44 |
45 | settings.get('blocksources', function (error, sources) {
46 | if (error || sources.length === 0) {
47 | cb(error)
48 | return
49 | }
50 |
51 | updateBlockSources(sources, cb)
52 | })
53 | })
54 | }
55 |
56 |
57 | function updateBlockSources (sources, cb, count) {
58 | if (typeof count === 'undefined') {
59 | count = 0
60 | }
61 |
62 | if (count >= sources.length) {
63 | cb(null)
64 | return
65 | }
66 |
67 | var source = sources[count]
68 | var updateUrl = source.url + '?' + Math.random().toString(36).substr(2, 5)
69 | var infoFile = path.join(blockDirectory, source.name, 'info.json')
70 |
71 | // get the update json file
72 | request(updateUrl, {json: true}, function (error, response, body) {
73 | if (error) {
74 | updateBlockSources(sources, cb, (count + 1))
75 | return
76 | }
77 |
78 | // read te local json file
79 | fs.readFile(infoFile, 'utf8', function (error, data) {
80 | if (error && error.code !== 'ENOENT') {
81 | updateBlockSources(sources, cb, (count + 1))
82 | return
83 | }
84 |
85 | if (error && error.code === 'ENOENT') {
86 | // if no such local file exists, set the data to version 0 to force an update
87 | data = { version: 0.0 }
88 | }
89 |
90 | // see if there's an update
91 | var updateVersion = body.version
92 | var currentVersion = data.version
93 | if (updateVersion <= currentVersion) {
94 | // no update available, go to next block
95 | updateBlockSources(sources, cb, (count + 1))
96 | return
97 | }
98 |
99 | // there is an update available, perform it
100 | updateSource(body, source, function (error) {
101 | updateBlockSources(sources, cb, (count + 1))
102 | return
103 | })
104 | })
105 | })
106 | }
107 |
108 |
109 | function updateSource (updateJson, settingJson, cb) {
110 | var savename = path.join(blockDirectory, settingJson.name)
111 | var url = updateJson.downloadUrl + '?' + Math.random().toString(36).substr(2, 5)
112 |
113 | // first delete the current directory
114 | fs.remove(savename, function (error) {
115 | if (error && error.code !== 'ENOENT') {
116 | cb()
117 | return
118 | }
119 |
120 | // now perform the download
121 | request.get(url)
122 |
123 | // check the hash of the tar file
124 | .pipe(digestStream('md5', 'hex', function (hash, length) {
125 | downloadCorrect = (hash === updateJson.compressedMD5)
126 | }))
127 |
128 | // extract the file
129 | .pipe(unzipper.Extract({ path: blockDirectory }))
130 |
131 | // call downloadComplete when it's done
132 | .promise()
133 | .then(function () {
134 | downloadComplete(updateJson, settingJson, cb)
135 | }, cb)
136 | })
137 | }
138 |
139 |
140 | // called when download is complete
141 | function downloadComplete (updateJson, settingJson, cb) {
142 | // if not calculated yet, re-run in 1 second
143 | if (downloadCorrect === 'undefined') {
144 | setTimeout(function () {
145 | downloadComplete(updateJson, settingJson, cb)
146 | }, 1000)
147 | return
148 | }
149 |
150 | // check download hash matched correctly
151 | if (!downloadCorrect) {
152 | cb()
153 | return
154 | }
155 |
156 | // get the name the zip extracted to
157 | var extractName = 'pibakery-blocks-new'
158 | if ('extract' in updateJson) {
159 | extractName = updateJson.extract
160 | }
161 |
162 | // where we are moving from and to
163 | var source = path.join(blockDirectory, extractName)
164 | var target = path.join(blockDirectory, settingJson.name)
165 |
166 | // move the downloaded directory to the target directory
167 | fs.move(source, target, {overwrite: true}, function (error) {
168 | if (error) {
169 | cb()
170 | return
171 | }
172 |
173 | cb()
174 | })
175 | }
176 |
--------------------------------------------------------------------------------
/lib/cmdline-parser.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | parse: parse,
23 | write, write,
24 | get, get,
25 | set, set
26 | }
27 |
28 | var fs = require('fs-extra')
29 | var childProcess = require('child_process')
30 |
31 | var parsed = []
32 |
33 |
34 | // parse a given cmdline.txt file
35 | function parse (filename, cb) {
36 | // reset the variables
37 | parsed = []
38 |
39 | // read the cmdline.txt file
40 | fs.readFile(filename, 'utf8', function readComplete (error, contents) {
41 | if (error) {
42 | cb(error)
43 | return
44 | }
45 |
46 | // now parse the file into paramaters and attributes
47 | contents = contents.trim()
48 | contents = contents.split(' ')
49 | for (var i = 0; i < contents.length; i++) {
50 |
51 | // special case for non key-values, just keys
52 | if (contents[i].indexOf('=') == -1) {
53 | var obj = {
54 | key: contents[i],
55 | value: null
56 | }
57 | parsed.push(obj)
58 | continue
59 | }
60 |
61 | // loop through all key-value pairs
62 | var pair = contents[i].split(/=/)
63 | var key = pair.shift()
64 | var value = pair.join('=')
65 |
66 | var obj = {
67 | key: key,
68 | value: value
69 | }
70 |
71 | parsed.push(obj)
72 | }
73 |
74 | cb(null)
75 | })
76 | }
77 |
78 |
79 | // write out the (probably modified) contents of parsed to new cmdline.txt file
80 | function write (filename, cb) {
81 | var pairs = []
82 |
83 | // loop through all pairs, combining them into a string
84 | for (var i = 0; i < parsed.length; i++) {
85 | var key = parsed[i].key
86 | var value = parsed[i].value
87 |
88 | var pair = key
89 | if (value) {
90 | pair = key + '=' + value
91 | }
92 | pairs.push(pair)
93 | }
94 |
95 | // join all pairs together as string, and write them to file
96 | var contents = pairs.join(' ').trim()
97 | fs.writeFile(filename, contents, 'utf8', function writeComplete (error) {
98 | if (error) {
99 | cb(error)
100 | return
101 | }
102 |
103 | // file was written successfully
104 | cb(null)
105 | })
106 | }
107 |
108 |
109 | // retrieve a paramater
110 | function get (paramater) {
111 | for (var i = 0; i < parsed.length; i++) {
112 | if (parsed[i].key == paramater) {
113 | return parsed[i].value
114 | }
115 | }
116 |
117 | return ''
118 | }
119 |
120 |
121 | // set a paramater
122 | function set (paramater, value) {
123 | for (var i = 0; i < parsed.length; i++) {
124 | if (parsed[i].key == paramater) {
125 | parsed[i].value = value
126 | return
127 | }
128 | }
129 |
130 | var obj = {
131 | key: paramater,
132 | value: value
133 | }
134 | parsed.push(obj)
135 | }
136 |
--------------------------------------------------------------------------------
/lib/elevate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Resin.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License")
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | var electron = require('electron')
20 | var isElevated = require('is-elevated')
21 | var sudoPrompt = require('sudo-prompt')
22 | var os = require('os')
23 | var platform = os.platform()
24 | var packageJSON = require('../package.json')
25 | var path = require('path')
26 |
27 | exports.require = function (additionalArguments, callback) {
28 | isElevated(function (error, elevated) {
29 | if (error) {
30 | return callback(error)
31 | }
32 |
33 | if (elevated) {
34 | return callback()
35 | }
36 |
37 | // copy the args and add in the additional arguments
38 | var args = process.argv.slice()
39 | for (var i = 0; i < additionalArguments.length; i++) {
40 | args.push(additionalArguments[i])
41 | }
42 |
43 | // case split for unix and windows
44 | if (platform === 'darwin' || platform === 'linux') {
45 |
46 | sudoPrompt.exec(args.join(' '), {
47 | name: packageJSON.displayName,
48 | icns: path.join(__dirname, '../app/img/icon.icns')
49 | }, callback)
50 |
51 | } else if (platform === 'win32') {
52 |
53 | const elevator = require('elevator')
54 | elevator.execute(args, {}, callback)
55 |
56 | }
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/lib/imgmanager.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | getImages: getImages,
23 | addImage: addImage
24 | }
25 |
26 | var path = require('path')
27 | var fs = require('fs-extra')
28 | var multifs = require('./multifs.js')
29 | var settings = require('./settings.js')
30 |
31 |
32 | function getImages (cb) {
33 | // get previously used images
34 | settings.get('previousImages', function (error, previousImages) {
35 | if (error) {
36 | cb(error)
37 | return
38 | }
39 |
40 | // see how many of the previousImages still exist
41 | multifs.exists(previousImages, function (error, existingImages) {
42 | if (error) {
43 | cb(error)
44 | return
45 | }
46 |
47 | // if the array is longer than 6 elements, only give the first 6
48 | if (existingImages.length > 6) {
49 | existingImages = array.slice(0, 6);
50 | }
51 |
52 | // convert list of operatingSystems to array of objects
53 | var existingImagesObjects = []
54 | for (var i = 0; i < existingImages.length; i++) {
55 | var existingImage = {
56 | display: path.basename(existingImages[i]),
57 | value: existingImages[i]
58 | }
59 | existingImagesObjects.push(existingImage)
60 | }
61 |
62 | // return all the previously used images that still exist
63 | cb(null, existingImagesObjects)
64 |
65 | // save the possibly truncated esults back to settings, do nothing with callback
66 | settings.set('previousImages', existingImages, function () {})
67 | })
68 | })
69 | }
70 |
71 |
72 | function addImage (imagePath) {
73 | settings.get('previousImages', function (error, currentImages) {
74 | if (error) {
75 | cb(error)
76 | return
77 | }
78 |
79 | // append the new image
80 | currentImages.push(imagePath)
81 |
82 | // save the results back to settings, do nothing with callback
83 | settings.set('previousImages', currentImages, function () {})
84 | })
85 | }
86 |
87 |
88 | // OLD CODE.
89 | /*function getOsPath () {
90 | if (process.platform === 'darwin') {
91 | // Mac stores the OS in Application Support
92 | return path.normalize('/Library/Application Support/PiBakery/os/')
93 | } else if (process.platform === 'win32' || process.platform === 'linux') {
94 | // Windows and Linux store the OS in install directory
95 | return path.join(__dirname, '/../os/')
96 | } else {
97 | return null
98 | }
99 | }
100 |
101 | function getImages (cb) {
102 | // get the directory that the operating systems (and images.json) are stored
103 | var operatingSystemDirectory = getOsPath()
104 |
105 | // read the images configuration file
106 | fs.readFile(path.join(operatingSystemDirectory, 'images.json'), 'utf8', function (error, data) {
107 | if (error) {
108 | cb(error, false)
109 | return
110 | }
111 |
112 | // load the json file into javascript object
113 | try {
114 | var localImagesJson = JSON.parse(data)
115 | } catch (error) {
116 | cb(error, false)
117 | return
118 | }
119 |
120 | // use function for loop
121 | loopThroughImages([], localImagesJson, 0, function installedImagesResult(error, installedImages) {
122 | cb(false, installedImages)
123 | })
124 | })
125 | }
126 |
127 | // extra function needed because we can't have callbacks in loops
128 | function loopThroughImages(installedImages, localImagesJson, counter, cb) {
129 | if (counter == localImagesJson.length) {
130 | // end of the loop, return the result
131 | cb(null, installedImages)
132 | return
133 | }
134 |
135 | var operatingSystemDirectory = getOsPath()
136 |
137 | // see if a specific image is installed (index counter)
138 | localImagesJson[counter].path = path.join(operatingSystemDirectory, localImagesJson[counter].filename)
139 | osIsInstalled(localImagesJson[counter].path, function(error, installed) {
140 | // if the os is installed, push it to the installedImages array
141 | if (installed) {
142 | installedImages.push(localImagesJson[counter])
143 | }
144 |
145 | // run on the next image by calling this function again, with the counter+1
146 | loopThroughImages(installedImages, localImagesJson, counter+1, cb)
147 | })
148 | }
149 |
150 | function osIsInstalled (filepath, cb) {
151 | // try to access the file, return whether we can or not
152 | fs.stat(filepath, function (error, stat) {
153 | if (!error) {
154 | cb(null, true)
155 | return
156 | }
157 | cb(error)
158 | })
159 | }*/
160 |
--------------------------------------------------------------------------------
/lib/kpartx.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | isInstalled: isInstalled,
23 | addPartitionMappings, addPartitionMappings,
24 | deletePartitionMappings, deletePartitionMappings
25 | }
26 |
27 | var fs = require('fs-extra')
28 | var childProcess = require('child_process')
29 |
30 |
31 | // cb(true|false)
32 | function isInstalled (cb) {
33 | // run command to determine if kpartx is installed
34 | childProcess.execFile('which', ['kpartx'], function (error, stdout, stderr) {
35 | if (error) {
36 | // if error, return false (system doesn't have kpartx)
37 | cb(false)
38 | return
39 | }
40 |
41 | // return true if there is a path to kpartx
42 | cb(stdout != '')
43 | })
44 | }
45 |
46 |
47 | function addPartitionMappings (device, cb) {
48 | // command to add mappers, with verbose output
49 | var cmd = 'kpartx -asv ' + device
50 | childProcess.execFile('kpartx', ['-asv', device], function (error, stdout, stderr) {
51 | if (error) {
52 | // if error, return error
53 | cb(error)
54 | return
55 | }
56 |
57 | // if there is content in stderr, then
58 | if (stderr.trim() != '') {
59 | var error = new Error(stderr)
60 | error.name = 'KPARTX_ADD_ERROR'
61 | cb(error)
62 | return
63 | }
64 |
65 | // check the stdout is as expected
66 | if (stdout.trim().indexOf('add map ') != 0) {
67 | var error = new Error('Unable to add mappings, output:' + stdout)
68 | error.name = 'KPARTX_ADD_ERROR'
69 | cb(error)
70 | return
71 | }
72 |
73 | // parse stdout to get the mapping paths and sizes
74 | var lines = stdout.split('\n')
75 | var maps = []
76 | for (var i = 0; i < lines.length; i++) {
77 | // loop through all the mounts
78 | var map = lines[i].split(' ')[2]
79 | var size = lines[i].split(' ')[5]
80 |
81 | if (map && size) {
82 | maps.push({
83 | map: map,
84 | size: size
85 | })
86 | }
87 | }
88 |
89 | cb(null, maps)
90 | })
91 | }
92 |
93 |
94 | function deletePartitionMappings (device, cb) {
95 | // command to delete mappers
96 | var cmd = 'kpartx -d ' + device
97 | childProcess.execFile('kpartx', ['-d', device], function (error, stdout, stderr) {
98 | if (error) {
99 | // if error, return error
100 | cb(error)
101 | return
102 | }
103 |
104 | // if there is content in stderr, then
105 | if (stderr.trim() != '') {
106 | var error = new Error(stderr)
107 | error.name = 'KPARTX_DELETE_ERROR'
108 | cb(error)
109 | return
110 | }
111 |
112 | // check stdout is as expected
113 | /*if (stdout.trim().indexOf('loop deleted : ') != 0) {
114 | var error = new Error('Unable to delete mappings, output:' + stdout)
115 | error.name = 'KPARTX_DELETE_ERROR'
116 | cb(error)
117 | return
118 | }*/
119 |
120 | cb(null)
121 | })
122 | }
123 |
--------------------------------------------------------------------------------
/lib/multifs.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | // Used to make copying of multiple files to multiple destinations easy
22 |
23 | module.exports = {
24 | copy: copy,
25 | write: write,
26 | exists: exists
27 | }
28 |
29 | var fs = require('fs-extra')
30 |
31 |
32 | function copy (files, cb, count) {
33 | if (typeof count === 'undefined') {
34 | count = 0
35 | }
36 |
37 | if (count == files.length) {
38 | cb()
39 | return
40 | }
41 |
42 | var source = files[count].source
43 | var destination = files[count].destination
44 | fs.copy(source, destination, function (error) {
45 | if (error) {
46 | cb(error)
47 | return
48 | }
49 |
50 | // do we need to set file permissions for this file?
51 | if ('chmod' in files[count]) {
52 | fs.chmod(destination, files[count].chmod, function (error) {
53 | if (error) {
54 | cb(error)
55 | return
56 | }
57 |
58 | // permissions set fine, copy next file
59 | copy(files, cb, count + 1)
60 | })
61 | } else {
62 | // that file copied fine, go and copy the next one
63 | copy(files, cb, count + 1)
64 | }
65 | })
66 | }
67 |
68 |
69 | function write (files, cb, count) {
70 | if (typeof count === 'undefined') {
71 | count = 0
72 | }
73 |
74 | if (count == files.length) {
75 | cb()
76 | return
77 | }
78 |
79 | var file = files[count].file
80 | var contents = files[count].contents
81 |
82 | // delete the file if it exists
83 | fs.remove(file, function (error) {
84 | if (error) {
85 | // for some reason we don't get an error if the file doesn't exist, so we
86 | // don't need to check for that condition
87 | cb(error)
88 | return
89 | }
90 |
91 | // now write the file
92 | fs.writeFile(file, contents, function (error) {
93 | if (error) {
94 | cb(error)
95 | return
96 | }
97 |
98 | // do we need to set file permissions for this file?
99 | if ('chmod' in files[count]) {
100 | fs.chmod(file, files[count].chmod, function (error) {
101 | if (error) {
102 | cb(error)
103 | return
104 | }
105 |
106 | // permissions set fine, write next file
107 | write(files, cb, count + 1)
108 | })
109 | } else {
110 | // that file wrote fine, go and write the next one
111 | write(files, cb, count + 1)
112 | }
113 | })
114 | })
115 | }
116 |
117 |
118 | function exists (files, cb, count, result) {
119 | if (typeof count === 'undefined') {
120 | count = 0
121 | }
122 |
123 | if (typeof result === 'undefined') {
124 | result = []
125 | }
126 |
127 | if (count == files.length) {
128 | cb(null, result)
129 | return
130 | }
131 |
132 | var file = files[count]
133 | fs.stat(file, function (error) {
134 | if (error && error.code != 'ENOENT') {
135 | cb(error)
136 | return
137 | }
138 |
139 | if (!error) {
140 | // file exists
141 | result.push(file)
142 | }
143 |
144 | // process next file
145 | exists(files, cb, count + 1, result)
146 | })
147 | }
148 |
--------------------------------------------------------------------------------
/lib/rootwriter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | // runs as root to write the sd cards
22 |
23 | module.exports = {
24 | connect: connect,
25 | write: write,
26 | doWrite: doWrite
27 | }
28 |
29 | var electron = require('electron')
30 | var imageWrite = require('etcher-image-write')
31 | var fs = require('fs-extra')
32 | var childProcess = require('child_process')
33 | var ipc = require("crocket")
34 | var client = new ipc()
35 |
36 |
37 | function connect (socketPath, cb) {
38 | client.connect({
39 | path: socketPath
40 | }, function (error) {
41 | if (error) {
42 | throw error
43 | }
44 |
45 | // connected successfully
46 | cb()
47 | })
48 | }
49 |
50 |
51 | function write (imagePath, drive, cb) {
52 | doWrite(imagePath, drive, {
53 | validate: false
54 | }, {
55 | progress: writeImageProgress,
56 | error: function (error) {
57 | writeImageError(error)
58 | client.close()
59 | cb()
60 | },
61 | done: function (state, drive) {
62 | writeImageDone(state, drive)
63 | client.close()
64 | cb()
65 | }
66 | })
67 | }
68 |
69 |
70 | function writeImageProgress (state) {
71 | // send message to the client
72 | client.emit('write-progress', JSON.stringify(state))
73 | }
74 |
75 | function writeImageError (error) {
76 | client.emit('write-error', JSON.stringify(error))
77 | }
78 |
79 | function writeImageDone (state, drive) {
80 | client.emit('write-done', JSON.stringify(state))
81 | }
82 |
83 |
84 | function doWrite (imagePath, drive, options, cb) {
85 | cb.progress({text:'Unmounting SD card...'})
86 | unmount(drive, function (error) {
87 | if (error) {
88 | error.guimsg = 'Error writing to SD card'
89 | cb.error(error)
90 | return
91 | }
92 | beginWrite(imagePath, drive, options, cb.progress, cb.error, cb.done)
93 | })
94 | }
95 |
96 | function beginWrite (imagePath, drive, options, onProgress, onError, onDone) {
97 | onProgress({text:'Preparing for write...'})
98 | fs.open(drive.raw, 'rs+', function (error, driveFileDescriptor) {
99 | if (error) {
100 | error.guimsg = 'Error opening .img for reading'
101 | onError(error)
102 | return
103 | }
104 |
105 | fs.stat(imagePath, function (error, stats) {
106 | if (error) {
107 | error.guimsg = 'Error reading stats of .img file'
108 | onError(error)
109 | return
110 | }
111 |
112 | // check the sd card is large enough for the chosen image
113 | if (drive.size < stats.size) {
114 |
115 | var msg = 'The chosen device is not large enough for the chosen image. '
116 | msg = msg + 'Device: ' + drive.device
117 | msg = msg + '. Image: ' + imagePath
118 |
119 | var error = new Error('drive too small')
120 | error.name = 'SD_TOO_SMALL'
121 | error.guimsg = msg
122 | onError(error)
123 | return
124 | }
125 |
126 | var writer = imageWrite.write({
127 | fd: driveFileDescriptor,
128 | device: drive.raw,
129 | size: drive.size
130 | }, {
131 | stream: fs.createReadStream(imagePath),
132 | size: stats.size
133 | }, {
134 | check: options.check
135 | })
136 |
137 | onProgress({text:'Writing IMG to SD card...'})
138 |
139 | writer.on('progress', onProgress)
140 | writer.on('error', function (error) {
141 | error.guimsg = 'Error during SD write'
142 | onError(error)
143 | })
144 |
145 | writer.on('done', function (state) {
146 | fs.close(driveFileDescriptor, function (error) {
147 | if (error) {
148 | error.guimsg = 'Error closing drive file descriptor'
149 | onError(error)
150 | return
151 | }
152 | onDone(state, drive)
153 | })
154 | })
155 | })
156 | })
157 | }
158 |
159 | function unmount (drive, cb) {
160 | var unmountCommand = ''
161 | if (process.platform === 'darwin') {
162 | unmountCommand = '/usr/sbin/diskutil unmountDisk force ' + drive.device
163 | } else if (process.platform === 'linux') {
164 | unmountCommand = 'umount ' + drive.device + '?* 2>/dev/null || /bin/true'
165 | } else {
166 | cb(false)
167 | return
168 | }
169 |
170 | childProcess.exec(unmountCommand, function (error, stdout, stderr) {
171 | if (error) {
172 | error.guimsg = 'Error unmounting drive'
173 | cb(error)
174 | return
175 | }
176 | cb(false)
177 | })
178 | }
179 |
--------------------------------------------------------------------------------
/lib/settings.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | get: get,
23 | set: set
24 | }
25 |
26 | var app = require('electron').app || require('electron').remote.app
27 | var path = require('path')
28 | var fs = require('fs-extra')
29 |
30 |
31 | var settings = {}
32 |
33 |
34 | // get the proper settings file
35 | var settingsFile = path.join(app.getPath('appData'), 'PiBakery/settings.json')
36 | var hardSettingsFile = path.join(__dirname, '../settings.json')
37 | var exists = fs.existsSync(settingsFile)
38 |
39 | // if settings file doesn't exist, try and create it
40 | if (!exists) {
41 | try {
42 | fs.copySync(hardSettingsFile, settingsFile)
43 | } catch (e) {
44 | // if error on copying, revert back to hard settings file
45 | settingsFile = hardSettingsFile
46 | }
47 | }
48 |
49 |
50 | function load (cb) {
51 | fs.readJson(settingsFile, function (error, data) {
52 | if (error) {
53 | cb(error)
54 | return
55 | }
56 |
57 | settings = data
58 | cb(null)
59 | })
60 | }
61 |
62 |
63 | function save (cb) {
64 | // don't modify the in-app settings file
65 | if (settingsFile === hardSettingsFile) {
66 | return
67 | }
68 |
69 | try {
70 | var data = JSON.stringify(settings, null, 2) // save with pretty printing
71 | } catch (e) {
72 | cb(e)
73 | return
74 | }
75 |
76 | fs.writeFile(settingsFile, data, function (error) {
77 | if (error) {
78 | cb(error)
79 | return
80 | }
81 |
82 | cb(null)
83 | })
84 | }
85 |
86 |
87 | function get (setting, cb) {
88 | load(function (error) {
89 | if (error) {
90 | cb(error)
91 | return
92 | }
93 |
94 | if (! settings.hasOwnProperty(setting)) {
95 | cb(null, undefined)
96 | return
97 | }
98 |
99 | cb(null, settings[setting])
100 | })
101 | }
102 |
103 |
104 | function set (setting, value, cb) {
105 | load(function (error) {
106 | if (error) {
107 | cb(error)
108 | return
109 | }
110 |
111 | settings[setting] = value
112 |
113 | save(cb)
114 | })
115 | }
116 |
--------------------------------------------------------------------------------
/lib/ui.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PiBakery 2.0.0 - The easy to use setup tool for Raspberry Pi
3 | * Copyright (C) 2018 David Ferguson
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | 'use strict'
20 |
21 | module.exports = {
22 | imageChosenCallback: null,
23 |
24 | sdChooserOpen: false,
25 | updateChooserOpen: false,
26 | loadChooserOpen: false,
27 |
28 | showSdChooser: showSdChooser,
29 | updateSdChooser: updateSdChooser,
30 | showSdWriter: showSdWriter,
31 | updateProgress: updateProgress,
32 | showSdError: showSdError,
33 | showSdComplete: showSdComplete,
34 |
35 | showDriveChooser: showDriveChooser,
36 | updateUpdateChooser: updateUpdateChooser,
37 |
38 | closeDialog: dialogCloseClicked,
39 |
40 | showBlockInfo: showBlockInfo,
41 | removeIntro: removeIntro,
42 | showWorkspace: showWorkspace,
43 | elements: {}
44 | }
45 |
46 | module.exports.elements = {
47 | editor: document.getElementById('blockly_editor'),
48 | toolbox: document.getElementById('toolbox'),
49 | writeBtn: document.getElementById('flashSD'),
50 | updateBtn: document.getElementById('updateSD'),
51 | loadBtn: document.getElementById('loadSD'),
52 | importBtn: document.getElementById('import'),
53 | exportBtn: document.getElementById('export'),
54 | settingsBtn: document.getElementById('settings')
55 | }
56 |
57 | var path = require('path')
58 | var dialog = require('electron').remote.dialog
59 |
60 |
61 | function showChooserDialog (title, btnText, sd, os, modifications, dialogWriteClicked) {
62 | var hiderElement = document.createElement('div')
63 | hiderElement.setAttribute('id', 'hider')
64 |
65 | var dialogElement = document.createElement('div')
66 | dialogElement.setAttribute('id', 'dialog')
67 |
68 | var titleElement = document.createElement('p')
69 | titleElement.setAttribute('id', 'dialogTitle')
70 | titleElement.innerText = title
71 | dialogElement.appendChild(titleElement)
72 |
73 | var tableElement = document.createElement('table')
74 | tableElement.setAttribute('id', 'dialogTable')
75 | dialogElement.appendChild(tableElement)
76 |
77 | if (sd) {
78 | var sdRowElement = document.createElement('tr')
79 | var sdTextColumnElement = document.createElement('td')
80 | var sdTextElement = document.createElement('span')
81 | sdTextElement.innerText = 'SD Card:'
82 | sdTextColumnElement.appendChild(sdTextElement)
83 | var sdChoiceColumnElement = document.createElement('td')
84 | var sdChoiceElement = document.createElement('select')
85 | sdChoiceElement.setAttribute('id', 'dialogSdChoice')
86 | sdChoiceElement.setAttribute('class', 'dialogChoice')
87 | var sdChoicePlaceholderElement = document.createElement('option')
88 | sdChoicePlaceholderElement.innerText = 'loading...'
89 | sdChoicePlaceholderElement.disabled = true
90 | sdChoicePlaceholderElement.selected = true
91 | sdChoiceElement.appendChild(sdChoicePlaceholderElement)
92 | sdChoiceColumnElement.appendChild(sdChoiceElement)
93 | sdRowElement.appendChild(sdTextColumnElement)
94 | sdRowElement.appendChild(sdChoiceColumnElement)
95 | tableElement.appendChild(sdRowElement)
96 | }
97 |
98 | if (os) {
99 | var osRowElement = document.createElement('tr')
100 | var osTextColumnElement = document.createElement('td')
101 | var osTextElement = document.createElement('span')
102 | osTextElement.innerText = 'Operating System:'
103 | osTextColumnElement.appendChild(osTextElement)
104 | var osChoiceColumnElement = document.createElement('td')
105 | var osChoiceElement = document.createElement('select')
106 | osChoiceElement.setAttribute('id', 'dialogOsChoice')
107 | osChoiceElement.setAttribute('class', 'dialogChoice')
108 | var osChoicePlaceholderElement = document.createElement('option')
109 | osChoicePlaceholderElement.innerText = 'loading...'
110 | osChoicePlaceholderElement.disabled = true
111 | osChoicePlaceholderElement.selected = true
112 | osChoiceElement.appendChild(osChoicePlaceholderElement)
113 | osChoiceColumnElement.appendChild(osChoiceElement)
114 | osRowElement.appendChild(osTextColumnElement)
115 | osRowElement.appendChild(osChoiceColumnElement)
116 | tableElement.appendChild(osRowElement)
117 | }
118 |
119 | var writeButtonElement = document.createElement('button')
120 | writeButtonElement.setAttribute('id', 'dialogWriteButton')
121 | writeButtonElement.setAttribute('class', 'dialogButton')
122 | writeButtonElement.disabled = true
123 | writeButtonElement.innerText = btnText
124 | writeButtonElement.addEventListener('click', dialogWriteClicked)
125 | dialogElement.appendChild(writeButtonElement)
126 |
127 | var closeButtonElement = document.createElement('button')
128 | closeButtonElement.setAttribute('id', 'dialogCloseButton')
129 | closeButtonElement.setAttribute('class', 'dialogButton')
130 | closeButtonElement.innerText = 'Close'
131 | closeButtonElement.addEventListener('click', dialogCloseClicked)
132 | dialogElement.appendChild(closeButtonElement)
133 |
134 | if (typeof modifications === 'function') {
135 | modifications(dialogElement)
136 | }
137 |
138 | document.body.appendChild(hiderElement)
139 | document.body.appendChild(dialogElement)
140 | }
141 |
142 |
143 | function showSdChooser (writeCallback) {
144 | module.exports.sdChooserOpen = true
145 |
146 | showChooserDialog('Write New SD Card', 'Write', true, true, function (dialog) {
147 | // any modifications go here
148 | dialog.style.height = '165px'
149 | dialog.style.marginTop = '-70px'
150 | }, function () {
151 | // write button pushed callback
152 | module.exports.sdChooserOpen = false
153 |
154 | var sdChoiceElement = document.getElementById('dialogSdChoice')
155 | var chosenDriveElement = sdChoiceElement.options[sdChoiceElement.selectedIndex]
156 | var chosenDrive = JSON.parse(chosenDriveElement.value)
157 |
158 | var osChoiceElement = document.getElementById('dialogOsChoice')
159 | var chosenOperatingSystemElement = osChoiceElement.options[osChoiceElement.selectedIndex]
160 | var chosenOs = {
161 | name: chosenOperatingSystemElement.innerHTML,
162 | path: chosenOperatingSystemElement.value
163 | }
164 |
165 | writeCallback(chosenDrive, chosenOs)
166 | })
167 | }
168 |
169 |
170 | function showDriveChooser (updateCallback, title, btn) {
171 | module.exports.updateChooserOpen = true
172 |
173 | showChooserDialog(title, btn, true, false, function (dialog) {
174 | // any modifications go here
175 | dialog.style.height = '150px'
176 | dialog.style.marginTop = '-65px'
177 | }, function () {
178 | // write button pushed callback
179 | module.exports.updateChooserOpen = false
180 |
181 | var sdChoiceElement = document.getElementById('dialogSdChoice')
182 | var chosenDriveElement = sdChoiceElement.options[sdChoiceElement.selectedIndex]
183 | var chosenDrive = JSON.parse(chosenDriveElement.value)
184 |
185 | updateCallback(chosenDrive)
186 | })
187 | }
188 |
189 |
190 | function updateUpdateChooser (drives) {
191 | if (drives) {
192 | var sdChoiceElement = document.getElementById('dialogSdChoice')
193 |
194 | // build up array of new drives
195 | var newDrives = []
196 | for (var i = 0; i < drives.length; i++) {
197 | var currentDrive = drives[i]
198 | var driveOptionElement = document.createElement('option')
199 | driveOptionElement.setAttribute('value', JSON.stringify(currentDrive))
200 | driveOptionElement.innerText = currentDrive.name
201 | newDrives.push(driveOptionElement)
202 | }
203 |
204 | updateSelectElement(sdChoiceElement, newDrives)
205 | }
206 |
207 | enableWriteButton(true, false)
208 | }
209 |
210 |
211 | function dialogCloseClicked () {
212 | if (module.exports.sdChooserOpen) {
213 | module.exports.sdChooserOpen = false
214 | } else if (module.exports.updateChooserOpen) {
215 | module.exports.updateChooserOpen = false
216 | }
217 |
218 | var hiderElement = document.getElementById('hider')
219 | var dialogElement = document.getElementById('dialog')
220 |
221 | hiderElement.parentNode.removeChild(hiderElement)
222 | dialogElement.parentNode.removeChild(dialogElement)
223 | }
224 |
225 |
226 | function optionsIndexOf (array, option) {
227 | // a form of indexOf for an array of