Create artistic visualisations with your exercise data (Python version)

Overview

strava_py

Create artistic visualisations with your exercise data (Python version).

This is a port of the R strava package to Python.

Examples

Facets

A plot of activities as small multiples. The concept behind this plot was originally inspired by Sisu.

facets

Map

A map of activities viewed in plan.

map

How to use

Bulk export from Strava

The process for downloading data is described on the Strava website here: [https://support.strava.com/hc/en-us/articles/216918437-Exporting-your-Data-and-Bulk-Export#Bulk], but in essence, do the following:

  1. Log in to Strava
  2. Select "Settings" from the main drop-down menu at top right of the screen
  3. Select "My Account" from the navigation menu to the left of the screen.
  4. Under the "Download or Delete Your Account" heading, click the "Get Started" button.
  5. Under the "Download Request", heading, click the "Request Your Archive" button. Don't click anything else on that page, i.e. particularly not the "Request Account Deletion" button.
  6. Wait for an email to be sent
  7. Click the link in email to download zipped folder containing activities
  8. Unzip files

Process the data

The main function for importing and processing activity files expects a path to a directory of unzipped GPX and / or FIT files. If required, the fit2gpx package provides useful tools for pre-processing bulk files exported from Strava, e.g. unzipping activity files (see Use Case 3: Strava Bulk Export Tools).

df = process_data(<path to folder with GPX and / or FIT files>)

Plot activities as small multiples

plot_facets(df, output_file = 'plot.png')

Plot activity map

plot_map(df, lon_min=None, lon_max= None, lat_min=None, lat_max=None,
             alpha=0.3, linewidth=0.3, output_file="map.png")
Comments
  • Getting:

    Getting: "gpxpy.gpx.GPXException: latitude is mandatory in None (got None)"

    Hey,

    I tried using the CLI tool but I'm getting this error: gpxpy.gpx.GPXException: latitude is mandatory in None (got None)

    I tried setting --lon_max, --lat_max to 180,90 and --lat_min, --lon_min to 0,0 but still getting the same error.

    Is this a problem with a specific .gpx file? And if so how can I find that one because the error doesn't show which file.

    Thanks

    opened by Alpha249 7
  • Starting cli.py gives error: ModuleNotFoundError: No module named 'stravavis'

    Starting cli.py gives error: ModuleNotFoundError: No module named 'stravavis'

    Thanks for the app, it looks quite nice. But as I'm a bit new to Python, I'm having trouble to get it started.

    I cloned the repo and tried from its root directory: [email protected] strava_py % python3 src/stravavis/cli.py /Users/jmizv/Downloads/gpx

    But I just got that error message:

    Traceback (most recent call last):
      File "/Users/jmizv/IdeaProjects/strava_py/src/stravavis/cli.py", line 91, in <module>
        main()
      File "/Users/jmizv/IdeaProjects/strava_py/src/stravavis/cli.py", line 41, in main
        from stravavis.plot_calendar import plot_calendar
    ModuleNotFoundError: No module named 'stravavis'
    [email protected] strava_py % 
    

    I'm probably missing something obvious but I don't see it. Could you, @marcusvolz, please give an advice?

    opened by jmizv 7
  • Add CLI as strava_py

    Add CLI as strava_py

    This PR adds a CLI for creating visualisations, and puts it into an installable package, ready for distributions via https://pypi.org so it can be installed using pip. (I can help with how to do that later!)

    First, I moved the strava_py directory into a src directory. This is commonly used for Python projects. The main benefits is to make sure when you're testing, you're testing against something that has been installed, and not something that happens to have the same directory name in your current dir. Much more:

    • https://packaging.python.org/en/latest/tutorials/packaging-projects/#a-simple-project
    • https://blog.ionelmc.ro/2014/05/25/python-packaging/
    • https://hynek.me/articles/testing-packaging/

    The way to install from source:

    pip install .
    

    Or if you're developing, -e means an editable install, so you can make changes to your local source tree and they're reflected in what's run:

    pip install -e .
    

    Then I went for strava_py as the CLI name (this can be changed):

    $ strava_py --help
    usage: strava_py [-h] [-o OUTPUT_FILE] path
    
    positional arguments:
      path                  Input path to folder with GPX and / or FIT files
    
    options:
      -h, --help            show this help message and exit
      -o OUTPUT_FILE, --output_file OUTPUT_FILE
                            Output PNG file (default: plot.png)
    

    Example run:

    $ strava_py /tmp/my_strava_activities
    Processing data...
    Processing: 6628958226.gpx
    Processing: 6626094662.gpx
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 142); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 162); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 203); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 249); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 285); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 326); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 351); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 397); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 443); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 487); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 531); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 575); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 619); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 663); adding dummy dev data...
      warnings.warn(msg)
    /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/fitdecode/reader.py:909: UserWarning: 'field "native_field_num" (idx #0) not found in message "field_description"' (local_mesg_num: 0; chunk_offset: 707); adding dummy dev data...
      warnings.warn(msg)
    Processing: 6678512291.fit
    Processing: 6619621876.gpx
    Processing: 6604948302.gpx
    Processing: 6860888905.fit
    Processing: 6678648994.fit
    Processing: 6676505895.fit
    Processing: 6810573487.fit
    Processing: 6623947053.gpx
    Processing: 6630963143.gpx
    Plotting facets...
    Saved to plot.png
    

    I'll also add a bunch of inline comments to explain what some things do, please feel free to ask more about any of this!

    opened by hugovk 3
  • Elevations

    Elevations

    @hugovk Thanks for reviewing these and for the recent PRs. This one just adds the elevations small multiples plot.

    I think I might have missed the suggested commits in your previous PRs, could you let me know if everything went through ok? Thanks!

    opened by marcusvolz 2
  • ValueError: All objects passed were None

    ValueError: All objects passed were None

    Hi, I have been trying to apply this package on my data pull from Strava but in the first step of processing data I'm getting this error of "ValueError: All objects passed were None" attaching a screenshot for reference. I tried to give address of either the activities or routes folder which has gpx files but still the error remains constant Screenshot 2022-12-25 at 16 03 22

    opened by shu3hamiitkgp 1
  • Fix squashed maps

    Fix squashed maps

    This is similar to https://github.com/marcusvolz/strava/issues/5.

    Here's the same GPX using main:

    strava-map

    Like https://stackoverflow.com/a/14457180/724176, rather than using latitude and longitude values, this uses a Mercator projection calculation to come up with x and y values instead.

    Dummy, unit map width and height values are used, as we don't know and don't need to know the final size as matplotlib will take care of that.

    And with this PR:

    strava-map

    Measuring performance:

    With main, on 2,946 GPX files (but commenting out some bits of cli.py to only run "Processing data..." and "Plotting map..."), it takes 11m45s on a Mac M1 16GB.

    With the PR it takes a comparable 11m43s, which is really good because the transform is done by Pandas so presumably in C.

    opened by hugovk 1
  • UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 379: character maps to <undefined>

    UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 379: character maps to

    When I execute stravavis against this single attached file (5670595833.gpx.txt, you need to remove the txt extension) it fails with the below error. I tend to add emojis to my strava activity titles so this is in my opinion the problem. Not sure if you could exclude this easily? The files are certainly not encoded with Cp1252.

    C:\Users\...>stravavis C:\Users\...
    Processing data...
    Processing ----------------------------------------   0% -:--:--
    multiprocessing.pool.RemoteTraceback:
    """
    Traceback (most recent call last):
      File "C:\Program Files\Python310\lib\multiprocessing\pool.py", line 125, in worker
        result = (True, func(*args, **kwds))
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\stravavis\process_data.py", line 13, in process_file
        return process_gpx(fpath)
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\stravavis\process_data.py", line 21, in process_gpx
        activity = gpxpy.parse(open(gpxfile))
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\gpxpy\__init__.py", line 37, in parse
        parser = mod_parser.GPXParser(xml_or_file)
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\gpxpy\parser.py", line 70, in __init__
        self.init(xml_or_file)
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\gpxpy\parser.py", line 82, in init
        text = xml_or_file.read() if hasattr(xml_or_file, 'read') else xml_or_file # type: ignore
      File "C:\Program Files\Python310\lib\encodings\cp1252.py", line 23, in decode
        return codecs.charmap_decode(input,self.errors,decoding_table)[0]
    UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 379: character maps to <undefined>
    """
    
    The above exception was the direct cause of the following exception:
    
    Traceback (most recent call last):
      File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
        exec(code, run_globals)
      File "C:\Users\...\AppData\Roaming\Python\Python310\Scripts\stravavis.exe\__main__.py", line 7, in <module>
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\stravavis\cli.py", line 51, in main
        df = process_data(args.path)
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\stravavis\process_data.py", line 108, in process_data
        processed = list(it)
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\rich\progress.py", line 168, in track
        yield from progress.track(
      File "C:\Users\...\AppData\Roaming\Python\Python310\site-packages\rich\progress.py", line 1211, in track
        for value in sequence:
      File "C:\Program Files\Python310\lib\multiprocessing\pool.py", line 870, in next
        raise value
    UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 379: character maps to <undefined>
    

    5670595833.gpx.txt

    bug 
    opened by jmizv 1
  • Add --bbox CLI param as a shortcut

    Add --bbox CLI param as a shortcut

    It's a bit tedious to enter four params and values on the command line when creating a map:

    stravavis activities --lon_min 23.516665 --lat_min 60.000467 --lon_max 26.390877 --lat_max 61.360291

    This PR adds a --bbox shortcut for those, that takes a bounding box of comma-separated values of lon_min, lat_min, lon_max, lat_max:

    stravavis activities --bbox 23.516665,60.000467,26.390877,61.360291

    It's quite easy to find bounding boxes online, for example: http://bboxfinder.com/#60.000467,23.516665,61.360291,26.390877


    I also noticed that these lat/lon values are not actually passed from the CLI to the plotting function, and neither are alpha or linewidth, so let's fix that too :)


    Finally, bump some GitHub Actions versions for the CI.

    enhancement 
    opened by hugovk 1
  • Updated README: Add installation instructions and link directly to images

    Updated README: Add installation instructions and link directly to images

    Images are broken on PyPI:

    image

    Instead of linking to things like https://github.com/marcusvolz/strava_py/blob/main/plots/facets001.png, which is an HTML page showing the image, and which GitHub can apparently handle redirects to for the README, link directly to the image file, like https://raw.githubusercontent.com/marcusvolz/strava_py/main/plots/facets001.png

    This also adds installation instructions to the README.

    opened by hugovk 1
  • More progress bars for slow loops, and refactor

    More progress bars for slow loops, and refactor

    Also in Python we generally prefer to iterate over objects in a list (or tuple, or other so-called "iterable") rather than using an index of range(len(thing)).

    So for example, instead of:

    n = len(activities)
    for i in range(n):
        print(activities[i])
    

    This is more direct and readable:

    for activity in activities:
        print(activity)
    

    If we do need an index as well, enumerate is often used:

    for i, activity in enumerate(activities):
        print(i, activity)
    
    opened by hugovk 1
  • Use multiprocessing to speed up processing of input files

    Use multiprocessing to speed up processing of input files

    Use multiprocessing to speed up processing of input files, using the number of processes which matches the CPU count.

    On my old dual-core Mac, the processing step speeds up:

    • 601 files (all of 2021): 2m7s -> 1m20s
    • 2,629 files (my full Strava archive): 10m29s -> 5m9s

    Will be a larger gain for machines with more CPUs.

    multiprocessing needs a single worker function, this is the new process_file which decides which of process_gpx and process_fit to call. They also need to be top-level functions to work with multiprocessing.

    I also formatted the file using the popular Black autoformatter:

    python -m pip install -U black
    black . 
    

    I also fixed a bug when using an input path like ~/dir, it needs to expand ~ before checking it's a dir (and appending *).

    opened by hugovk 1
Releases(v0.1.0)
  • v0.1.0(Dec 30, 2022)

    What's Changed

    • Add --bbox CLI param as a shortcut by @hugovk in https://github.com/marcusvolz/strava_py/pull/18
    • Add support for Python 3.11 by @hugovk in https://github.com/marcusvolz/strava_py/pull/26
    • Catch exception to skip invalid file by @hugovk in https://github.com/marcusvolz/strava_py/pull/27
    • Fix encoding on Windows by @hugovk in https://github.com/marcusvolz/strava_py/pull/20
    • Fix squashed maps by @hugovk in https://github.com/marcusvolz/strava_py/pull/21
    • Fix README typo by @hugovk in https://github.com/marcusvolz/strava_py/pull/24

    Full Changelog: https://github.com/marcusvolz/strava_py/compare/v0.0.2...v0.1.0

    Source code(tar.gz)
    Source code(zip)
  • v0.0.2(Sep 1, 2022)

    What's Changed

    • Updated README: Add installation instructions and link directly to images by @hugovk in https://github.com/marcusvolz/strava_py/pull/17

    Full Changelog: https://github.com/marcusvolz/strava_py/compare/v0.0.1...v0.0.2

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1(Sep 1, 2022)

    What's Changed

    • Add CLI as strava_py by @hugovk in https://github.com/marcusvolz/strava_py/pull/1
    • added plot_map by @marcusvolz in https://github.com/marcusvolz/strava_py/pull/2
    • Rename package and CLI program to stravavis by @hugovk in https://github.com/marcusvolz/strava_py/pull/3
    • Allow input path specification and show progress bar by @hugovk in https://github.com/marcusvolz/strava_py/pull/4
    • Elevations by @marcusvolz in https://github.com/marcusvolz/strava_py/pull/5
    • Test run on GitHub Actions by @hugovk in https://github.com/marcusvolz/strava_py/pull/6
    • Landscape by @marcusvolz in https://github.com/marcusvolz/strava_py/pull/8
    • Use multiprocessing to speed up processing of input files by @hugovk in https://github.com/marcusvolz/strava_py/pull/9
    • Calendar by @marcusvolz in https://github.com/marcusvolz/strava_py/pull/10
    • Fix local variable 'activities' referenced before assignment by @hugovk in https://github.com/marcusvolz/strava_py/pull/11
    • Fix ValueError with --activities_path by @hugovk in https://github.com/marcusvolz/strava_py/pull/12
    • Dumbbell by @marcusvolz in https://github.com/marcusvolz/strava_py/pull/13
    • Allow --activities_path to point to file or directory by @hugovk in https://github.com/marcusvolz/strava_py/pull/14
    • More progress bars for slow loops, and refactor by @hugovk in https://github.com/marcusvolz/strava_py/pull/15

    New Contributors

    • @hugovk made their first contribution in https://github.com/marcusvolz/strava_py/pull/1
    • @marcusvolz made their first contribution in https://github.com/marcusvolz/strava_py/pull/2

    Full Changelog: https://github.com/marcusvolz/strava_py/commits/v0.0.1

    Source code(tar.gz)
    Source code(zip)
Owner
Marcus Volz
Data visualisation | mathematical visualisation | 3D visualisation
Marcus Volz
An interactive UMAP visualization of the MNIST data set.

Code for an interactive UMAP visualization of the MNIST data set. Demo at https://grantcuster.github.io/umap-explorer/. You can read more about the de

grant 70 Dec 27, 2022
Getting started with Python, Dash and Plot.ly for the Data Dashboards team

data_dashboards Getting started with Python, Dash and Plot.ly for the Data Dashboards team Getting started MacOS users: # Install the pyenv version ma

Department for Levelling Up, Housing and Communities 1 Nov 08, 2021
Curvipy - The Python package for visualizing curves and linear transformations in a super simple way

Curvipy - The Python package for visualizing curves and linear transformations in a super simple way

Dylan Tintenfich 55 Dec 28, 2022
Open-source demos hosted on Dash Gallery

Dash Sample Apps This repository hosts the code for over 100 open-source Dash apps written in Python or R. They can serve as a starting point for your

Plotly 2.7k Jan 07, 2023
Bokeh Plotting Backend for Pandas and GeoPandas

Pandas-Bokeh provides a Bokeh plotting backend for Pandas, GeoPandas and Pyspark DataFrames, similar to the already existing Visualization feature of

Patrik Hlobil 822 Jan 07, 2023
A guide for using Bootstrap 5 classes in Dash Bootstrap Components V1

dash-bootstrap-cheatsheet This handy interactive cheatsheet makes it easy to use the Bootstrap 5 classes with your Dash app made with the latest versi

10 Dec 22, 2022
Yata is a fast, simple and easy Data Visulaization tool, running on python dash

Yata is a fast, simple and easy Data Visulaization tool, running on python dash. The main goal of Yata is to provide a easy way for persons with little programming knowledge to visualize their data e

Cybercreek 3 Jun 28, 2021
A Python Binder that merge 2 files with any extension by creating a new python file and compiling it to exe which runs both payloads.

Update ! ANONFILE MIGHT NOT WORK ! About A Python Binder that merge 2 files with any extension by creating a new python file and compiling it to exe w

Vesper 15 Oct 12, 2022
Project coded in Python using Pandas to look at changes in chase% for batters facing a pitcher first time through the order vs. thrid time

Project coded in Python using Pandas to look at changes in chase% for batters facing a pitcher first time through the order vs. thrid time

Jason Kraynak 1 Jan 07, 2022
D-Analyst : High Performance Visualization Tool

D-Analyst : High Performance Visualization Tool D-Analyst is a high performance data visualization built with python and based on OpenGL. It allows to

4 Apr 14, 2022
view cool stats related to your discord account.

DiscoStats cool statistics generated using your discord data. How? DiscoStats is not a service that breaks the Discord Terms of Service or Community G

ibrahim hisham 5 Jun 02, 2022
CPG represent!

CoolPandasGroup CPG represent! Arianna Brandon Enne Luan Tracie Project requirements: use Pandas to clean and format datasets use Jupyter Notebook to

Enne 3 Feb 07, 2022
Tools for calculating and visualizing Elo-like ratings of MLB teams using Retosheet data

Overview This project uses historical baseball games data to calculate an Elo-like rating for MLB teams based on regular season match ups. The Elo rat

Lukas Owens 0 Aug 25, 2021
Practical-statistics-for-data-scientists - Code repository for O'Reilly book

Code repository Practical Statistics for Data Scientists: 50+ Essential Concepts Using R and Python by Peter Bruce, Andrew Bruce, and Peter Gedeck Pub

1.7k Jan 04, 2023
Python scripts to manage Chia plots and drive space, providing full reports. Also monitors the number of chia coins you have.

Chia Plot, Drive Manager & Coin Monitor (V0.5 - April 20th, 2021) Multi Server Chia Plot and Drive Management Solution Be sure to ⭐ my repo so you can

338 Nov 25, 2022
GitHub Stats Visualizations : Transparent

GitHub Stats Visualizations : Transparent Generate visualizations of GitHub user and repository statistics using GitHub Actions. ⚠️ Disclaimer The pro

YuanYap 7 Apr 05, 2022
Scientific Visualization: Python + Matplotlib

An open access book on scientific visualization using python and matplotlib

Nicolas P. Rougier 8.6k Dec 31, 2022
Apache Superset is a Data Visualization and Data Exploration Platform

Superset A modern, enterprise-ready business intelligence web application. Why Superset? | Supported Databases | Installation and Configuration | Rele

The Apache Software Foundation 50k Jan 06, 2023
Keir&'s Visualizing Data on Life Expectancy

Keir's Visualizing Data on Life Expectancy Below is information on life expectancy in the United States from 1900-2017. You will also find information

9 Jun 06, 2022
This project is created to visualize the system statistics such as memory usage, CPU usage, memory accessible by process and much more using Kibana Dashboard with Elasticsearch.

System Stats Visualizer This project is created to visualize the system statistics such as memory usage, CPU usage, memory accessible by process and m

Vishal Teotia 5 Feb 06, 2022