Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic Spoiler Season #2991

Merged
merged 29 commits into from
Jan 10, 2018
Merged

Automatic Spoiler Season #2991

merged 29 commits into from
Jan 10, 2018

Conversation

ZeldaZach
Copy link
Member

@ZeldaZach ZeldaZach commented Jan 4, 2018

This PR contains a lot, so lets try and break it down a bit.

The goal of this PR is to allow users to automatically download and manage their spoilers through our spoiler service, Magic-Spoiler. In order to accomplish this, I've added components to the settings dialog.

screenshot 2018-01-08 23 52 23

If the user ticks the "Download Spoilers Automatically" button, the following gears internally will start turning:

  • On Cockatrice launch, check if the file "SpoilerSeasonEnabled" exists on the Magic-Spoiler repo. If it does not, then we know we are not in spoiler season and will continue with normal launch.

  • If the file does exist, then we will continue and now download spoiler.xml from the Magic-Spoiler repo. The system will then check if the downloaded copy matches the current local copy. If there is no local copy or the file hashes do not match, then we will replace the local copy with the downloaded copy and alert the user.

screenshot 2018-01-08 23 58 02

  • If the file hashes do match, then we know the spoilers are up to date and we discard the downloaded version and alert the user.

screenshot 2018-01-08 23 56 18

  • If the user presses the "Update Spoilers" button in the system settings, it will trigger bullet point 1 above with the SpoilerSeasonEnabled check and continue down the line.

  • If any errors occur, they will be properly logged in the debugger.


In addition to the changes above, there is some general cleanup in the code base (as dictated by CLion's strict IDE policy of ensuring top quality code). Some examples include adding explicit to functions or overrides. In addition, there are some bracket realignments and header realignments to meet the new coding policy (which will be updated in the near future).

Continuing on, the first attempt of this PR used Oracle to download the spoilers. Since I find that code to be useful, I have decided to leave it in the PR as an Easter Egg so it can be used in the future by another process (who knows, maybe we'll want to have Oracle do new things, and this is a great example of what it is capable of!)

Finally, I detected a race-time condition that can come about if the system attempts to reload the card database while a reload is currently in progress. I have added a Mutex to make the card database reload thread safe from now on.

@ZeldaZach ZeldaZach changed the title Spoiler Season via Oracle Automatic Spoiler Season Jan 6, 2018
@dev-id
Copy link
Contributor

dev-id commented Jan 6, 2018

Tested and working on Windows 7 (portable install)

Looks good. Finally a way to update spoilers automatically. Obsoletes a lot of #2764 . Somewhat handles #1874

@tooomm
Copy link
Member

tooomm commented Jan 7, 2018

As said in gitter, Cockatrice crashes immediately after/during updating on my Windows 10.
Again: Experienced it after the spoiler set was the only enabled set, then tried again with spoiler (RIX) plus Ixalan with same result, it crashes shortly after the app starts.
There is no way to stop the update process to eventually prevent a crash and disable the setting for example. One can't open Edit sets... and disconnecting from the internet also doesn't help.

I need to retest with a clean install and fresh settings now.

Useless Windows Crash Log

@@ -3,6 +3,7 @@
#include <QIcon>
#include <QTranslator>
#include <QLibraryInfo>
#include <QCommandLineParser>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaving the changes in Oracle as an Easter egg that can be used in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the first attempt of this PR used Oracle to download the spoilers. Since I find that code to be useful, I have decided to leave it in the PR as an Easter Egg so it can be used in the future by another process (who knows, maybe we'll want to have Oracle do new things, and this is a great example of what it is capable of!)

If I understand it correctly you added unused code, shouldn't you comment that accordingly in the code at least?
The reference in this pr will be forgotten and the next coder will wonder. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's nothing to comment really. It's fairly obvious this code isn't used (as there are no calls to it).

@tooomm
Copy link
Member

tooomm commented Jan 9, 2018

I really like that we have something working like this now 👍

Here is my list of thoughts:

  • Cockatrice should handle the key file SpoilerSeasonStatus beeing non-existent/deleted or simply not available due to whatever reason.

  • What happens when the same set/cards appears in spoiler.xml and mtgjson (->cards.xml)? This could happen during a short overlap or if there are other circumstances and should be covered!

  • I like this to be a bit smarter.
    It always takes the newest file - even with no changes - and replaces the old. It'll also notifies you falsely about a successful update and new available spoilers.
    For example: Right now everything for RIX is spoiled since a few days, just very few very tiny updates will happen. But Cockatrice doesn't know, so each and every launch (can be several times a day too!), and for like 10 days in a row the spoiler updater will do it's work, and show a useless message to the user. Actually the message is wrong in the sense that it says "Spoilers updated!" which is wrong most of the time.
    The date could give you a hint, but it's more likely to be just confusing. Anyway, I only want notifications if they tell me something new/useful. ;)

    How it could work imo
    During season, on launch:

    • Check timestamp
      (from the fresh file against a value in settings, because a user could mess with the local file)
      • New
        as is now
      • Same timestamp = no update available
        Tell the user that there are no new spoiler updates available (notification)
        (It could also be no notification, but then we need to educate the user in the "settings", otherwise they could think something is not working. Also, if there is no notification, users might think the Update Spoilers button in settings isn't working, or that needs to always trigger something)

  • The notification could be a bit more helpful and explaining to the user.
    Not only is the timestamp in its raw form a bit cryptic, but there is room for interpretation and misunderstanding. Take the user by their hand and guide them. :)

If you add a hint and explain the data a bit it's more helpful to everybody.
Discarding some less useful information like day, year and seconds help too:
(overall size is actually ~ the same)

Spoilers have been updated!
Last change: Jan 06, 21:53 (UTC)

Original message:

Spoilers have been updated!
Sat, Jan 06 2018, 21:53:13 (UTC)

automatic spoiler season notification


  • Are the spoiler 100% ready for this and can we guarantee data reliably? @dev-id
    Maybe we should get scryfall going as default first: Migrate to Scryfall as primary data source Magic-Spoiler#170 (main parts)
    Their quality and speed is nice, also updating is really incredible. Within a very short time (can be 30min), if you use the on-page reporting directly, they fix typos etc.! Just experienced that yesterday again.

Design: (I'm sorry, but we never spoke about it before)

  • I don't like the extra file this is dependent on and that needs to be maintained additionally, maybe we can automate that somehow @ spoiler project, though.

  • Did you think about using "file existence" rather than "file content"?
    File name could be SpoilerSeasonEnabled or SpoilerActive instead some specific content of a SpoilerSeasonStatus file. That seems more straight forward and simpler at first. Now you have to download the season file all the time, maybe just checking a file location for its existence is better and faster. At least it would require less data transfer. :)
    A file content might be easier to update manually, but we are definitely aiming at an automation here. And then file creation/deletion should be simpler than changing content of a existing file. We didn't investigated into that yet at spoiler repo for sure.

  • Why don't we use github releases as a source instead the workaround with hosting output files for downloading in a branch? That's really not ideal.
    Releases would allow to check for a change date before downloading the file via API too. So you could check all year for changes with no effort and we wouldn't need the additional SpoilerSeasonStatus content to check against at all. That sounds more straight forward to me. Also easier to maintain and no shenanigans for automation needed.
    Actually, we could do a date check with your design as well. Checking against the timestamp in spoiler.xml all the time. During season it is downloaded anyway, and in off-season the difference in size for an spoiler.xml with no cards and the SpoilerSeasonStatus file can't be that big. But using proper releases as a download source and the API should be a more mature, future proof solution with several additions and more possibilities. We have good experiences with it for app updates already!

  • What about our former spoiler handling?
    We even included special code for spoilers in custom sets: Import spoiler.xml as spoiler.xml and overwrite existing #2784
    That is no longer needed and can be removed I guess?

I retested with the latest commit btw and the reload problem is now also covered.


setLayout(mainLayout);
auto *lpGeneralGrid = new QGridLayout;
auto *lpSpoilerGrid = new QGridLayout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All GeneralGrid could be removed maybe?
It has been empty for a long time now, and most likely will for some more... I don't know of any feature in the planning that could end up here!

And it looks not really nice 😄
settings

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see harm in leaving it - let's not increase the amount of stuff being changed since this is already large

@ZeldaZach
Copy link
Member Author

ZeldaZach commented Jan 9, 2018

@tooomm

I will respond to your bullets with my own bullets.

  • Implemented. It will now check for file SpoilerSeasonEnabled on the Magic-Spoiler repo. If this file is found, spoiler season code will execute, otherwise it will not. The content of the file does not matter.

  • If spoiler season is disabled, the spoiler.xml file will be removed from the user system. Users are not to modify this file (as it says so in the file header) so it is our right to delete it. This will help avoid the conflict with Oracle when it has the spoilers on MTGJSON.

  • If spoiler season is enabled, the downloaded spoiler.xml file will have headers compared with the local version. If they match, then a popup will say "Spoilers up to date". If the user hits the update button, they will get the same response (provided they didn't delete their local copy before pressing the button).

  • Notification changed to say "Last changed" with the date stamp.

Design Bullets 1 & 2 have been address. For 3 & 4, I'll wait to see what others think

@Cockatrice Cockatrice deleted a comment from Rendroc Jan 9, 2018
@Daenyth
Copy link
Member

Daenyth commented Jan 9, 2018

The description in the main pr comment sounds like an excellent workflow. I might offer a tip that on error we could pop up a notification that instructs the user to check the debug log, but that's minor - overall the workflow sounds good now.

Copy link
Member

@Daenyth Daenyth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really awesome work Zach!

#include <QMessageBox>

const int CardDatabase::versionNeeded = 3;
const char* CardDatabase::TOKENS_SETNAME = "TK";

static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSet *set)
{
if (! set)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explicitly check nullptr here rather than the implicit style please? It helps with clarity and intent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And further down in the file also

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'll fix that up

}
}

static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
{
if (! info)
{
qDebug() << "operator<< (~376) info is nullptr";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is 376?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was referring to the line number, i'll be more explicit


const QList<CardRelation *> related = info->getRelatedCards();
for (int i = 0; i < related.size(); i++) {

for (auto i : related)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -124,12 +123,12 @@ DlgConnect::DlgConnect(QWidget *parent)
QGroupBox *btnGroupBox = new QGroupBox(tr(""));
btnGroupBox->setLayout(buttons);

QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see the point in auto-fying these but I guess it doesn't hurt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLion says auto is better and more "modern" so I'm listening to it and fixing all the "warnings" i come across


setLayout(mainLayout);
auto *lpGeneralGrid = new QGridLayout;
auto *lpSpoilerGrid = new QGridLayout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see harm in leaving it - let's not increase the amount of stuff being changed since this is already large

// Delete the spoiler.xml file as we're not in spoiler season
if (file.exists() && file.remove())
{
qDebug() << "Spoiler Season Offline, Deleting spoiler.xml";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also pop a desktop notification telling the user the spoiler file is being removed and they need to run oracle? Otherwise checking for updates will remove cards from their db and leave them confused

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, yeah I can do that


/*
* ALERT: Ensure two reloads of the card database do not happen
* at the same time or a racetime condition can/will happen!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have a mutex around db reloading to avoid that, but maybe in another pr unless you feel up to adding that. Might need refactoring

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I'm going to hold off on that so we can get this PR in and i'll start working on it later.

}

// Check if the data matches. If it does, then spoilers are up to date.
if (getHash(fileName) == getHash(data))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you hashing the file name or the file contents? Maybe include the individual hash values in a qDebug?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm I see it's overloaded. That works

if (file.write(data) == -1)
{
qDebug() << "Spoiler Service Error: File write (w) failed for" << fileName;
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file handle won't be closed in this branch I think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never has been, but I can make it close

if (file.open(QFile::ReadOnly))
{
// Only read the first 512 bytes (enough to get the "created" tag)
const QByteArray bytes = file.read(512);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth qDebugging this value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty spoiler.xml file looks like this: https://github.com/Cockatrice/Magic-Spoiler/blob/77ec169ec5ec36352d02db8ab1137a780ba7133e/spoiler.xml

It has 272 bytes, so only checking first 256bytes instead 512 is sufficient?
Adding a do-not-edit hint as requested by you, won't change too much since it could simply be placed behind the timestamp.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tooomm I read the files and put it through systems to get byte counts. Most services tell me i'll need ~400 bytes to be certain to capture the date. As such, I put a power of 2 in place. 512 bytes isn't that much 😄

@tooomm
Copy link
Member

tooomm commented Jan 9, 2018

Finished testing the second last iteration (c70dfc3)

Further thoughts:

  • New notification body is to long on windows and wraps around the line end:
    updates available msg
    The "nothing available" one is good:
    no updates available msg
    Can't you tweak the timestamp as suggested earlier?
    Maybe you want to check out QDateTime and a simple fromString --> toString (or some other smarter way) will do the trick to convert from
    ddd, MMM dd yyyy, hh:mm:ss to simpler+shorter MMM d, hh:mm
    Also, the notification is only displayed for 3s which is hardly enough to understand all numbers with the linebreak etc.

  • In settings, having the last run and/or last changes date/time could be helpful, too

  • If you disable spoiler in menu, the spoiler database file should be deleted as well

  • Good thing is spoiler.xml seems to not get deleted if there is simply no connection. 👍
    But it still shows Spoiler Service Online in Debug Log which is false information. We just don't know because trice can't check when there is a network problem.
    Simply assuming it is all up and running and telling the user so in logs isn't ideal.
    Also, once there is a network issue or no internet connection, the notification will still pop up and tell the user that Spoilers are up to date which is misleading and probably wrong.

  • Run into another issue/false error:
    If you go to settings and hit Update Spoiler and close the settings shortly after after it still seems to check+inform you, but you'll see an error about database load and if you want to change its location (Hitting Yes here will do nothing btw):
    false error

The updated pr description is very helpful and detailed btw 👍

@Daenyth
Copy link
Member

Daenyth commented Jan 9, 2018

That last error is kind of concerning. Can you reproduce it reliably?

@ZeldaZach
Copy link
Member Author

ZeldaZach commented Jan 10, 2018

@tooomm I've resolved all bullets and have made the reload thread safe.

The last error is no longer an issue based on the refactoring I just completed.

@tooomm
Copy link
Member

tooomm commented Jan 10, 2018

Nice, thank you!


The last error is no longer an issue based on the refactoring I just completed.

Sadly I can still reproduce the error the same as before.
db error settings

And yes @Daenyth, it can easily and 100% reliable be reproduced:
Go to Settings, switch to Deck Editor submenu, hit Update Spoiler, close Settings again (hit Ok or close the settings dialog). The most normal workflow if you want to update spoilers without restarting the app. Even if the notification appears and you close then it'll pop up.
You literally have to wait until all the spoiler/db stuff silently finished in the background - let's say 5s. But you don't know when the hidden update is finished... simply waiting for the notification isn't enough for me.
Anyway, it's hard for users to understand that, and what's going on. So trouble is sure to follow. :(
The msg content and the question if you want to change your database location is not really related too.

Copy link
Member

@tooomm tooomm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button disable and its label change is an improved indicator that it's not done yet and something still happening in the background, I like that.

Overall it's a great PR with an awesome feature. 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants