71 Commits

Author SHA1 Message Date
Alexis Svinartchouk
ec015da795 Avoid "Invalid state percentage: null" errors
Change-type: patch
2019-03-29 17:24:34 +01:00
Alexis Svinartchouk
871db09447 fix(tests): Fix gui tests
Change-type: patch
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2019-01-24 13:21:26 +01:00
Alexis Svinartchouk
63967d1558 fix(tests): Remove throw if no percentage or eta test
Since 25916200f2864a9b137325f919c0f8ef5d62fa60 we can handle a progress
state with no percentage or eta.
2019-01-24 13:21:26 +01:00
Lorenzo Alberto Maria Ambrosi
20996b153d fix(GUI): Reject drives with null size
Change-type: patch
Changelog-entry: Reject drives with null size (fixes pretty-bytes error)
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzoa@balena.io>
2018-11-22 19:57:45 +01:00
Alexis Svinartchouk
92d969b075 fix(test): Move tests to correct scope
Move available-drives, flash-state and selection-state tests to gui
tests. These files only test gui code and make `make test-cli` fail.

Change-type: patch
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2018-08-07 09:22:29 +02:00
Benedict Aas
5863319c0b
fix(GUI): file-picker performance and design improvements
- Replace onClick arrow functions in all components that use them for
  efficiency reasons: 300-500% speed-up
- Sort by folders and ignore case for better UX
- Remove use of `rendition.Button` in files, leading to a 10-20%
  performance increase when browsing files
- Proper sidebar width and spacing
- Recents and favorites are now filtered by existence async for a tiny
  performance improvement
- Make Breadcrumbs and Icon pure components to stop frequent re-rendering
- Initial support for array constraints
- Use first constraint as initial path instead of homedir if a
  constraint is set
- Use correct design height on modal, `calc(100vh - 20px)`
- Reset scroll position when browsing a new folder
- Fuse Bluebird `.map()` and `.reduce()` in
  `files.getAllFilesMetadataAsync`.
- Use `localeCompare`'s own case-insensitive option instead of calling
  `.toLowerCase()` twice on `n-2` files compared.
- Use 16px font sizes in sidebar and files to match design.
- Disable `$locationProvider.html5Mode.rewriteLinks`, which seemed to
  take 50ms of the directory changing time.
- Leave file extension as-is in `files.getFileMetadataSync` and the
  async counterpart for a very minor performance improvement.

Change-Type: patch
2018-05-30 12:39:59 +02:00
Jonas Hermsmeier
e0ebdc9045
fix(test): Fix lint errors & tests 2018-05-25 20:35:59 +02:00
Jonas Hermsmeier
6232cc7d49
test(settings): Update test specs accordingly
Change-Type: patch
2018-05-25 20:35:59 +02:00
Jonas Hermsmeier
447efc7096
refactor: Move shared/store.js -> gui/app/models/store.js 2018-05-23 21:20:50 +02:00
Benedict Aas
f6ce603e45
feat(GUI): add convenience localstorage class (#2276)
* feat(GUI): add convenience localstorage class

We add a class `Storage` and accompanying helper methods that makes
localStorage usage easier.

Change-Type: patch
Changelog-Entry: Add a convenience Storage class on top of localStorage.
2018-05-15 11:59:36 +01:00
Benedict Aas
4dc64ee15b
feat(GUI): add app to gui folder structure (#2002)
* feat(GUI): add app to gui folder structure

We add a `lib/gui/app/` folder to help transition to Webpack usage.

Change-Type: patch
Changelog-Entry: Add `lib/gui/app` folder to ease into Webpack usage.
2018-01-29 18:34:21 +00:00
Juan Cruz Viotti
d8e31665a0 chore: follow standardjs guidelines (#1664)
- Extend the `standard` ESLint configuration
- Remove ESLint rules that are defined in the `standard` configuration
- Get rid of semi-colons

See: https://github.com/resin-io/etcher/pull/1657
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-08-03 09:01:54 -04:00
Juan Cruz Viotti
5c19b70e83 refactor: adhere mostly to StandardJS guidelines (#1657)
This commit changes the whole codebase to adhere to all StandardJS
guidelines rules except semicolons, since the removal of semicolons
affect pretty much all lines, and the final diff is very hard to follow
(and to assess other more involved changes).

In a nutshell:

- When using `function`, we now require a space before the opening
  parenthesis
- If a line with operators is broken into multiple lines, the operator
  should now go after the line break
- Unnecessary padding lines are now forbidden

There were also some minor things that the `standard` CLI caught that I
updated here.

See: https://standardjs.com
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-08-03 06:59:02 -04:00
Juan Cruz Viotti
fc3eeff1c9 refactor(GUI): make settings model setter asynchronous (#1597)
This is part of the process of implementing support for a configuration
file. We previously decoupled the Redux store from localStorage by
moving the logic that actually persists the data to localStorage to a
local-settings.js file, however the localStorage API is synchronous, so
it follows that at the moment, all functions that interact with are also
synchronous.

Moving to storing the settings to a file means turning all these
functions to promises, which we do in this commit, in order to not mix
the addition of the configuration file feature with the huge amount of
refactoring it requires.

See: https://github.com/resin-io/etcher/issues/1356
Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-08-01 10:27:15 -04:00
Juan Cruz Viotti
4122e0bf1d refactor: move most models to lib/shared/ (#1596)
Now that the Redux store is no longer front-end dependent, we can move
most of the models to lib/shared. Currently, lib/shared is a mess, and
contains all sorts of functionality, however moving things out of the
GUI is the first step. Once we have everything decoupled, we can
organise all the code we have there.

Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-07-18 12:34:10 -03:00
Juan Cruz Viotti
fdceaadec5 refactor(GUI): move redux store to lib/shared (#1576)
After https://github.com/resin-io/etcher/pull/1569, the redux store can
run outside of a browser context. This commit moves it to `lib/shared`,
where we will likely move all the other models as well. As an extra, I
renamed `Store` to `store`, since there was no reason for that variable
to be capitalized.

Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-07-07 18:47:39 -04:00
Juan Cruz Viotti
4dc56f4678 refactor(GUI): decouple localStorage from the redux store (#1569)
We're currently persisting the user settings in localSettings by using a
redux plugin called redux-localstorage. As a way to decouple the redux
store from a technology that is browser specific, this commit makes the
following changes:

- Create local-settings.js, which is concerned with managing settings in
  a persisting location

- Decouple the redux store from the persisting storage method

- Extend the settings model to persist settings, cache reads, etc

Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-07-06 17:49:13 -04:00
Juan Cruz Viotti
103a048e4d refactor(GUI): replace SET_SETTING with an atomic SET_SETTINGS action (#1515)
This commit is the first on a series of commit to incrementally
implement support for configuration files (so we avoid a huge PR like we
have at the moment).

Once of the first things we can do is replace the `SET_SETTING` redux
action with an atomic `SET_SETTINGS` action that sets all the settings
for the application at once.

The purpose of this change is that later the `SET_SETTINGS` action can
be modified to stringify all the settings and store them in a
configuration file, without having to deal with merges, conflicts, etc
(since the client application if forced to resolve those problems before
calling the `SET_SETTINGS` action.)

The behaviour of the code remains almost the same, with the exception
that the user can now set settings that we don't know about, so the user
can switch between Etcher versions without getting weird errors if one
of the configuration keys he has doesn't exist in the other version.

See: https://github.com/resin-io/etcher/pull/1382
Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-06-15 12:57:15 -04:00
Benedict Aas
ef739fb222 refactor(GUI): remove angular dependency from selection-state (#1509)
We remove the dependency on Angular from SelectionStateModel and rename
it to selectionState.
2017-06-12 08:40:52 -04:00
Benedict Aas
14b04413b5 refactor(GUI): remove angular from DrivesModel (#1265)
We remove usage of Angular from DrivesModel.

Depends: https://github.com/resin-io/etcher/pull/1261
Depends: https://github.com/resin-io/etcher/pull/1264

* remove angular injection from tests

* move file

* add empty array test
2017-06-05 10:30:58 -04:00
Benedict Aas
efa7f986a4 fix(GUI): bound flash progress percentage within 0-100 (#1258)
We limit the Store state flashing percentage to be within 0-100,
throwing errors otherwise. Comes with tests.

Changelog-Type: Bound flash progress percentage within 0-100 range.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-05-09 19:24:44 -04:00
Juan Cruz Viotti
c9e410fcb1 refactor: address file-extensions review comments (#1347)
See: https://github.com/resin-io/etcher/pull/1343#pullrequestreview-34288142
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-26 23:53:03 -04:00
Ștefan Daniel Mihăilă
1fe87d8883 feat(GUI): collect archive and image extension in analytics (#1343)
Change-Type: patch
2017-04-22 20:40:42 -04:00
Ștefan Daniel Mihăilă
9e7e5de63a feat(GUI): add uuid to flash events (#1344)
Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-22 20:40:05 -04:00
Juan Cruz Viotti
0890b1e369 refactor(GUI): move SupportedFormatsModel to lib/shared (#1251)
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-13 14:38:30 -04:00
Benedict Aas
b28c345ba0 refactor(GUI): remove Angular from FlashStateModel and rename (#1264)
We remove the usage of Angular from 'FlashStateModel' and its usage
throughout the project comes under the new 'flashState' moniker.


Depends: https://github.com/resin-io/etcher/pull/1261
2017-04-10 14:30:21 -04:00
Jonas Hermsmeier
b3b928ae4f feat(image-stream): Support Apple Disk Images (#1257)
This adds support for flashing Apple Disk Images (.dmg)

Change-Type: minor
2017-04-07 21:34:18 +02:00
Benedict Aas
ff495a45a8 refactor(GUI): remove angular from SettingsModel and rename (#1261)
We remove the usage of Angular in SettingsModel in our efforts to make
Etcher as agnostic as possible as we make our move to React.
2017-04-07 10:45:43 -04:00
Juan Cruz Viotti
802d9abb1d refactor: unify image objects across GUI and image-stream (#1229)
`image-stream` returns image objects that look like this:

```js
{
  stream: <readable stream>,
  transform: <transform stream>,
  size: {
    original: <number>,
    final: {
      value: <number>,
      estimation: <boolean>
    }
  },
  ...
}
```

While the GUI handles image objects that look like this:

```sh
{
  path: <string>,
  size: <number>,
  ...
}
```

It looks like we should share a common structure between both, so we can
use `image-stream` images in `drive-constraints`, for example.

Turns out that we actually transform `image-stream` image objects to GUI
image objects when the user selects an image using the image selector
dialog, which is another indicator that we should normalise this
situation.

As a solution, this commit does the following:

- Add `path` to `image-stream` image object
- Reuse `image-stream` image objects in the GUI, given they are a
  superset of GUI image objects

See: https://github.com/resin-io/etcher/pull/1223#discussion_r108165110
Fixes: https://github.com/resin-io/etcher/issues/1232
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-06 09:41:08 -04:00
Juan Cruz Viotti
e16234e3e8 refactor(GUI): get rid of drive-constraints Angular factory wrapper (#1250)
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-04 13:44:03 -04:00
Andrew Scheller
c9702c3a6d chore: get the CI tests working again (#1182)
* chore: get the CI tests working again
 * an updated eslint-plugin-lodash was creating extra linter errors where we
   were using built-in String methods instead of the lodash equivalents
 * an updated codespell package now installs with a different executable-name
   (no .py extension), and now supports multiple dictionaries
 * while I was at it I replaced the messy double-quoting-string logic with the
   command-join module

Changelog-type: patch

* chore: downgrade codespell to 1.9.2 because the latest version has issues running on Windows
(we'll hopefully be able to revert this commit when codespell works again properly!)
2017-03-15 16:32:25 -04:00
Juan Cruz Viotti
6c8bc117ab chore: revise ESLint built-in configuration (#1149)
There are a lot of new rules since the last time I revised the ESLint
rules documentation.

I've updated the main `.eslintrc.yml` to include some newer additions,
plus I added another ESLint configuration file inside `tests`, so we can
add some stricted rules to the production code while relaxing them for
the test suite (due to the fact that Mocha is not very ES6 friendly and
Angular tests require a bit of dark magic to setup).

This is a summary of the most important changes:

- Disallow "magic numbers"

These should now be extracted to constants, which forces us to think of
a good name for them, and thus make the code more self-documenting (I
had to Google up the meaning of some existing magic numbers, so I guess
this will be great for readability purposes).

- Require consistent `return` statements

Some functions relied on JavaScript relaxed casting mechanism to work,
which now have explicit return values. This flag also helped me detect
some promises that were not being returned, and therefore risked not
being caught by the exception handlers in case of errors.

- Disallow redefining function arguments

Immutability makes functions easier to reason about.

- Enforce JavaScript string templates instead of string concatenation

We were heavily mixing boths across the codebase.

There are some extra rules that I tweaked, however most of codebase
changes in this commit are related to the rules mentioned above.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-03-07 23:46:44 -04:00
Ștefan Daniel Mihăilă
5b9104e8aa feat(GUI): warn when atempting to burn Windows images (#1043)
Change-Type: minor
Changelog-Entry: Show warning when user tries to flash a Windows image
Closes: https://github.com/resin-io/etcher/issues/1035
2017-02-24 15:12:03 -04:00
Juan Cruz Viotti
70c7ddc52a fix(GUI): don't auto select system drives in unsafe mode
Currently Etcher will auto-select a system drive if its the only
available one when the "unsafe mode" is enabled, which doesn't feel
right.

Change-Type: patch
Changelog-Entry: Don't auto select system drives in unsafe mode.
See: https://github.com/resin-io/etcher/pull/1061
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-02-07 13:34:07 -04:00
Juan Cruz Viotti
95d4f2f608 fix(GUI): prevent flashing the drive where the source image is located
Currently Etcher will allow you to flash an image to the same drive that
contains the image. As a way to protect against that case we introduce
the concept of a "source drive", which means a drive that contains the
source image.

This commit adds the following logic around this new concept:

- Don't auto-select a source drive
- De-select an already selected drive if an image inside it is selected
- Disable the drive in the drive selector modal
- Add a "Source Drive" badge to the drive in the drive selector modal

Fixes: https://github.com/resin-io/etcher/issues/830
Change-Type: minor
Changelog-Entry: Prevent flashing the drive where the source image is located.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-02-06 14:53:08 -04:00
Juan Cruz Viotti
ea95177e94 test(GUI): fix non-running test in DrivesModel test suite (#1061)
We have a test to ensure a drive is not auto-selected when its
protected, however the real functionality of this test was not being
tested given that the size the fake drive had was not enough to hold the
image.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-01-30 10:03:24 -04:00
Juan Cruz Viotti
fd9d3ce749
Revert "refactor: remove extended archives extra functionality (#1055)"
This reverts commit b78473ea0ebd76233217fb4f236bb34be635da90.
2017-01-26 18:36:33 -04:00
Juan Cruz Viotti
b78473ea0e refactor: remove extended archives extra functionality (#1055)
We currently attempt to read extra metadata from ZIP files, such as a
logo, image name, image url, etc. In order to simplify the metadata
story, we decided that this metadata will not live on the image itself,
but rather on a centralised repo, which greatly simplified our custom
archive extraction logic and allows us to make use of these nice
features even when streaming the image directly from the internet.

We'll be working on bringing back this functionality from a centralised
repo in subsequent commits.

Change-Type: major
Changelog-Entry: Remove extended archives metadata extraction logic.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-01-26 15:21:01 -04:00
Ștefan Daniel Mihăilă
050d187e6f refactor: extract SelectionStateModel stateless functions (#982)
`SelectionStateModel`'s methods `isSystemDrive` and `isDriveLocked`
don't depend on application state. They have been extracted in a different
AngularJS service: `DriveConstraintsModel`. The new service's actual
implementation is in `lib/src`, in order to be reused by the CLI.

Miscellaneous changes:

- Rename `lib/src` to `lib/shared`
- Refactor `drive-constraints` to throw when image is undefined

The default behaviour was to pretend that we're all good if the if
the image is not specified. We're not using this "feature", and
it can be dangerous if we forget to pass in the image.

- Make `isSystemDrive` return `false` if `system` property is undefined
- Use `drive-constraints` in `store.js`

Change-Type: patch
2017-01-06 12:42:11 -04:00
Benedict Aas
d7e1fe09fc feat(gui): label system drives in drive-list widget (#978)
System drives in the drive-list widget now have a red warning label
beneath. Also new is the `isSystemDrive()` method under
`SelectionStateModel` and its unit-tests.

See: https://github.com/resin-io/etcher/issues/888
Changelog-Entry: Label system drives in the drive-list widget
2016-12-16 10:41:04 -04:00
Juan Cruz Viotti
c7f4313ded upgrade: etcher-image-stream to v5.1.0 (#752)
We're particularly interested in the following change:

- Add `etch` support.

Also:

- Add a `.description` property to all archive related errors.
- Rename archive metadata base path from `_info` to `.meta`.

Change-Type: minor
Changelog-Entry: Add support for `etch` images.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-10-07 15:32:25 -03:00
Juan Cruz Viotti
401cdb6f52 feat(GUI): image-defined recommended drive size (#703)
Recently, we've added support for a `recommendedDriveSize` property in
the `manifest.json` of extended image archives, which the image can use
to warn the user that his drive, even if it is large enough to hold the
image, might not be large enough to deliver a good usage experience
later on.

When this property is found, the GUI reacts in the following ways:

- Drives that are large enough to hold the image but don't meet the
  recommended drive size are tagged with a warning label in the drive
  selector component.

- Attempting to select a "labeled" drive opens a warning modal asking
  for user confirmation.

- Drives that don't meet the recommended drive size declared in the
  image won't get auto-selected.

- If there is a drive already selected, and the user picks an image
  whose recommended drive size is greater than the drive size, the
  currently selected drive gets auto-deselected.

Code-wise, the following significant changes have been introduced:

- Implement `SelectionStateModel.getImageRecommendedDriveSize()`.
- Implement `SelectionStateModel.isDriveSizeRecommended()`.
- Extract `WarningModal` out of the settings page (the dangerous setting
  modal).

Change-Type: minor
Changelog-Entry: Allow images to declare a recommended minimum drive size.
See: https://github.com/resin-io-modules/etcher-image-stream/pull/36
Fixes: https://github.com/resin-io/etcher/issues/698
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-14 18:06:00 -07:00
Juan Cruz Viotti
ce1c1d724d upgrade: etcher-image-write to v8.0.0 (#688)
This new version stops sending a `passedValidation` boolean property
upon completion and still throws an `EVALIDATION` error when validation
fails.

Such small chance allows us to get rid of lot of complexity related to
handling the `passedValidation` value in the application state.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-07 10:24:34 -07:00
Juan Cruz Viotti
b01ff5f0db feat(GUI): "Need help?" dynamically opens the image support url (#683)
We've recently added support for a `supportUrl` property in an archive
image `_info/manifest.json`, which the publisher can use to define the
URL where it would like to redirect users facing problems.

This PR makes the "Need help?" link at the top right corner open the
configured `supportUrl` url, and fallback to the original Etcher's help
page if no `supportUrl` is found.

In order to accomplish this, we made the following changes:

- Implement `SelectionStateModel.getImageSupportUrl()`
- Implement `HeaderController` controller

Change-Type: minor
Changelog-Entry: Make the "Need help?" link dynamically open the image support url.
Fixes: https://github.com/resin-io/etcher/issues/662
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-05 18:15:51 -07:00
Juan Cruz Viotti
da81849d4b refactor: rely on etcher-image-stream to fetch bmap contents (#654)
In order to get the bmap file contents to the Etcher CLI, we were
handling extraction, writing to a temporary file, then reading again,
and all sorts of other mumbo-jumbo, without realising that
`etcher-image-stream` already has this information right where we need
it (in the CLI's writer module) and in the way we need it (as plain
text).

Re-using from there hugely simplifies things.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-08-24 10:19:18 -04:00
Juan Cruz Viotti
a5a195e8fb feat: integrate bmap support (#635)
This PR integrates `.bmap` support recently added to
`etcher-image-write` into Etcher itself.

It does it in the following way:

- It adds a `--bmap` option to the Etcher CLI.
- It saves a potential `bmap` file contents to the
  `SelectionStateModel`.
- In the GUI, at the time of writing, if there is a `bmap` file content
  in `SelectionStateModel`, it gets written to a temporary file and such
  path is passed as the `--bmap` option to the CLI.

Since validation checksums don't make sense anymore, the finish screen
doesn't show the checksum box in this case.

Change-Type: minor
Changelog-Entry: Add `.bmap` support.
Fixes: https://github.com/resin-io/etcher/issues/171
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-08-22 09:50:56 -04:00
Juan Cruz Viotti
4b0eb8236a minifix(GUI): permit a number error code in flash results (#610)
We were currently throwing a validation error if the error code was not
string when setting the flash results, however a number error code makes
sense in some cases, and its what its returned by a `ChildProcess`
object.

Change-Type: patch
See: https://github.com/resin-io/etcher/issues/609
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-28 12:54:05 -04:00
Juan Cruz Viotti
3fc8f02611 refactor(GUI): increase encapsulation of flash results structure (#608)
Currently, the client application knows too much about how the flash
results are stored in the internal state, and relies on its structure to
perform its logic.

This PR introduces several getters to `FlashStateModel` and makes
`FlashStateModel.getFlashResults()` private, ensuring clients don't
depend on the flash results object.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-27 22:33:34 -04:00
Juan Cruz Viotti
1febeda04e refactor(GUI): extract FlashStateModel from ImageWriterService (#606)
`ImageWriterService` currently has two responsibilities. It contains
logic to start and manage a flash process, and provides an API to
interact with the current flash state.

To honour the single responsibility principle, we extract
`FlashStateModel` from `ImageWriterService`.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-27 21:21:23 -04:00
Juan Cruz Viotti
70fe34b591 minifix(GUI): deselect auto-selected drive if image is too large (#605)
We recently introduced a feature where a single available drive would be
auto-selected even if the user didn't select an image.

This introduces a potential scenario that brings the state to an
incoherent state:

- Start Etcher.
- Let a drive get auto-selected.
- Select an image larger than the auto-selected drive.

In this case, the drive is known to not have enough space to hold the
image, but remains auto-selected.

This PR makes sure the drive is deselected automatically in the above
scenario.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-27 20:37:51 -04:00