Contents
- Editorial
-
May 2018
This month's edition might be a little more unprepared than usual because for the last eight days I have been confined to my bed with a damaged back. Today being the 27th April is the first day I have been able to sit in a chair without extreme pain so hopefully I will get a few words down. Anyway, enough about me.
Have you ever thought about what software you couldn't do without? After giving this some though I found it very difficult to identify a single piece of software as my favourite or I mostly used. I would like to hear from anyone who can provide a preference and explain why it is better than others. For example, I use the Seamonkey Suite as I like the browser, email client and the html editor which I use very often. I often read people say they prefer Palemoon, or Firefox over Seamonkey but they never explain why. I would like to hear your views on all different categories of software and I'm sure other readers would too.
This month there is a comprehensive article by Terry Schuster about a manual frugal installation of Puppy. I must admit I always do manual installations as I like to have full control over the procedure.
Bigpup has provided some information on how to gain access to your BIOS.
I have included a great python script written by Matt Martz to test internet speed.
smokey01
- Distro's
-
The Puppy Linux Discussion Forum is the main source for a lot of the information repeated here. Where appropriate, links will be provided so you can read first hand about the distribution development. This section will likely just provide highlights as a starting point.
- BionicDog
-
BionicDog by fredx181
There is a new dog in town, BionicDog to be exact.
BionicDog is a variant of "DebianDog" with the difference that it's Ubuntu based.
BionicDog WebPage
- OE aarch64
-
OE aarch64 by Barry Kauler
http://bkhome.org/news/201804/first-oe-aarch64-build-updates.html
- Software
-
This months covers: shellCMS by Barry Kauler
- shellCMS
-
ShellCMS written by Barry Kauler
Last year I watched with interest as Barry searched to find some simple but effective software for his blog. He tried and modified many but eventually wrote his own called shellCMS which was forked from bashlog.
If you have not seen his blog you can view it here: http://bkhome.org/
If you are looking to create a simple but effective blog, take a look at shellCMS.
- Tutorials
-
- Manual Frugal Installation
-
Written by Terry Schuster
Here’s How I do It –
Manual Frugal Puppy Installation
On a USB Using AntiX and GRUB2
One of the advantages of Linux and especially Puppy Linux is the number of options for installation. The plethora of options as exampled by http://puppylinux.org/wikka/InstallationIndex and bigpup in http://www.murga-linux.com/puppy/viewtopic.php?t=60302 are fairly overwhelming for a novice, and it is not easy to know where to begin. Thankfully, when I was ready to move past a Live CD and install on a HDD, there were relatively easy processes via the Ubuntu and Puppy Installers, and in the process my Windows OS stayed intact.
I found that running a single additional OS alongside my Windows had limitations in trying other OS, apart from a Live CD. This led me to install Linux OS (Ubuntu) on USB Flash drives, leading to a full install of Puppy Linux on a USB flash drive which I use for sound recording and restoration; the setup being documented in the Sept 2017 issue of the PL Newsletter. However, by limiting one OS to one USB I was still using/reusing a number of USB memory sticks.
Thankfully there are many other ways to install Puppy Linux so as to give greater flexibility. Unfortunately, multi-boot USB systems were not consistently successful on my hardware so I looked for other options. I managed to effect a MANUAL frugal installation after a fair bit of trial and error, mainly due to issues with the bootloader menu and difficulty booting from a USB on my computer. These issues have been resolved and I now use manual frugal installations extensively on both USB and HDD installations running Linux OS. This article particularly addresses the bootloader issues to encourage others to use a manual frugal installation.
Manual frugal installations give one flexibility and speed installing, deleting and using MULTIPLE Puppy Linux OSes and allowing easier backups. Installation to a USB Flash drive also allows for optional saving of session changes. Overall, one also has greater sense of control over, and understanding of, the whole process. Using a USB Drive may seem to limit space, but my practice is to save any changed/new files on my host computer’s (Windows) HDD and only save changes to the OS on the USB when necessary, so the USB is saved from excessive writing.
However, successful implementation of a manual frugal install is predicated upon a successful editing of the boot-loader menu. It is relatively easy process once mastered, and while the boot-loader menu configuration may be well known to experienced Puppy users, there are problems afoot for the novice. While there are many articles on the forums dealing with these matters, it is not always clear which GRUB ( GRUB4DOS, GRUB, or GRUB2) is being referred to, which leads to confusion. It is not always made clear in the Forums as to what the boot-loader commands refer to so one might know what a successful process looks like. Exposure and experience does improve this situation, however.
This article is focused upon a MANUAL Frugal Install which I trust will be accessible to novices who wish to move beyond the Universal Installer stage built in to each Puppy OS. It utilizes the file system of a full installation of a HOST Linux OS which is either on a HDD, USB-HDD or USB flash drive. For this implementation to succeed, the host Linux system must utilize GRUB2, such as Ubuntu and similar mainstream Linux distributions use. The host OS must be able to install and boot from a USB flash drive, and the GRUB2 bootloader menu must be displayed on the screen in the normal course of boot-up, so that the Puppy Linux options can be displayed and selected. The process would otherwise need to be modified with other GRUBs such as Grub4DOS. In addition, the use of GRUB2 on a USB stick means my Windows boot system does not need to be altered in any way, apart from the selection of a USB boot device on bootup, a process which is entirely reversible.
I have found the full Ubuntu installation onto a USB drive using the OTHER installation option was an initially successful way to load the HOST OS onto a USB although the process can take a long time and requires specific information to manage. A well documented description of how to install Ubuntu on a USB can be done can be found at http://ubuntuhandbook.org/index.php/2014/11/install-real-ubuntu-os-usb-drive/ .
However, as Ubuntu used up quite a bit of the USB space I switched to Lubuntu which gave a significant space improvement. As any small Linux OS running GRUB2 could improve things further, I looked for other options and found AntiX to be an excellent HOST solution, as it is a small OS, and loads, runs and shuts down very fast. In particular, it has a well documented, fast and effective installation program, which especially caters for USB installations. The AntiX installation is also claimed to be UEFI compliant. A well documented description of the Antix installation process can be found at: http://yatsite.blogspot.com.au/2009/07/install-antix-82-final-on-asus-eee-900.html.
The manual frugal installation technique I use is well documented on the forums, with some scripting variations. Example references include those by :
puppy linux wikka: http://puppylinux.org/wikka/Steps_to_Installation
mamasboy: http://www.murga-linux.com/puppy/viewtopic.php?t=63175
Global Moderator: https://www.bleepingcomputer.com/forums/t/579574/how-to-install-puppy-linux-frugal-and-configure-grub2-bootloader/
now3by: in https://forums.linuxmint.com/viewtopic.php?t=247111
HiDeHo: in http://murga-linux.com/puppy/viewtopic.php?t=64525
The technique referenced here covers pretty much all of the Puppy Linux OS I have attempted to frugally install and is applicable for GRUB2 only. It includes the following steps:
Install a full installation of a host Linux OS on a USB flash drive of at least 16Gb. While this does take up space on the USB (~2Gb), the OS provides the GRUB2 bootloader and removes the extra complication of installing GRUB2 by hand, which, as a newbie, I have not yet mastered. I recommend the use of AntiX as the Host OS. It is an interesting OS in its own right and an update has just been released. See https://antixlinux.com/antix-17-1-released/
As recommended, copy the 2 – 4 frugal Puppy OS files in a suitably named folder which is created in the host OS root directory (the first directory on the drive). It is not strictly necessary to create a folder, but it does keep items pertaining to the Puppy OS together. In the case below, the script psubdir=Xenial_Pup_64_75 points to the folder Xenial_Pup_64_75 containing the frugal files.
The Frugal Puppy files are gleaned for the ISO disk image and typically include vmlinuz, initrd and the puppy linux initial savefile (.sfs). See: HiDeHo in http://murga-linux.com/puppy/viewtopic.php?t=64525
Edit the 40_custom file in the /etc/grub/ folder of the host OS to include a suitable menu for bootloading. This is best done from a separate linux distribution, to avoid the issue of write permissions. However, AntiX has a neat option to easily edit some system configuration files – including the bootloader menu scripts.
Run the host OS and use sudo update-grub in the terminal to effect an update of the bootloader scripts stored in grub.cfg, which advisedly should not be edited by hand.
Shutdown and run the bootloader. The new Puppy OS should appear in the bootloader menu.
A typical GRUB2 bootloader menu listing for my situation is shown below - in this case for a 64 bit Xenial Puppy OS. The frugal files are contained in a folder named Xenial_Pup_64_75 in the root of the USB flashdrive, which is why the name appears three times in the script. The script is pretty consistent with what is listed in the forums, although it is probably one of the shortest, and is by no means inclusive nor definitive...but it consistently works in my situation.
menuentry "Xenial_Puppy_64bit_75" {
search --no-floppy --fs-uuid --set 5d64890b-01ad-4cc7-b079-c468b2f7fc0f
linux /Xenial_Pup_64_75/vmlinuz pmedia=atahd psubdir=Xenial_Pup_64_75 nousbwait=1
initrd /Xenial_Pup_64_75/initrd.gz
}
An excellent article on the construction of GRUB2 menus is found at: https://help.ubuntu.com/community/Grub2/CustomMenus
From my experience, the effectiveness of the process can be enhanced by the following:
1) The most common script used to find the menu listing is set root=(hdX,Y), where X is the drive number counting from 0, and Y is the partition number on that drive, counting from 1. In practice, I have found that there can be an issue with the set root= command, as the required partition may not be recognized at boot-up.
As evidenced above, there is an alternate means to access the required partition using the UUID value of that partition. (A universally unique identifier (UUID) is a 128-bit number that identifies Unique Internet objects or data). Using a UUID also makes the USB drive more portable from one computer to another.
Ensure the target USB device is mounted. Run pMount to determine the target device or partition, eg device sdd5. The UUID of all connected devices can be found by running the sudo blkid ( blkid in Puppy Linux) command in the terminal. An example output is as follows:
/dev/sdb1: UUID="77B4-D86A" TYPE="vfat" PARTUUID="00095d50-01"
/dev/sdb5: UUID="1e2767ed-f14a-48da-8a05-133a26bc1d28" TYPE="ext4" PARTUUID="00095d50-05"
/dev/sdb6: UUID="0ec659a1-87dc-4d6b-83c2-fafa1abb40f4" TYPE="swap" PARTUUID="00095d50-06"
/dev/sdd1: UUID="1E15-6B28" TYPE="vfat" PARTUUID="12a7d734-01"
/dev/sdd5: UUID="5438389c-1cb4-49b3-976f-aa137d7bd5b0" TYPE="ext4" PARTUUID="12a7d734-05"
terkath@terkath-HP-Compaq-dc7100-SFF-PK856PA:~$
In this case, the USB has been determined to be on sdd5, hence the UUID of the device is 5438389c-1cb4-49b3-976f-aa137d7bd5b0, (there is only one partition) which can be copied and pasted to the menu in the 40_custom file using Geaney. Otherwise the PARTUUID should be used.
2) With some USB flash drives the Frugal OS can be missed on booting because the software or hardware is too slow. To counter this, the wait time (in seconds) for the USB to settle can be increased from nousbwait=1 to nousbwait=5. This is great advice which was discovered on the FatDog help site.
3) To avoid loading the host OS and running update-grub each time the menu is altered, a better alternative is to ignore the 40_custom script and create a file called custom.cfg in the /boot/grub/ folder. This can be done by copying the grub.cfg file to the desktop, renaming it, opening it up with Geany, deleting all of its contents, and pasting the new file in the same folder as the grub.cfg file. Then add your grub menu items into the new file. This file will be read by grub when booting as well as the 40_custom and all collected menu items will be displayed in the boot menu. The custom.cfg can be opened and edited from another guest OS or an external Linux OS, or perhaps the host OS.
Having said that, there is significant benefit in running sudo update-grub, as it will find any typos or other errors in any script. For a new and untried distribution, it might be advisable to use the 40_custom method first, and then transfer the working script to the custom.cfg file, deleting the original and running sudo update-grub in the host OS to save changes.
4) Apart from space limitations there is no reasonable limit to the number of Puppy OSes one can install on a single USB flash drive. Each will have its own menuentry, set of frugal files in a “home” folder, and the generated savefolder(s) for each OS, which can also be situated in the “home” folder. However, if one of more Puppy Os is used to host a virtual operating system via Virtualbox etc, the space on the host USB will soon fill up, especially if the guest OS is large (eg Ubuntu, Win XP). Hence there may be reasons why frugal files might be better saved on a second USB drive, which does not contain a host OS and is suitably formatted, such as ext2 or ext4. The menuentry will then point to the UUID of the second device, allowing the original host USB to retain its free memory.
In conclusion, frugal installations of Puppy Linux are a great advantage and all credit to Barry K for making this so easy and available in respect of Puppy Linux. Readers who have not attempted any type of frugal install are encouraged to do so - especially manually, and on a USB Flash drive.
- GtkDialog Part 7
-
This month we will have a look at the edit tag.
Below is the code.
#!/bin/sh
[ -z $GTKDIALOG ] && GTKDIALOG=gtkdialog
MAIN_DIALOG='
<window>
<vbox>
<edit>
<variable>EDITOR</variable>
<height>150</height>
<width>350</width>
<default>
"This is the default text of the editor."
</default>
</edit>
<hbox>
<button cancel></button>
<button ok></button>
</hbox>
</vbox>
</window>
'
export MAIN_DIALOG
case $1 in
-d | --dump) echo "$MAIN_DIALOG" ;;
*) $GTKDIALOG --program=MAIN_DIALOG ;;
esac
- Compiling
-
This is a good section to discuss how to compile software. Compiling is not everyone's cup of tea but the more people that can manage it, the longer life Puppy will have.
The hardest part of compiling is the build recipe as there are so many options. Let's include some proven recipes here.
You must have the devx loaded as this is where most of your compiling tools reside. In some cases you may also need to have the kernel sources loaded.
- Beginners Linux compiling
-
Discovered by smokey01
The absolute beginners guide to compiling in Linux.
http://www.codecoffee.com/tipsforlinux/articles/18.html
Here are some more tips from the same website
- Scripts & Code
-
Basic Shell (Console) operation for beginners
http://www.codecoffee.com/tipsforlinux/articles/18.html
- Test Internet Speed
-
Article written by smokey01 and the script was written by Matt Martz.
Have you ever wondered what your real internet download/upload speed is?
There are many web applications that will provide this data but in my experience they all provide slightly different results.
Below is a great little python script written by Matt Martz that I found very useful which delivers consistent results.
This is where I found it: https://github.com/sivel/speedtest-cli/blob/master/speedtest.py
You must have the python language installed to run this script.
Copy the entire script below and paste it into a text editor then save it as speedtest.py.
Copy speedtest.py to one of your bins: eg: /usr/local/bin
From a terminal type: speedtest.py and you will be provided with some statistics.
Typing speedtest.py ? will provide some help.
Speedtest.py tends to use the closest server to your location. I generally make it use my ISP so I type:
# speedtest.py --server 234
Retrieving speedtest.net configuration...
Testing from iiNet Limited (124.171.188.151)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by Internode (Adelaide) [0.22 km]: 10.197 ms
Testing download speed................................................................................
Download: 68.80 Mbit/s
Testing upload speed................................................................................................
Upload: 34.05 Mbit/s
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2012-2016 Matt Martz
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import re
import csv
import sys
import math
import errno
import signal
import socket
import timeit
import datetime
import platform
import threading
import xml.parsers.expat
try:
import gzip
GZIP_BASE = gzip.GzipFile
except ImportError:
gzip = None
GZIP_BASE = object
__version__ = '1.0.6'
class FakeShutdownEvent(object):
"""Class to fake a threading.Event.isSet so that users of this module
are not required to register their own threading.Event()
"""
@staticmethod
def isSet():
"Dummy method to always return false"""
return False
# Some global variables we use
USER_AGENT = None
SOURCE = None
SHUTDOWN_EVENT = FakeShutdownEvent()
SCHEME = 'http'
DEBUG = False
# Used for bound_interface
SOCKET_SOCKET = socket.socket
# Begin import game to handle Python 2 and Python 3
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
json = None
try:
import xml.etree.cElementTree as ET
except ImportError:
try:
import xml.etree.ElementTree as ET
except ImportError:
from xml.dom import minidom as DOM
ET = None
try:
from urllib2 import urlopen, Request, HTTPError, URLError
except ImportError:
from urllib.request import urlopen, Request, HTTPError, URLError
try:
from httplib import HTTPConnection
except ImportError:
from http.client import HTTPConnection
try:
from httplib import HTTPSConnection
except ImportError:
try:
from http.client import HTTPSConnection
except ImportError:
HTTPSConnection = None
try:
from Queue import Queue
except ImportError:
from queue import Queue
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
try:
from urlparse import parse_qs
except ImportError:
try:
from urllib.parse import parse_qs
except ImportError:
from cgi import parse_qs
try:
from hashlib import md5
except ImportError:
from md5 import md5
try:
from argparse import ArgumentParser as ArgParser
from argparse import SUPPRESS as ARG_SUPPRESS
PARSER_TYPE_INT = int
PARSER_TYPE_STR = str
except ImportError:
from optparse import OptionParser as ArgParser
from optparse import SUPPRESS_HELP as ARG_SUPPRESS
PARSER_TYPE_INT = 'int'
PARSER_TYPE_STR = 'string'
try:
from cStringIO import StringIO
BytesIO = None
except ImportError:
try:
from StringIO import StringIO
BytesIO = None
except ImportError:
from io import StringIO, BytesIO
try:
import __builtin__
except ImportError:
import builtins
from io import TextIOWrapper, FileIO
class _Py3Utf8Stdout(TextIOWrapper):
"""UTF-8 encoded wrapper around stdout for py3, to override
ASCII stdout
"""
def __init__(self, **kwargs):
buf = FileIO(sys.stdout.fileno(), 'w')
super(_Py3Utf8Stdout, self).__init__(
buf,
encoding='utf8',
errors='strict'
)
def write(self, s):
super(_Py3Utf8Stdout, self).write(s)
self.flush()
_py3_print = getattr(builtins, 'print')
_py3_utf8_stdout = _Py3Utf8Stdout()
def to_utf8(v):
"""No-op encode to utf-8 for py3"""
return v
def print_(*args, **kwargs):
"""Wrapper function for py3 to print, with a utf-8 encoded stdout"""
kwargs['file'] = _py3_utf8_stdout
_py3_print(*args, **kwargs)
else:
del __builtin__
def to_utf8(v):
"""Encode value to utf-8 if possible for py2"""
try:
return v.encode('utf8', 'strict')
except AttributeError:
return v
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5.
Taken from https://pypi.python.org/pypi/six/
Modified to set encoding to UTF-8 always
"""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
encoding = 'utf8' # Always trust UTF-8 for output
if (isinstance(fp, file) and
isinstance(data, unicode) and
encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
# Exception "constants" to support Python 2 through Python 3
try:
import ssl
try:
CERT_ERROR = (ssl.CertificateError,)
except AttributeError:
CERT_ERROR = tuple()
HTTP_ERRORS = ((HTTPError, URLError, socket.error, ssl.SSLError) +
CERT_ERROR)
except ImportError:
HTTP_ERRORS = (HTTPError, URLError, socket.error)
class SpeedtestException(Exception):
"""Base exception for this module"""
class SpeedtestCLIError(SpeedtestException):
"""Generic exception for raising errors during CLI operation"""
class SpeedtestHTTPError(SpeedtestException):
"""Base HTTP exception for this module"""
class SpeedtestConfigError(SpeedtestException):
"""Configuration provided is invalid"""
class ConfigRetrievalError(SpeedtestHTTPError):
"""Could not retrieve config.php"""
class ServersRetrievalError(SpeedtestHTTPError):
"""Could not retrieve speedtest-servers.php"""
class InvalidServerIDType(SpeedtestException):
"""Server ID used for filtering was not an integer"""
class NoMatchedServers(SpeedtestException):
"""No servers matched when filtering"""
class SpeedtestMiniConnectFailure(SpeedtestException):
"""Could not connect to the provided speedtest mini server"""
class InvalidSpeedtestMiniServer(SpeedtestException):
"""Server provided as a speedtest mini server does not actually appear
to be a speedtest mini server
"""
class ShareResultsConnectFailure(SpeedtestException):
"""Could not connect to speedtest.net API to POST results"""
class ShareResultsSubmitFailure(SpeedtestException):
"""Unable to successfully POST results to speedtest.net API after
connection
"""
class SpeedtestUploadTimeout(SpeedtestException):
"""testlength configuration reached during upload
Used to ensure the upload halts when no additional data should be sent
"""
class SpeedtestBestServerFailure(SpeedtestException):
"""Unable to determine best server"""
class GzipDecodedResponse(GZIP_BASE):
"""A file-like object to decode a response encoded with the gzip
method, as described in RFC 1952.
Largely copied from ``xmlrpclib``/``xmlrpc.client`` and modified
to work for py2.4-py3
"""
def __init__(self, response):
# response doesn't support tell() and read(), required by
# GzipFile
if not gzip:
raise SpeedtestHTTPError('HTTP response body is gzip encoded, '
'but gzip support is not available')
IO = BytesIO or StringIO
self.io = IO()
while 1:
chunk = response.read(1024)
if len(chunk) == 0:
break
self.io.write(chunk)
self.io.seek(0)
gzip.GzipFile.__init__(self, mode='rb', fileobj=self.io)
def close(self):
try:
gzip.GzipFile.close(self)
finally:
self.io.close()
def get_exception():
"""Helper function to work with py2.4-py3 for getting the current
exception in a try/except block
"""
return sys.exc_info()[1]
def bound_socket(*args, **kwargs):
"""Bind socket to a specified source IP address"""
sock = SOCKET_SOCKET(*args, **kwargs)
sock.bind((SOURCE, 0))
return sock
def distance(origin, destination):
"""Determine distance between 2 sets of [lat,lon] in km"""
lat1, lon1 = origin
lat2, lon2 = destination
radius = 6371 # km
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
math.cos(math.radians(lat1)) *
math.cos(math.radians(lat2)) * math.sin(dlon / 2) *
math.sin(dlon / 2))
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
d = radius * c
return d
def build_user_agent():
"""Build a Mozilla/5.0 compatible User-Agent string"""
global USER_AGENT
if USER_AGENT:
return USER_AGENT
ua_tuple = (
'Mozilla/5.0',
'(%s; U; %s; en-us)' % (platform.system(), platform.architecture()[0]),
'Python/%s' % platform.python_version(),
'(KHTML, like Gecko)',
'speedtest-cli/%s' % __version__
)
USER_AGENT = ' '.join(ua_tuple)
printer(USER_AGENT, debug=True)
return USER_AGENT
def build_request(url, data=None, headers=None, bump=''):
"""Build a urllib2 request object
This function automatically adds a User-Agent header to all requests
"""
if not USER_AGENT:
build_user_agent()
if not headers:
headers = {}
if url[0] == ':':
schemed_url = '%s%s' % (SCHEME, url)
else:
schemed_url = url
if '?' in url:
delim = '&'
else:
delim = '?'
# WHO YOU GONNA CALL? CACHE BUSTERS!
final_url = '%s%sx=%s.%s' % (schemed_url, delim,
int(timeit.time.time() * 1000),
bump)
headers.update({
'User-Agent': USER_AGENT,
'Cache-Control': 'no-cache',
})
printer('%s %s' % (('GET', 'POST')[bool(data)], final_url),
debug=True)
return Request(final_url, data=data, headers=headers)
def catch_request(request):
"""Helper function to catch common exceptions encountered when
establishing a connection with a HTTP/HTTPS request
"""
try:
uh = urlopen(request)
return uh, False
except HTTP_ERRORS:
e = get_exception()
return None, e
def get_response_stream(response):
"""Helper function to return either a Gzip reader if
``Content-Encoding`` is ``gzip`` otherwise the response itself
"""
try:
getheader = response.headers.getheader
except AttributeError:
getheader = response.getheader
if getheader('content-encoding') == 'gzip':
return GzipDecodedResponse(response)
return response
def get_attributes_by_tag_name(dom, tag_name):
"""Retrieve an attribute from an XML document and return it in a
consistent format
Only used with xml.dom.minidom, which is likely only to be used
with python versions older than 2.5
"""
elem = dom.getElementsByTagName(tag_name)[0]
return dict(list(elem.attributes.items()))
def print_dots(current, total, start=False, end=False):
"""Built in callback function used by Thread classes for printing
status
"""
if SHUTDOWN_EVENT.isSet():
return
sys.stdout.write('.')
if current + 1 == total and end is True:
sys.stdout.write('\n')
sys.stdout.flush()
def do_nothing(*args, **kwargs):
pass
class HTTPDownloader(threading.Thread):
"""Thread class for retrieving a URL"""
def __init__(self, i, request, start, timeout):
threading.Thread.__init__(self)
self.request = request
self.result = [0]
self.starttime = start
self.timeout = timeout
self.i = i
def run(self):
try:
if (timeit.default_timer() - self.starttime) <= self.timeout:
f = urlopen(self.request)
while (not SHUTDOWN_EVENT.isSet() and
(timeit.default_timer() - self.starttime) <=
self.timeout):
self.result.append(len(f.read(10240)))
if self.result[-1] == 0:
break
f.close()
except IOError:
pass
class HTTPUploaderData(object):
"""File like object to improve cutting off the upload once the timeout
has been reached
"""
def __init__(self, length, start, timeout):
self.length = length
self.start = start
self.timeout = timeout
self._data = None
self.total = [0]
def pre_allocate(self):
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
multiplier = int(round(int(self.length) / 36.0))
IO = BytesIO or StringIO
self._data = IO(
('content1=%s' %
(chars * multiplier)[0:int(self.length) - 9]
).encode()
)
@property
def data(self):
if not self._data:
self.pre_allocate()
return self._data
def read(self, n=10240):
if ((timeit.default_timer() - self.start) <= self.timeout and
not SHUTDOWN_EVENT.isSet()):
chunk = self.data.read(n)
self.total.append(len(chunk))
return chunk
else:
raise SpeedtestUploadTimeout()
def __len__(self):
return self.length
class HTTPUploader(threading.Thread):
"""Thread class for putting a URL"""
def __init__(self, i, request, start, size, timeout):
threading.Thread.__init__(self)
self.request = request
self.request.data.start = self.starttime = start
self.size = size
self.result = None
self.timeout = timeout
self.i = i
def run(self):
request = self.request
try:
if ((timeit.default_timer() - self.starttime) <= self.timeout and
not SHUTDOWN_EVENT.isSet()):
try:
f = urlopen(request)
except TypeError:
# PY24 expects a string or buffer
# This also causes issues with Ctrl-C, but we will concede
# for the moment that Ctrl-C on PY24 isn't immediate
request = build_request(self.request.get_full_url(),
data=request.data.read(self.size))
f = urlopen(request)
f.read(11)
f.close()
self.result = sum(self.request.data.total)
else:
self.result = 0
except (IOError, SpeedtestUploadTimeout):
self.result = sum(self.request.data.total)
class SpeedtestResults(object):
"""Class for holding the results of a speedtest, including:
Download speed
Upload speed
Ping/Latency to test server
Data about server that the test was run against
Additionally this class can return a result data as a dictionary or CSV,
as well as submit a POST of the result data to the speedtest.net API
to get a share results image link.
"""
def __init__(self, download=0, upload=0, ping=0, server=None):
self.download = download
self.upload = upload
self.ping = ping
if server is None:
self.server = {}
else:
self.server = server
self._share = None
self.timestamp = '%sZ' % datetime.datetime.utcnow().isoformat()
self.bytes_received = 0
self.bytes_sent = 0
def __repr__(self):
return repr(self.dict())
def share(self):
"""POST data to the speedtest.net API to obtain a share results
link
"""
if self._share:
return self._share
download = int(round(self.download / 1000.0, 0))
ping = int(round(self.ping, 0))
upload = int(round(self.upload / 1000.0, 0))
# Build the request to send results back to speedtest.net
# We use a list instead of a dict because the API expects parameters
# in a certain order
api_data = [
'recommendedserverid=%s' % self.server['id'],
'ping=%s' % ping,
'screenresolution=',
'promo=',
'download=%s' % download,
'screendpi=',
'upload=%s' % upload,
'testmethod=http',
'hash=%s' % md5(('%s-%s-%s-%s' %
(ping, upload, download, '297aae72'))
.encode()).hexdigest(),
'touchscreen=none',
'startmode=pingselect',
'accuracy=1',
'bytesreceived=%s' % self.bytes_received,
'bytessent=%s' % self.bytes_sent,
'serverid=%s' % self.server['id'],
]
headers = {'Referer': 'http://c.speedtest.net/flash/speedtest.swf'}
request = build_request('://www.speedtest.net/api/api.php',
data='&'.join(api_data).encode(),
headers=headers)
f, e = catch_request(request)
if e:
raise ShareResultsConnectFailure(e)
response = f.read()
code = f.code
f.close()
if int(code) != 200:
raise ShareResultsSubmitFailure('Could not submit results to '
'speedtest.net')
qsargs = parse_qs(response.decode())
resultid = qsargs.get('resultid')
if not resultid or len(resultid) != 1:
raise ShareResultsSubmitFailure('Could not submit results to '
'speedtest.net')
self._share = 'http://www.speedtest.net/result/%s.png' % resultid[0]
return self._share
def dict(self):
"""Return dictionary of result data"""
return {
'download': self.download,
'upload': self.upload,
'ping': self.ping,
'server': self.server,
'timestamp': self.timestamp,
'bytes_sent': self.bytes_sent,
'bytes_received': self.bytes_received,
'share': self._share,
}
def csv(self, delimiter=','):
"""Return data in CSV format"""
data = self.dict()
out = StringIO()
writer = csv.writer(out, delimiter=delimiter, lineterminator='')
row = [data['server']['id'], data['server']['sponsor'],
data['server']['name'], data['timestamp'],
data['server']['d'], data['ping'], data['download'],
data['upload']]
writer.writerow([to_utf8(v) for v in row])
return out.getvalue()
def json(self, pretty=False):
"""Return data in JSON format"""
kwargs = {}
if pretty:
kwargs.update({
'indent': 4,
'sort_keys': True
})
return json.dumps(self.dict(), **kwargs)
class Speedtest(object):
"""Class for performing standard speedtest.net testing operations"""
def __init__(self, config=None):
self.config = {}
self.get_config()
if config is not None:
self.config.update(config)
self.servers = {}
self.closest = []
self.best = {}
self.results = SpeedtestResults()
def get_config(self):
"""Download the speedtest.net configuration and return only the data
we are interested in
"""
headers = {}
if gzip:
headers['Accept-Encoding'] = 'gzip'
request = build_request('://www.speedtest.net/speedtest-config.php',
headers=headers)
uh, e = catch_request(request)
if e:
raise ConfigRetrievalError(e)
configxml = []
stream = get_response_stream(uh)
while 1:
configxml.append(stream.read(1024))
if len(configxml[-1]) == 0:
break
stream.close()
uh.close()
if int(uh.code) != 200:
return None
printer(''.encode().join(configxml), debug=True)
try:
root = ET.fromstring(''.encode().join(configxml))
server_config = root.find('server-config').attrib
download = root.find('download').attrib
upload = root.find('upload').attrib
# times = root.find('times').attrib
client = root.find('client').attrib
except AttributeError:
root = DOM.parseString(''.join(configxml))
server_config = get_attributes_by_tag_name(root, 'server-config')
download = get_attributes_by_tag_name(root, 'download')
upload = get_attributes_by_tag_name(root, 'upload')
# times = get_attributes_by_tag_name(root, 'times')
client = get_attributes_by_tag_name(root, 'client')
ignore_servers = list(
map(int, server_config['ignoreids'].split(','))
)
ratio = int(upload['ratio'])
upload_max = int(upload['maxchunkcount'])
up_sizes = [32768, 65536, 131072, 262144, 524288, 1048576, 7340032]
sizes = {
'upload': up_sizes[ratio - 1:],
'download': [350, 500, 750, 1000, 1500, 2000, 2500,
3000, 3500, 4000]
}
size_count = len(sizes['upload'])
upload_count = int(math.ceil(upload_max / size_count))
counts = {
'upload': upload_count,
'download': int(download['threadsperurl'])
}
threads = {
'upload': int(upload['threads']),
'download': int(server_config['threadcount']) * 2
}
length = {
'upload': int(upload['testlength']),
'download': int(download['testlength'])
}
self.config.update({
'client': client,
'ignore_servers': ignore_servers,
'sizes': sizes,
'counts': counts,
'threads': threads,
'length': length,
'upload_max': upload_count * size_count
})
self.lat_lon = (float(client['lat']), float(client['lon']))
printer(self.config, debug=True)
return self.config
def get_servers(self, servers=None):
"""Retrieve a the list of speedtest.net servers, optionally filtered
to servers matching those specified in the ``servers`` argument
"""
if servers is None:
servers = []
self.servers.clear()
for i, s in enumerate(servers):
try:
servers[i] = int(s)
except ValueError:
raise InvalidServerIDType('%s is an invalid server type, must '
'be int' % s)
urls = [
'://www.speedtest.net/speedtest-servers-static.php',
'http://c.speedtest.net/speedtest-servers-static.php',
'://www.speedtest.net/speedtest-servers.php',
'http://c.speedtest.net/speedtest-servers.php',
]
headers = {}
if gzip:
headers['Accept-Encoding'] = 'gzip'
errors = []
for url in urls:
try:
request = build_request('%s?threads=%s' %
(url,
self.config['threads']['download']),
headers=headers)
uh, e = catch_request(request)
if e:
errors.append('%s' % e)
raise ServersRetrievalError()
stream = get_response_stream(uh)
serversxml = []
while 1:
serversxml.append(stream.read(1024))
if len(serversxml[-1]) == 0:
break
stream.close()
uh.close()
if int(uh.code) != 200:
raise ServersRetrievalError()
printer(''.encode().join(serversxml), debug=True)
try:
try:
root = ET.fromstring(''.encode().join(serversxml))
elements = root.getiterator('server')
except AttributeError:
root = DOM.parseString(''.join(serversxml))
elements = root.getElementsByTagName('server')
except (SyntaxError, xml.parsers.expat.ExpatError):
raise ServersRetrievalError()
for server in elements:
try:
attrib = server.attrib
except AttributeError:
attrib = dict(list(server.attributes.items()))
if servers and int(attrib.get('id')) not in servers:
continue
if int(attrib.get('id')) in self.config['ignore_servers']:
continue
try:
d = distance(self.lat_lon,
(float(attrib.get('lat')),
float(attrib.get('lon'))))
except:
continue
attrib['d'] = d
try:
self.servers[d].append(attrib)
except KeyError:
self.servers[d] = [attrib]
printer(''.encode().join(serversxml), debug=True)
break
except ServersRetrievalError:
continue
if servers and not self.servers:
raise NoMatchedServers()
return self.servers
def set_mini_server(self, server):
"""Instead of querying for a list of servers, set a link to a
speedtest mini server
"""
urlparts = urlparse(server)
name, ext = os.path.splitext(urlparts[2])
if ext:
url = os.path.dirname(server)
else:
url = server
request = build_request(url)
uh, e = catch_request(request)
if e:
raise SpeedtestMiniConnectFailure('Failed to connect to %s' %
server)
else:
text = uh.read()
uh.close()
extension = re.findall('upload_?[Ee]xtension: "([^"]+)"',
text.decode())
if not extension:
for ext in ['php', 'asp', 'aspx', 'jsp']:
try:
f = urlopen('%s/speedtest/upload.%s' % (url, ext))
except:
pass
else:
data = f.read().strip().decode()
if (f.code == 200 and
len(data.splitlines()) == 1 and
re.match('size=[0-9]', data)):
extension = [ext]
break
if not urlparts or not extension:
raise InvalidSpeedtestMiniServer('Invalid Speedtest Mini Server: '
'%s' % server)
self.servers = [{
'sponsor': 'Speedtest Mini',
'name': urlparts[1],
'd': 0,
'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]),
'latency': 0,
'id': 0
}]
return self.servers
def get_closest_servers(self, limit=5):
"""Limit servers to the closest speedtest.net servers based on
geographic distance
"""
if not self.servers:
self.get_servers()
for d in sorted(self.servers.keys()):
for s in self.servers[d]:
self.closest.append(s)
if len(self.closest) == limit:
break
else:
continue
break
printer(self.closest, debug=True)
return self.closest
def get_best_server(self, servers=None):
"""Perform a speedtest.net "ping" to determine which speedtest.net
server has the lowest latency
"""
if not servers:
if not self.closest:
servers = self.get_closest_servers()
servers = self.closest
results = {}
for server in servers:
cum = []
url = os.path.dirname(server['url'])
urlparts = urlparse('%s/latency.txt' % url)
printer('%s %s/latency.txt' % ('GET', url), debug=True)
for _ in range(0, 3):
try:
if urlparts[0] == 'https':
h = HTTPSConnection(urlparts[1])
else:
h = HTTPConnection(urlparts[1])
headers = {'User-Agent': USER_AGENT}
start = timeit.default_timer()
h.request("GET", urlparts[2], headers=headers)
r = h.getresponse()
total = (timeit.default_timer() - start)
except HTTP_ERRORS:
e = get_exception()
printer('%r' % e, debug=True)
cum.append(3600)
continue
text = r.read(9)
if int(r.status) == 200 and text == 'test=test'.encode():
cum.append(total)
else:
cum.append(3600)
h.close()
avg = round((sum(cum) / 6) * 1000.0, 3)
results[avg] = server
try:
fastest = sorted(results.keys())[0]
except IndexError:
raise SpeedtestBestServerFailure('Unable to connect to servers to '
'test latency.')
best = results[fastest]
best['latency'] = fastest
self.results.ping = fastest
self.results.server = best
self.best.update(best)
printer(best, debug=True)
return best
def download(self, callback=do_nothing):
"""Test download speed against speedtest.net"""
urls = []
for size in self.config['sizes']['download']:
for _ in range(0, self.config['counts']['download']):
urls.append('%s/random%sx%s.jpg' %
(os.path.dirname(self.best['url']), size, size))
request_count = len(urls)
requests = []
for i, url in enumerate(urls):
requests.append(build_request(url, bump=i))
def producer(q, requests, request_count):
for i, request in enumerate(requests):
thread = HTTPDownloader(i, request, start,
self.config['length']['download'])
thread.start()
q.put(thread, True)
callback(i, request_count, start=True)
finished = []
def consumer(q, request_count):
while len(finished) < request_count:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
finished.append(sum(thread.result))
callback(thread.i, request_count, end=True)
q = Queue(self.config['threads']['download'])
prod_thread = threading.Thread(target=producer,
args=(q, requests, request_count))
cons_thread = threading.Thread(target=consumer,
args=(q, request_count))
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
cons_thread.join(timeout=0.1)
stop = timeit.default_timer()
self.results.bytes_received = sum(finished)
self.results.download = (
(self.results.bytes_received / (stop - start)) * 8.0
)
if self.results.download > 100000:
self.config['threads']['upload'] = 8
return self.results.download
def upload(self, callback=do_nothing, pre_allocate=True):
"""Test upload speed against speedtest.net"""
sizes = []
for size in self.config['sizes']['upload']:
for _ in range(0, self.config['counts']['upload']):
sizes.append(size)
# request_count = len(sizes)
request_count = self.config['upload_max']
requests = []
for i, size in enumerate(sizes):
# We set ``0`` for ``start`` and handle setting the actual
# ``start`` in ``HTTPUploader`` to get better measurements
data = HTTPUploaderData(size, 0, self.config['length']['upload'])
if pre_allocate:
data.pre_allocate()
requests.append(
(
build_request(self.best['url'], data),
size
)
)
def producer(q, requests, request_count):
for i, request in enumerate(requests[:request_count]):
thread = HTTPUploader(i, request[0], start, request[1],
self.config['length']['upload'])
thread.start()
q.put(thread, True)
callback(i, request_count, start=True)
finished = []
def consumer(q, request_count):
while len(finished) < request_count:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
finished.append(thread.result)
callback(thread.i, request_count, end=True)
q = Queue(self.config['threads']['upload'])
prod_thread = threading.Thread(target=producer,
args=(q, requests, request_count))
cons_thread = threading.Thread(target=consumer,
args=(q, request_count))
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
cons_thread.join(timeout=0.1)
stop = timeit.default_timer()
self.results.bytes_sent = sum(finished)
self.results.upload = (
(self.results.bytes_sent / (stop - start)) * 8.0
)
return self.results.upload
def ctrl_c(signum, frame):
"""Catch Ctrl-C key sequence and set a SHUTDOWN_EVENT for our threaded
operations
"""
SHUTDOWN_EVENT.set()
print_('\nCancelling...')
sys.exit(0)
def version():
"""Print the version"""
print_(__version__)
sys.exit(0)
def csv_header():
"""Print the CSV Headers"""
print_('Server ID,Sponsor,Server Name,Timestamp,Distance,Ping,Download,'
'Upload')
sys.exit(0)
def parse_args():
"""Function to handle building and parsing of command line arguments"""
description = (
'Command line interface for testing internet bandwidth using '
'speedtest.net.\n'
'------------------------------------------------------------'
'--------------\n'
'https://github.com/sivel/speedtest-cli')
parser = ArgParser(description=description)
# Give optparse.OptionParser an `add_argument` method for
# compatibility with argparse.ArgumentParser
try:
parser.add_argument = parser.add_option
except AttributeError:
pass
parser.add_argument('--no-download', dest='download', default=True,
action='store_const', const=False,
help='Do not perform download test')
parser.add_argument('--no-upload', dest='upload', default=True,
action='store_const', const=False,
help='Do not perform upload test')
parser.add_argument('--bytes', dest='units', action='store_const',
const=('byte', 8), default=('bit', 1),
help='Display values in bytes instead of bits. Does '
'not affect the image generated by --share, nor '
'output from --json or --csv')
parser.add_argument('--share', action='store_true',
help='Generate and provide a URL to the speedtest.net '
'share results image, not displayed with --csv')
parser.add_argument('--simple', action='store_true', default=False,
help='Suppress verbose output, only show basic '
'information')
parser.add_argument('--csv', action='store_true', default=False,
help='Suppress verbose output, only show basic '
'information in CSV format. Speeds listed in '
'bit/s and not affected by --bytes')
parser.add_argument('--csv-delimiter', default=',', type=PARSER_TYPE_STR,
help='Single character delimiter to use in CSV '
'output. Default ","')
parser.add_argument('--csv-header', action='store_true', default=False,
help='Print CSV headers')
parser.add_argument('--json', action='store_true', default=False,
help='Suppress verbose output, only show basic '
'information in JSON format. Speeds listed in '
'bit/s and not affected by --bytes')
parser.add_argument('--list', action='store_true',
help='Display a list of speedtest.net servers '
'sorted by distance')
parser.add_argument('--server', help='Specify a server ID to test against',
type=PARSER_TYPE_INT)
parser.add_argument('--mini', help='URL of the Speedtest Mini server')
parser.add_argument('--source', help='Source IP address to bind to')
parser.add_argument('--timeout', default=10, type=PARSER_TYPE_INT,
help='HTTP timeout in seconds. Default 10')
parser.add_argument('--secure', action='store_true',
help='Use HTTPS instead of HTTP when communicating '
'with speedtest.net operated servers')
parser.add_argument('--no-pre-allocate', dest='pre_allocate',
action='store_const', default=True, const=False,
help='Do not pre allocate upload data. Pre allocation '
'is enabled by default to improve upload '
'performance. To support systems with '
'insufficient memory, use this option to avoid a '
'MemoryError')
parser.add_argument('--version', action='store_true',
help='Show the version number and exit')
parser.add_argument('--debug', action='store_true',
help=ARG_SUPPRESS, default=ARG_SUPPRESS)
options = parser.parse_args()
if isinstance(options, tuple):
args = options[0]
else:
args = options
return args
def validate_optional_args(args):
"""Check if an argument was provided that depends on a module that may
not be part of the Python standard library.
If such an argument is supplied, and the module does not exist, exit
with an error stating which module is missing.
"""
optional_args = {
'json': ('json/simplejson python module', json),
'secure': ('SSL support', HTTPSConnection),
}
for arg, info in optional_args.items():
if getattr(args, arg, False) and info[1] is None:
raise SystemExit('%s is not installed. --%s is '
'unavailable' % (info[0], arg))
def printer(string, quiet=False, debug=False, **kwargs):
"""Helper function to print a string only when not quiet"""
if debug and not DEBUG:
return
if debug:
out = '\033[1;30mDEBUG: %s\033[0m' % string
else:
out = string
if not quiet:
print_(out, **kwargs)
def shell():
"""Run the full speedtest.net test"""
global SHUTDOWN_EVENT, SOURCE, SCHEME, DEBUG
SHUTDOWN_EVENT = threading.Event()
signal.signal(signal.SIGINT, ctrl_c)
args = parse_args()
# Print the version and exit
if args.version:
version()
if not args.download and not args.upload:
raise SpeedtestCLIError('Cannot supply both --no-download and '
'--no-upload')
if args.csv_header:
csv_header()
if len(args.csv_delimiter) != 1:
raise SpeedtestCLIError('--csv-delimiter must be a single character')
validate_optional_args(args)
socket.setdefaulttimeout(args.timeout)
# If specified bind to a specific IP address
if args.source:
SOURCE = args.source
socket.socket = bound_socket
if args.secure:
SCHEME = 'https'
debug = getattr(args, 'debug', False)
if debug == 'SUPPRESSHELP':
debug = False
if debug:
DEBUG = True
# Pre-cache the user agent string
build_user_agent()
if args.simple or args.csv or args.json:
quiet = True
else:
quiet = False
if args.csv or args.json:
machine_format = True
else:
machine_format = False
# Don't set a callback if we are running quietly
if quiet or debug:
callback = do_nothing
else:
callback = print_dots
printer('Retrieving speedtest.net configuration...', quiet)
try:
speedtest = Speedtest()
except (ConfigRetrievalError, HTTP_ERRORS):
printer('Cannot retrieve speedtest configuration')
raise SpeedtestCLIError(get_exception())
if args.list:
try:
speedtest.get_servers()
except (ServersRetrievalError, HTTP_ERRORS):
print_('Cannot retrieve speedtest server list')
raise SpeedtestCLIError(get_exception())
for _, servers in sorted(speedtest.servers.items()):
for server in servers:
line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) '
'[%(d)0.2f km]' % server)
try:
print_(line)
except IOError:
e = get_exception()
if e.errno != errno.EPIPE:
raise
sys.exit(0)
# Set a filter of servers to retrieve
servers = []
if args.server:
servers.append(args.server)
printer('Testing from %(isp)s (%(ip)s)...' % speedtest.config['client'],
quiet)
if not args.mini:
printer('Retrieving speedtest.net server list...', quiet)
try:
speedtest.get_servers(servers)
except NoMatchedServers:
raise SpeedtestCLIError('No matched servers: %s' % args.server)
except (ServersRetrievalError, HTTP_ERRORS):
print_('Cannot retrieve speedtest server list')
raise SpeedtestCLIError(get_exception())
except InvalidServerIDType:
raise SpeedtestCLIError('%s is an invalid server type, must '
'be an int' % args.server)
printer('Selecting best server based on ping...', quiet)
speedtest.get_best_server()
elif args.mini:
speedtest.get_best_server(speedtest.set_mini_server(args.mini))
results = speedtest.results
printer('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
'%(latency)s ms' % results.server, quiet)
if args.download:
printer('Testing download speed', quiet,
end=('', '\n')[bool(debug)])
speedtest.download(callback=callback)
printer('Download: %0.2f M%s/s' %
((results.download / 1000.0 / 1000.0) / args.units[1],
args.units[0]),
quiet)
else:
printer('Skipping download test')
if args.upload:
printer('Testing upload speed', quiet,
end=('', '\n')[bool(debug)])
speedtest.upload(callback=callback, pre_allocate=args.pre_allocate)
printer('Upload: %0.2f M%s/s' %
((results.upload / 1000.0 / 1000.0) / args.units[1],
args.units[0]),
quiet)
else:
printer('Skipping upload test')
if args.simple:
print_('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' %
(results.ping,
(results.download / 1000.0 / 1000.0) / args.units[1],
args.units[0],
(results.upload / 1000.0 / 1000.0) / args.units[1],
args.units[0]))
elif args.csv:
print_(results.csv(delimiter=args.csv_delimiter))
elif args.json:
if args.share:
results.share()
print_(results.json())
if args.share and not machine_format:
printer('Share results: %s' % results.share())
def main():
try:
shell()
except KeyboardInterrupt:
print_('\nCancelling...')
except (SpeedtestException, SystemExit):
e = get_exception()
if getattr(e, 'code', 1) != 0:
raise SystemExit('ERROR: %s' % e)
if __name__ == '__main__':
main()
- Tips & Tricks
-
Tips & Tricks are simple little actions you can take to make your life easier when using Puppy. You will probably know most of the tips but there are always new users that don't.
- Using F keys to access bios options
-
Written by bigpup
When a computer first starts to bootup.
There is a small period of time when you can access the bios.
All computers use a specific keyboard F key to access bios options.
Example:
F2 to access bios setup.
F8 or F12 to access a boot device selection menu.
Etc........
It always says to press a specific F key.
But a single press of the key usually will not make it do what is expected.
So, you press it multiple times hoping it will make it work.
Simple solution.
Press the F key and do not release it, keeping it pressed down.
When the screen pops up, that it is supposed to make visible.
Release the key.
- Download youtube Videos
-
Written by smokey01
How often have you tried to download a youtube video and it didn't work?
Below is an idea that works quite well. Once you have identified the video, in other words the one you are currently watching online, simply pause the video and add 1s at the end of youtube in the url, eg: youtube1s.com.
A website will open where you can download the video.
- Entertainment
-
The May crossword by greengeek.
- Crossword
-
Puppy Crossword (May 2018)
(Formatted by greengeek using the "Puzzlefast" website)
"Backwords"
This may be a bit more of a brain workout than previous crosswords.
It's not cryptic (well maybe a couple of clues are a bit obtuse) but the difficulty
here is that each word is spelt backwards.
Should not be too hard though as all of the clues are puppy derivative names.
You will see that the letters "pup" appear quite frequently - this is hard to avoid
since "pup" spelt backwards is - of course - "pup" !
(ps: if you have any suggestions for a clue theme/topic for future crosswords please let me know!)
(See clues below image)
Scroll further down for answers:
- Useful Links
-
Puppy Linux Forums USA
Ibiblio repository USA
nluug repository Netherlands
Internode repository Australia
University of Crete repository Greece
aarnet repository Australia
Internet archive repository USA
Puppy Linux Tips by smokey01
Puppy Linux wikka Puppy sites
Bookmarkos provided by kerl
Search the Puppy Linux Forums
Barry Kauler's News Blog
labbe5 Report
- Contributors this month
-
Not all of the people below have physically given me the information to publish. If I find information I will give the credit where it is due. So if you see your name on the list below please don't be alarmed or upset.
smokey01
greengeek
Terry Schuster
Matt Martz
bigpup
Barry Kauler
fredx181
Proof reading - russoodle
- Newsletter Information
-
Display tip:
To improve the Notecase display format please press F7 then:
- Tick the "Restore last position/size" checkbox.
- Select the "Display" tab and tick "Wrap text".
Newsletter index written by 6502coder can be found here:
http://www.murga-linux.com/puppy/viewtopic.php?p=945962#945962
Contributions
If you have information you would like to see in the newsletter please email it to smokey01 at smokey01@internode.on.net. I prefer it to be created in Notecase otherwise it makes my job a bit more difficult. I don't intend doing any significant editing but I will attempt to read all of the articles and ask a couple of others to do some proof reading. If you would like to assist in proof reading please let me know on the email address above.
Notecase is very easy to learn and use. Try and keep your articles to less than 1000 words. Photos and images should be no bigger than 1024 x 768. I can always make them smaller.
The deadline for articles is the 20th of each month. Let's not worry about time zones. I know it may be the 20th in Australia and only the 19th in the USA but I can live with this. If it's more than 24 hours late with respect to Australian CST then your article may be pushed right, into the next edition. I expect proof reading to take less than a week which will provide about four days to publish at the beginning of each month.
I will upload the Newsletter to my site at http://smokey01.com/newsletters. There will be two versions. One will be an xz compressed Notecase file and the other will be a html file so it can be read in a browser.
I have changed the original naming convention to 0001-PuppyLinuxNewsletter-Jan2017.ncd.xz and 0001-PuppyLinuxNewsletter-Jan2017.html respectively. The formatting of the html is not brilliant but readable. The newsletter is intended to be downloaded and read in Notecase.
Disclaimer
The editor has the right to veto any articles that he/she considers inappropriate. A reasonable effort will be made to avoid spelling and grammatical errors however, I'm sure some may slip through. This newsletter is published by volunteers, and is free, so please be kind. Constructive criticism and positive suggestions are welcomed.
smokey01