Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ideascube
ARCHIVED Kiwix Hotspot
Commits
cd5b73a4
Commit
cd5b73a4
authored
Nov 11, 2018
by
rgaudin
Browse files
Fixed
#263
,
#264
: added SD-card wipe dialog and partition table check on start
parent
9686ada9
Changes
5
Hide whitespace changes
Inline
Side-by-side
CHANGELOG
View file @
cd5b73a4
2.0-rc16
* fixed etcher-cli call on macOS GUI (space escaping)
* Added partition number check on startup (must be one)
* Added SD-card wipe dialog
2.0-rc15
* Fixed concurrent access error on Windows
...
...
kiwix-hotspot/backend/util.py
View file @
cd5b73a4
...
...
@@ -2,6 +2,7 @@
# vim: ai ts=4 sts=4 et sw=4 nu
import
os
import
re
import
sys
import
time
import
shlex
...
...
@@ -171,8 +172,9 @@ class EtcherWriterThread(threading.Thread):
logger
.
step
(
"Copy image to sd card using etcher-cli"
)
from_cli
=
logger
is
None
or
type
(
logger
)
==
CLILogger
cmd
,
log_to_file
,
log_file
=
get_etcher_command
(
image_fpath
,
device_fpath
,
logger
image_fpath
,
device_fpath
,
logger
,
from_cli
)
process
=
subprocess
.
Popen
(
...
...
@@ -309,8 +311,7 @@ def restore_sleep_policy(reference, logger):
return
def
get_etcher_command
(
image_fpath
,
device_fpath
,
logger
):
from_cli
=
logger
is
None
or
type
(
logger
)
==
CLILogger
def
get_etcher_command
(
image_fpath
,
device_fpath
,
logger
,
from_cli
):
# on macOS, GUI sudo captures stdout so we use a log file
log_to_file
=
not
from_cli
and
sys
.
platform
==
"darwin"
...
...
@@ -342,12 +343,72 @@ def get_etcher_command(image_fpath, device_fpath, logger):
return
cmd
,
log_to_file
,
log_file
def
flash_image_with_etcher
(
image_fpath
,
device_fpath
,
logger
):
cmd
,
log_to_file
,
log_file
=
get_etcher_command
(
image_fpath
,
device_fpath
,
logger
)
subprocess_pretty_check_call
(
cmd
,
logger
)
def
flash_image_with_etcher
(
image_fpath
,
device_fpath
,
retcode
):
""" flash an image onto SD-card
use only with small image as there is no output capture on OSX
and it is not really cancellable.
retcode is a multiprocessing.Value """
logger
=
CLILogger
()
cmd
,
log_to_file
,
log_file
=
get_etcher_command
(
image_fpath
,
device_fpath
,
logger
,
False
)
returncode
,
_
=
subprocess_pretty_call
(
cmd
,
check
=
False
,
logger
=
logger
)
retcode
.
value
=
returncode
if
log_to_file
:
log_file
.
close
()
try
:
subprocess_pretty_call
([
"/bin/cat"
,
log_file
.
name
],
logger
,
decode
=
True
)
log_file
.
close
()
os
.
unlink
(
log_file
.
name
)
except
Exception
as
exp
:
logger
.
err
(
str
(
exp
))
return
returncode
==
0
def
sd_has_single_partition
(
sd_card
,
logger
):
""" whether sd_card consists of a single partition (expected to be clean) """
try
:
if
sys
.
platform
==
"darwin"
:
disk_prefix
=
re
.
sub
(
r
"\/dev\/disk([0-9]+)"
,
r
"disk\1s"
,
sd_card
)
lines
=
subprocess_pretty_call
(
[
"diskutil"
,
"list"
,
sd_card
],
logger
,
check
=
True
,
decode
=
True
)
nb_partitions
=
len
(
[
line
.
strip
().
rsplit
(
" "
,
1
)[
-
1
].
replace
(
disk_prefix
,
""
).
strip
()
for
line
in
lines
if
disk_prefix
in
line
]
)
return
nb_partitions
==
1
elif
sys
.
platform
==
"win32"
:
disk_prefix
=
re
.
sub
(
r
".+PHYSICALDRIVE([0-9+])"
,
r
"Disk #\1, Partition #"
,
sd_card
)
lines
=
subprocess_pretty_call
(
[
"wmic"
,
"partition"
],
logger
,
check
=
True
,
decode
=
True
)
nb_partitions
=
len
(
[
re
.
sub
(
r
".+"
+
disk_prefix
+
r
"([0-9]+).+"
,
r
"\1"
,
line
)
for
line
in
lines
if
disk_prefix
in
line
]
)
return
nb_partitions
==
1
elif
sys
.
platform
==
"linux"
:
disk_prefix
=
re
.
sub
(
r
"\/dev\/([a-z0-9]+)"
,
r
"─\1"
,
sd_card
)
lines
=
subprocess_pretty_call
(
[
"/bin/lsblk"
,
sd_card
],
logger
,
check
=
True
,
decode
=
True
)
nb_partitions
=
len
(
[
line
.
strip
().
split
(
" "
,
1
)[
0
].
replace
(
disk_prefix
,
""
).
strip
()
for
line
in
lines
if
disk_prefix
in
line
]
)
return
nb_partitions
==
1
except
Exception
as
exp
:
logger
.
err
(
str
(
exp
))
return
False
kiwix-hotspot/gui.py
View file @
cd5b73a4
...
...
@@ -40,6 +40,7 @@ from util import split_proxy, save_prefs
from
backend.catalog
import
get_catalogs
from
util
import
CancelEvent
,
ProgressHelper
from
run_installation
import
run_installation
from
backend.util
import
sd_has_single_partition
,
flash_image_with_etcher
from
backend.mount
import
open_explorer_for_imdisk
from
util
import
human_readable_size
,
ONE_GB
,
ONE_MiB
from
backend.cache
import
clean_cache
,
reset_cache
...
...
@@ -309,6 +310,9 @@ class Application:
# cache
self
.
component
.
clean_cache_button
.
connect
(
"clicked"
,
self
.
activate_menu_cache
)
# sd clean
self
.
component
.
clean_sd_button
.
connect
(
"clicked"
,
self
.
activate_sd_clean
)
# wifi password
self
.
component
.
wifi_password_switch
.
connect
(
"notify::active"
,
...
...
@@ -351,6 +355,9 @@ class Application:
self
.
component
.
sd_card_combobox
.
connect
(
"changed"
,
lambda
_
:
self
.
update_free_space
()
)
self
.
component
.
sd_card_combobox
.
connect
(
"changed"
,
lambda
w
:
self
.
activate_sd_clean_button
(
w
)
)
self
.
component
.
sd_card_refresh_button
.
connect
(
"clicked"
,
self
.
sd_card_refresh_button_clicked
)
...
...
@@ -1105,6 +1112,138 @@ class Application:
else
:
dialog
.
close
()
def
activate_sd_clean
(
self
,
widget
):
sd_card
=
self
.
get_sd_card
()
class
SDCleanDialog
(
Gtk
.
Dialog
):
def
__init__
(
self
,
parent
,
parent_ui
):
Gtk
.
Dialog
.
__init__
(
self
,
"Wipe your SD-card clean before for installation"
,
parent
,
0
,
(
"Wipe {}"
.
format
(
sd_card
),
Gtk
.
ResponseType
.
OK
,
"Close"
,
Gtk
.
ResponseType
.
CANCEL
,
),
)
self
.
parent
=
parent
self
.
parent_ui
=
parent_ui
self
.
set_default_size
(
300
,
100
)
label
=
Gtk
.
Label
()
label
.
set_markup
(
"
\n
For Kiwix Hotspot to work properly,
\n
"
"you need your SD-card to be cleaned before starting,
\n
"
"hence having just a single FAT-like partition.
\n\n
"
"This process you only take a few minutes.
\n
"
"If this does not end within 10mn,
\n
"
"cancel-it and try clean your SD-card using a different tool.
\n\n
"
)
label
.
set_alignment
(
0
,
0.5
)
self
.
thread
=
None
self
.
retcode
=
multiprocessing
.
Value
(
"i"
,
-
1
)
self
.
run_progressbar
=
Gtk
.
ProgressBar
()
self
.
cancel_button
=
Gtk
.
Button
(
"Cancel"
)
self
.
cancel_button
.
connect
(
"clicked"
,
self
.
stop_clean_operation
)
box
=
self
.
get_content_area
()
box
.
add
(
label
)
box
.
add
(
self
.
run_progressbar
)
box
.
add
(
self
.
cancel_button
)
box
.
add
(
Gtk
.
Label
(
""
))
# spacer
self
.
show_all
()
self
.
run_progressbar
.
set_visible
(
False
)
self
.
cancel_button
.
set_visible
(
False
)
def
start_clean_operation
(
self
):
# do nothing if the thread is running
if
self
.
thread
is
not
None
and
self
.
thread
.
is_alive
():
return
# show progress bar and cancel button
self
.
run_progressbar
.
set_visible
(
True
)
self
.
cancel_button
.
set_label
(
"Cancel Wiping"
)
self
.
cancel_button
.
set_visible
(
True
)
# start progress bar animation
self
.
timeout_id
=
GObject
.
timeout_add
(
50
,
self
.
on_timeout
)
self
.
thread
=
multiprocessing
.
Process
(
target
=
flash_image_with_etcher
,
args
=
(
os
.
path
.
join
(
data
.
data_dir
,
"mbr.img"
),
sd_card
,
self
.
retcode
,
),
)
self
.
thread
.
start
()
def
on_timeout
(
self
):
# display post-thread dialog on cancelled thread
if
self
.
thread
is
not
None
and
not
self
.
thread
.
is_alive
():
self
.
display_post_clean_operation_dialog
()
return
False
elif
self
.
thread
is
None
:
# stop progress anim if thread not running
return
False
new_value
=
self
.
run_progressbar
.
get_fraction
()
+
0.035
# inverse direction if end reached
if
new_value
>
1
:
new_value
=
0
# switch from left-to-right to right-to-left at bounds
self
.
run_progressbar
.
set_inverted
(
not
self
.
run_progressbar
.
get_inverted
()
)
self
.
run_progressbar
.
set_fraction
(
new_value
)
return
True
# returns True so it continues to get called
def
stop_clean_operation
(
self
,
*
args
,
**
kwargs
):
if
self
.
thread
is
not
None
and
self
.
thread
.
is_alive
():
self
.
thread
.
terminate
()
self
.
thread
=
None
self
.
cancel_button
.
set_visible
(
False
)
self
.
run_progressbar
.
set_visible
(
False
)
self
.
close
()
def
display_post_clean_operation_dialog
(
self
):
if
self
.
retcode
.
value
==
0
:
title
=
"SD-card Cleaning Completed"
content
=
(
"Your SD-card ({}) has been wiped.
\n\n
"
"You now need to unplug then replug your device.
\n
"
"Once done, come back and hit the refresh button."
).
format
(
sd_card
)
else
:
title
=
"SD-card Cleaning Failed"
content
=
(
"You SD-card HAS NOT been wiped.
\n\n
"
"Please use a different tool to clean it."
)
msg_box
=
Gtk
.
MessageDialog
(
self
.
parent
,
None
,
Gtk
.
MessageType
.
INFO
,
Gtk
.
ButtonsType
.
OK
,
title
)
msg_box
.
format_secondary_text
(
content
)
msg_box
.
set_modal
(
True
)
msg_box
.
run
()
msg_box
.
destroy
()
self
.
close
()
self
.
parent_ui
.
sd_card_refresh_button_clicked
(
""
)
dialog
=
SDCleanDialog
(
self
.
component
.
window
,
self
)
ret
=
dialog
.
run
()
if
ret
==
Gtk
.
ResponseType
.
OK
:
dialog
.
start_clean_operation
()
else
:
dialog
.
close
()
self
.
sd_card_refresh_button_clicked
(
""
)
def
installation_done
(
self
,
error
):
ok
=
error
is
None
validate_label
(
self
.
component
.
done_label
,
ok
)
...
...
@@ -1518,6 +1657,17 @@ class Application:
clipboard
=
Gtk
.
Clipboard
.
get
(
Gdk
.
SELECTION_CLIPBOARD
)
clipboard
.
set_text
(
text_buffer
.
get_text
(
start
,
end
,
hidden
),
-
1
)
def
get_sd_card
(
self
):
if
self
.
component
.
output_stack
.
get_visible_child_name
()
==
"sd_card"
:
sd_card_id
=
self
.
component
.
sd_card_combobox
.
get_active
()
if
sd_card_id
==
-
1
:
return
None
else
:
device_index
=
sd_card_info
.
get_device_index
()
return
self
.
component
.
sd_card_list_store
[
sd_card_id
][
device_index
]
return
None
def
run_installation_button_clicked
(
self
,
button
):
all_valid
=
True
...
...
@@ -1588,23 +1738,22 @@ class Application:
all_valid
=
all_valid
and
valid_admin_login
and
valid_admin_pwd
output_size
=
self
.
get_output_size
()
sd_card
=
self
.
get_sd_card
()
if
self
.
component
.
output_stack
.
get_visible_child_name
()
==
"sd_card"
:
sd_card_id
=
self
.
component
.
sd_card_combobox
.
get_active
()
condition
=
sd_card_id
!=
-
1
condition
=
sd_card
is
not
None
validate_label
(
self
.
component
.
sd_card_label
,
condition
)
all_valid
=
all_valid
and
condition
if
sd_card_id
==
-
1
:
sd_card
=
None
else
:
device_index
=
sd_card_info
.
get_device_index
()
sd_card
=
self
.
component
.
sd_card_list_store
[
sd_card_id
][
device_index
]
else
:
sd_card
=
None
condition
=
output_size
>
0
validate_label
(
self
.
component
.
size_label
,
condition
)
all_valid
=
all_valid
and
condition
# check that SD card has a single partition (clean state)
condition
=
sd_has_single_partition
(
sd_card
,
self
.
logger
)
validate_label
(
self
.
component
.
sd_card_label
,
condition
)
all_valid
=
all_valid
and
condition
condition
=
self
.
update_free_space
()
>=
0
validate_label
(
self
.
component
.
free_space_name_label
,
condition
)
all_valid
=
all_valid
and
condition
...
...
@@ -1700,6 +1849,10 @@ class Application:
self
.
component
.
run_window
.
show
()
threading
.
Thread
(
target
=
target
,
daemon
=
True
).
start
()
def
activate_sd_clean_button
(
self
,
button
):
has_card
=
self
.
component
.
sd_card_combobox
.
get_active
()
!=
-
1
self
.
component
.
clean_sd_button
.
set_visible
(
has_card
)
def
sd_card_refresh_button_clicked
(
self
,
button
):
self
.
refresh_disk_list
()
self
.
update_free_space
()
...
...
kiwix-hotspot/run_installation.py
View file @
cd5b73a4
...
...
@@ -36,8 +36,8 @@ from backend.mount import (
guess_next_loop_device
,
)
from
backend.util
import
EtcherWriterThread
from
backend.util
import
prevent_sleep
,
restore_sleep_policy
from
backend.mount
import
can_write_on
,
allow_write_on
,
restore_mode
from
backend.util
import
flash_image_with_etcher
,
prevent_sleep
,
restore_sleep_policy
from
backend.sysreq
import
host_matches_requirements
,
requirements_url
...
...
@@ -115,15 +115,6 @@ def run_installation(
else
:
previous_loop_mode
=
None
# Prepare SD Card
if
sd_card
:
logger
.
step
(
"Flash clean MBR onto SD-card ({})"
.
format
(
sd_card
))
flash_image_with_etcher
(
image_fpath
=
os
.
path
.
join
(
data
.
data_dir
,
"mbr.img"
),
device_fpath
=
sd_card
,
logger
=
logger
,
)
# Download Base image
logger
.
stage
(
"master"
)
logger
.
step
(
"Retrieving base image file"
)
...
...
ui.glade
View file @
cd5b73a4
...
...
@@ -999,7 +999,7 @@ In File mode the image is created at build path, you can choose the size
<object
class=
"GtkLabel"
id=
"sd_card_label"
>
<property
name=
"visible"
>
True
</property>
<property
name=
"can_focus"
>
False
</property>
<property
name=
"tooltip_text"
translatable=
"yes"
>
SD
card
</property>
<property
name=
"tooltip_text"
translatable=
"yes"
>
SD
-
card
must contain only a single FAT-like partition
</property>
<property
name=
"label"
translatable=
"yes"
>
SD card :
</property>
</object>
<packing>
...
...
@@ -1012,6 +1012,7 @@ In File mode the image is created at build path, you can choose the size
<object
class=
"GtkBox"
id=
"no_id_18"
>
<property
name=
"visible"
>
True
</property>
<property
name=
"can_focus"
>
False
</property>
<property
name=
"spacing"
>
5
</property>
<child>
<object
class=
"GtkButton"
id=
"sd_card_refresh_button"
>
<property
name=
"label"
translatable=
"yes"
>
Refresh
</property>
...
...
@@ -1036,6 +1037,19 @@ In File mode the image is created at build path, you can choose the size
<property
name=
"position"
>
1
</property>
</packing>
</child>
<child>
<object
class=
"GtkButton"
id=
"clean_sd_button"
>
<property
name=
"label"
translatable=
"yes"
>
Wipe
</property>
<property
name=
"tooltip_text"
translatable=
"yes"
>
Wipe SD-card clean
</property>
<property
name=
"visible"
>
False
</property>
<property
name=
"can_focus"
>
True
</property>
</object>
<packing>
<property
name=
"expand"
>
False
</property>
<property
name=
"fill"
>
True
</property>
<property
name=
"position"
>
2
</property>
</packing>
</child>
</object>
<packing>
<property
name=
"expand"
>
False
</property>
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment