Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Commit

Permalink
Merged PR 44966: Generate visible QR / Cross-platform Implementation
Browse files Browse the repository at this point in the history
Notes:

- This has only been tested on Python 3.6.0
- Follow the instructions in the README for installation and execution.
- To run this version of the code, check out to develop before running python setup.py install
- Currently passing all tests in both Windows and macOS

Features:

- The QR code is able to be generated with the click of a simple GUI button using tkinter
- The code runs cross-platform and contains logic to install OS-specific dependencies
- Relatively fast, though performance could use some improvement

To Do:

- Write Instructions and Asset classes and their respective modules
- Implement iOS support using cfgutil
- Make the code run by double-clicking an executable (.app / .exe)
- Test using a USB stick
- Write more tests! 😃

Related work items: #1432843
  • Loading branch information
Eric Hanko committed Mar 4, 2017
2 parents d793ff4 + da8e4bf commit bd4a6a4
Show file tree
Hide file tree
Showing 39 changed files with 1,088 additions and 436 deletions.
44 changes: 28 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
# PyBar
Generates QRCodes for a physical inventory
# InQRy
Obtains machine hardware specifications then generates a QR code containing
the data.

### Install

`python3 setup.py install`
### Requirements
- `Python 3`

### Install
clone & `python3 setup.py install`

### Test

`pytest`


### Run
`python inqry` and click "Generate QR Code"

*TBD*
## Description

InQRy is a cross-platform application that generates a single QR code containing the machine's hardware
specifications. This application is designed primarily to be used during a physical inventory procedure.

## Description
The QR code contains detailed information about the client machine or device,
which can then be scanned it quickly add assets into a Snipe-IT database.

PyBar generates a single QR code when executed, which is designed to be used
during a physical inventory procedure.
## How It Works

The QR code contains detailed information about the asset the code was run on,
and is used for adding that asset's hardware specifications into a Snipe-IT
fieldset (i.e. form-field). PyBar quickly determines which fieldset to use based
on the machine it was run on, and add the necessary information to skip through
different types of fields in Snipe-IT.
InQRy obtains hardware specs using shell commands and parses the output for
the desired information. It then takes that information, processes it and
instructs the `qrcode` Python module to create a QR code, which is displayed
on the screen for scanning.

PyBar was written to obtain asset information quickly and accurately for both
InQRy determines which instructions to follow based on the
machine itself. Those instructions contain other necessary information that
allow it to move fluidly through different types of fields in the Snipe-IT asset
entry form.

InQRy was written to obtain asset information quickly and accurately for both
an initial physical inventory procedure, as well as subsequent hardware audits.

## Currently Supported Platforms
- macOS
- Windows
7 changes: 7 additions & 0 deletions inqry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__author__ = "OXO Hub Lab"
__copyright__ = "Copyright 2017, Microsoft Corporation"
__credits__ = ["Eric Hanko", "Jacob Zaval", "Michael Brown"]
__license__ = "MIT"
__email__ = "[email protected]"
__status__ = "Alpha"

44 changes: 44 additions & 0 deletions inqry/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import tkinter as tk
from inqry.qr_builder import AssetQRCode
from inqry.system_specs import systemspecs

ASSET = AssetQRCode(systemspecs.main())
ROOT = tk.Tk()


def get_dimensions_to_center_window():
pass


def calculate_center_coordinates(screen_dimension, current_dimension):
return (screen_dimension / 2) - (current_dimension / 2)


def obtain_default_dimensions_for_the_root_gui_object():
return tuple(int(_) for _ in ROOT.geometry().split('+')[0].split('x'))


def center(): # Not used at this time
ROOT.update_idletasks()
size = obtain_default_dimensions_for_the_root_gui_object()
height = size[0]

x = calculate_center_coordinates(ROOT.winfo_screenwidth(), height)
y = calculate_center_coordinates(ROOT.winfo_screenheight(), width)
ROOT.geometry(f'{height}x{width}+{x}+{y}')


def click():
ASSET.display()


def main():
ROOT.title("InQRy")
button = tk.Button(ROOT, text="Generate QR Code", command=click)
button.grid(column=1, row=0)
ROOT.focus_force()
ROOT.mainloop()


if __name__ == '__main__':
main()
File renamed without changes.
22 changes: 22 additions & 0 deletions inqry/qr_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from qrcode import QRCode


class AssetQRCode(QRCode):
"""An AssetQRCode instance generates and displays a QR code. At the time
of writing this. The QR code is built only by being passed a list of string
objects using the add_data() method"""

def __init__(self, profile):
self.qr = QRCode()
super(AssetQRCode, self).__init__()
self.profile = profile
self.code = self.build()

def build(self):
attribute_list = self.profile.list_all()
for attribute in attribute_list:
self.qr.add_data(attribute)
return self.qr

def display(self):
return self.qr.make_image().show()
19 changes: 19 additions & 0 deletions inqry/system_specs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys
import pip

OS = sys.platform


def install(package):
pip.main(['install', package])


if OS == 'win32':
install("pypiwin32")
install("wmi")
from inqry.system_specs import windows_system_profiler as system_profiler
elif OS == 'darwin':
from inqry.system_specs import mac_system_profiler as system_profiler
from inqry.system_specs import macdisk
else:
raise OSError('Operating system unknown')
3 changes: 2 additions & 1 deletion pybar/diskutil.py → inqry/system_specs/diskutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

def get_physical_disk_identifiers(diskutil_list_output=None):
diskutil_list_output = diskutil_list_output or list_all()
physical_disk_id_pattern = re.compile(r'(/dev/disk\d+) \(\w+, physical\).*')
physical_disk_id_pattern = re.compile(
r'(/dev/disk\d+) \(\w+, physical\).*')

return re.findall(physical_disk_id_pattern, diskutil_list_output)

Expand Down
72 changes: 72 additions & 0 deletions inqry/system_specs/mac_system_profiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from enum import Enum
import re
import subprocess
import yaml
from inqry.system_specs import macdisk

BASE_COMMAND = '/usr/sbin/system_profiler'


class DataTypes(Enum):
def __str__(self):
return 'SP' + self.value + 'DataType'

DEVELOPER_TOOLS = 'DeveloperTools'
DIAGNOSTICS = 'Diagnostics'
DISPLAYS = 'Displays'
HARDWARE = 'Hardware'
MEMORY = 'Memory'
SERIAL_ATA = 'SerialATA'
SOFTWARE = 'Software'
STORAGE = 'Storage'
THUNDERBOLT = 'Thunderbolt'


def get_data(data_type):
"""
Returns system_profiler data on macOS as a dictionary
`data_type` must be one of `DataTypes`, i.e.
get_data(DataTypes.HARDWARE)
:param data_type:
:type data_type: DataTypes
:return dict:
"""
return parse_command_output(_get_command_output(str(data_type)))


def parse_command_output(output):
return yaml.load(_yamlize_output(output))


def _get_command_output(*arguments):
return subprocess.check_output([BASE_COMMAND] + list(arguments)).decode('utf-8')


def _yamlize_output(output):
return '\n'.join([_yamlize_line(line) for line in output.split('\n')])


def _yamlize_line(line):
line = _remove_dash_after_first_colon(line)

eight_spaces = ' '
line = line[2:] if line.startswith(eight_spaces) else line

return line


def _remove_dash_after_first_colon(line):
return re.sub(pattern=r'(.+: )-', repl=r'\1', string=line)


def collector():
return get_data(DataTypes.HARDWARE)['Hardware']['Hardware Overview']


def get_mac_internal_storage():
disk_list = macdisk.get_all_physical_disks()
internal_disks = [disk for disk in disk_list if disk.is_internal]
return internal_disks
22 changes: 14 additions & 8 deletions pybar/macdisk.py → inqry/system_specs/macdisk.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import re
import yaml
from pybar import diskutil
from inqry.system_specs import diskutil


def create_from_diskutil_info_output(output):
return Disk(yaml.load(output))


def get_all_physical_disks():
return [
create_from_diskutil_info_output(diskutil.get_disk_info(disk_identifier))
for disk_identifier in diskutil.get_physical_disk_identifiers()
]
return [create_from_diskutil_info_output(diskutil.get_disk_info(disk_identifier)) for disk_identifier in
diskutil.get_physical_disk_identifiers()]


class Disk:

def __init__(self, attributes=None):
self.attributes = attributes or {}

Expand All @@ -31,6 +28,15 @@ def is_internal(self):
def is_external(self):
return self.device_location == 'External'

@property
def type(self):
if self.is_ssd:
return 'SSD'
elif not self.is_ssd:
return 'HDD'
else:
return 'Unknown'

@property
def device_name(self):
return self.attributes.get('Device / Media Name')
Expand All @@ -41,12 +47,12 @@ def is_ssd(self):

@property
def verbose_disk_size(self):
return self.attributes.get('Disk Size') or self.attributes.get('Total Size')
return self.attributes.get('Disk Size') or self.attributes.get(
'Total Size')

@property
def size(self):
disk_size_pattern = re.compile(r'(?P<disk_size>\d+\.?\d* [MGT]?B) .*$')
disk_size_match = re.match(disk_size_pattern, self.verbose_disk_size)

if disk_size_match:
return disk_size_match.group('disk_size')
Loading

0 comments on commit bd4a6a4

Please sign in to comment.