commit fcf7320f9f3ba35d92b738c01615e2dd70f39301
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Jan 20 20:21:10 2022 +0100

    Progress downloading objects and extras.

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java b/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
index 655c86db..55939f2c 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
@@ -70,7 +70,7 @@ public class RubikDialogTutorialView extends FrameLayout
     {
     super(act);
 
-    InputStream jsonStream = RubikObjectList.getTutorialStream(position,act);
+    InputStream jsonStream = RubikObjectList.getExtrasStream(position,act);
     String[][] tutorials=null;
 
     if( jsonStream!=null )
diff --git a/src/main/java/org/distorted/network/RubikNetwork.java b/src/main/java/org/distorted/network/RubikNetwork.java
index a5e67a4e..283f1ea6 100644
--- a/src/main/java/org/distorted/network/RubikNetwork.java
+++ b/src/main/java/org/distorted/network/RubikNetwork.java
@@ -36,6 +36,7 @@ import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.library.main.DistortedLibrary;
+import org.distorted.objectlib.json.JsonWriter;
 import org.distorted.objects.RubikObjectList;
 
 import static org.distorted.objects.RubikObjectList.MAX_LEVEL;
@@ -112,6 +113,7 @@ public class RubikNetwork implements Runnable
   private static String mVersion;
   private static String mDebug;
   private static int mNumObjects;
+  private static RubikUpdates mUpdates;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -122,7 +124,9 @@ public class RubikNetwork implements Runnable
     if( mCountry==null || newNum!=mNumObjects ) mCountry = new String[newNum][MAX_LEVEL][MAX_PLACES];
     if( mName==null    || newNum!=mNumObjects ) mName    = new String[newNum][MAX_LEVEL][MAX_PLACES];
     if( mTime==null    || newNum!=mNumObjects ) mTime    = new  float[newNum][MAX_LEVEL][MAX_PLACES];
-    if( mPlaces==null  || newNum!=mNumObjects ) mPlaces  = new int[newNum][MAX_LEVEL];
+    if( mPlaces==null  || newNum!=mNumObjects ) mPlaces  = new    int[newNum][MAX_LEVEL];
+
+    if( mUpdates==null ) mUpdates = new RubikUpdates();
 
     mNumObjects = newNum;
     }
@@ -157,7 +161,7 @@ public class RubikNetwork implements Runnable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private boolean fillValues()
+  private boolean fillValuesNormal()
     {
     int begin=-1 ,end, len = mScores.length();
     String row;
@@ -380,7 +384,18 @@ public class RubikNetwork implements Runnable
       conn.setRequestMethod("GET");
       conn.connect();
       conn.getOutputStream().flush();
-      conn.getInputStream();
+
+      InputStream is = conn.getInputStream();
+      BufferedReader r = new BufferedReader(new InputStreamReader(is));
+      StringBuilder answer = new StringBuilder();
+
+      for (String line; (line = r.readLine()) != null; )
+        {
+        answer.append(line).append('\n');
+        }
+
+      String updates = answer.toString();
+      mUpdates.parse(updates);
       }
     catch( final Exception e )
       {
@@ -490,12 +505,15 @@ public class RubikNetwork implements Runnable
     String country = scores.getCountry();
     String renderer = DistortedLibrary.getDriverRenderer();
     String version  = DistortedLibrary.getDriverVersion();
+    int objectAPI   = JsonWriter.VERSION_OBJECT_MAJOR;
+    int tutorialAPI = JsonWriter.VERSION_EXTRAS_MAJOR;
 
     renderer = URLencode(renderer);
     version  = URLencode(version);
 
-    String url="https://distorted.org/magic/cgi-bin/debugs.cgi";
-    url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion+"d"+"&d="+renderer+"&v="+version;
+    String url="https://distorted.org/magic/cgi-bin/debugs-new.cgi";
+    url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion+"d";
+    url += "&d="+renderer+"&v="+version+"&a="+objectAPI+"&b="+tutorialAPI;
 
     return url;
     }
@@ -599,7 +617,7 @@ public class RubikNetwork implements Runnable
 
     if( mRunning )
       {
-      receiveValues = fillValues();
+      receiveValues = fillValuesNormal();
       mRunning = false;
       }
 
diff --git a/src/main/java/org/distorted/network/RubikUpdates.java b/src/main/java/org/distorted/network/RubikUpdates.java
new file mode 100644
index 00000000..b37c80c3
--- /dev/null
+++ b/src/main/java/org/distorted/network/RubikUpdates.java
@@ -0,0 +1,151 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.network;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.objects.RubikObjectList;
+
+import java.util.ArrayList;
+
+public class RubikUpdates
+{
+  public static class UpdateInfo
+    {
+    public final String mObjectName;
+    public final int mMinorVersion;
+
+    public UpdateInfo(String name,int version)
+      {
+      mObjectName  = name;
+      mMinorVersion= version;
+      }
+    }
+
+  private String mUrl;
+  private final ArrayList<UpdateInfo> mNewObjects;
+  private final ArrayList<UpdateInfo> mNewExtras;
+  private final ArrayList<UpdateInfo> mUpdObjects;
+  private final ArrayList<UpdateInfo> mUpdExtras;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikUpdates()
+    {
+    mNewObjects = new ArrayList<>();
+    mNewExtras  = new ArrayList<>();
+    mUpdObjects = new ArrayList<>();
+    mUpdExtras  = new ArrayList<>();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String debug(ArrayList<UpdateInfo> list)
+    {
+    String ret = "";
+    for( UpdateInfo info : list) ret += ("  "+info.mObjectName+" "+info.mMinorVersion);
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void parseLine(String[] elements)
+    {
+    String objName  = elements[0];
+    String objMinor = elements[1];
+    String extMinor = elements[2];
+    int oMinor, eMinor;
+
+    try { oMinor = Integer.parseInt(objMinor); }
+    catch (NumberFormatException ex) { oMinor = -1; }
+    try { eMinor = Integer.parseInt(extMinor); }
+    catch (NumberFormatException ex) { eMinor = -1; }
+
+    int objOrdinal = RubikObjectList.getOrdinal(objName.toUpperCase());
+
+    if( oMinor>=0 )
+      {
+      if( objOrdinal>=0 )
+        {
+        int localObjectMinor = RubikObjectList.getLocalObjectMinor(objOrdinal);
+        if( localObjectMinor>=0 && localObjectMinor<oMinor )
+          {
+          UpdateInfo info = new UpdateInfo(objName,oMinor);
+          mUpdObjects.add(info);
+          }
+        }
+      else
+        {
+        UpdateInfo info = new UpdateInfo(objName,oMinor);
+        mNewObjects.add(info);
+        }
+      }
+
+    if( eMinor>=0 )
+      {
+      if( objOrdinal>=0 )
+        {
+        int localExtrasMinor = RubikObjectList.getLocalExtrasMinor(objOrdinal);
+        if( localExtrasMinor>=0 && localExtrasMinor<eMinor )
+          {
+          UpdateInfo info = new UpdateInfo(objName,eMinor);
+          mUpdExtras.add(info);
+          }
+        }
+      else
+        {
+        UpdateInfo info = new UpdateInfo(objName,eMinor);
+        mNewExtras.add(info);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void parse(String updates)
+    {
+    android.util.Log.e("D", updates);
+
+    mNewObjects.clear();
+    mNewExtras.clear();
+    mUpdObjects.clear();
+    mUpdExtras.clear();
+
+    String[] lines = updates.split("\n");
+    int numLines = lines.length;
+
+    if( numLines>=1 )
+      {
+      mUrl = lines[0];
+      for(int line=1; line<numLines; line++)
+        {
+        String[] elements = lines[line].split(" ");
+        if( elements.length>=3 ) parseLine(elements);
+        }
+      }
+
+    android.util.Log.e("D", "url: "+mUrl);
+    android.util.Log.e("D", "new objects: "+debug(mNewObjects));
+    android.util.Log.e("D", "new extras : "+debug(mNewExtras ));
+    android.util.Log.e("D", "upd objects: "+debug(mUpdObjects));
+    android.util.Log.e("D", "upd extras : "+debug(mUpdExtras ));
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 6b8070d6..80261a5c 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -21,6 +21,7 @@ package org.distorted.objects;
 
 import org.distorted.dmesh.ObjectMesh;
 import org.distorted.jsons.ObjectJson;
+import org.distorted.objectlib.json.JsonWriter;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.patterns.RubikPatternList;
 
@@ -33,12 +34,13 @@ public class RubikObject
   private final String mName;
   private final int mNumScramble;
   private final int mOrdinal;
-  private final int mJsonID, mMeshID, mTutorialsID;
+  private final int mJsonID, mMeshID, mExtrasID;
   private final int mIconID;
   private final String[][] mPatterns;
 
   private int mMeshState;
-  private int mTutorialOrdinal;
+  private int mExtrasOrdinal;
+  private int mObjectMinor, mExtrasMinor;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -50,27 +52,30 @@ public class RubikObject
     mOrdinal     = type.ordinal();
     mJsonID      = ObjectJson.getObjectJsonID(mOrdinal);
     mMeshID      = ObjectMesh.getMeshID(mOrdinal);
-    mTutorialsID = ObjectJson.getTutorialJsonID(mOrdinal);
+    mExtrasID    = ObjectJson.getExtrasJsonID(mOrdinal);
 
     int patternOrdinal  = RubikPatternList.getOrdinal(mOrdinal);
     mPatterns = RubikPatternList.getPatterns(patternOrdinal);
 
     mMeshState = MESH_NICE;
-    mTutorialOrdinal = -1;
+    mExtrasOrdinal = -1;
+
+    mObjectMinor = JsonWriter.VERSION_OBJECT_MINOR;
+    mExtrasMinor = JsonWriter.VERSION_EXTRAS_MINOR;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setTutorialOrdinal(int ordinal)
+  public void setExtrasOrdinal(int ordinal)
     {
-    mTutorialOrdinal = ordinal;
+    mExtrasOrdinal = ordinal;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getTutorialOrdinal()
+  public int getExtrasOrdinal()
     {
-    return mTutorialOrdinal;
+    return mExtrasOrdinal;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -132,9 +137,9 @@ public class RubikObject
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getTutorialJsonID()
+  public int getExtrasJsonID()
     {
-    return mTutorialsID;
+    return mExtrasID;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -143,4 +148,18 @@ public class RubikObject
     {
     return mPatterns;
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectMinor()
+    {
+    return mObjectMinor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getExtrasMinor()
+    {
+    return mExtrasMinor;
+    }
 }
diff --git a/src/main/java/org/distorted/objects/RubikObjectList.java b/src/main/java/org/distorted/objects/RubikObjectList.java
index a0f1979c..50f71b47 100644
--- a/src/main/java/org/distorted/objects/RubikObjectList.java
+++ b/src/main/java/org/distorted/objects/RubikObjectList.java
@@ -45,7 +45,7 @@ public class RubikObjectList
 
   private static RubikObjectList mType;
   private static int mNumObjects;
-  private static int mNumTutorials;
+  private static int mNumExtras;
   private static ArrayList<RubikObject> mObjects;
   private static int mObject = DEF_OBJECT;
 
@@ -66,8 +66,8 @@ public class RubikObjectList
 
   private RubikObjectList()
     {
-    mNumObjects   = 0;
-    mNumTutorials = 0;
+    mNumObjects= 0;
+    mNumExtras = 0;
 
     mObjects = new ArrayList<>();
 
@@ -85,10 +85,10 @@ public class RubikObjectList
       mObjects.add(obj);
       mNumObjects++;
 
-      if( obj.getTutorialJsonID()!=0 )
+      if( obj.getExtrasJsonID()!=0 )
         {
-        obj.setTutorialOrdinal(mNumTutorials);
-        mNumTutorials++;
+        obj.setExtrasOrdinal(mNumExtras);
+        mNumExtras++;
         }
       }
     }
@@ -242,24 +242,31 @@ public class RubikObjectList
     return -1;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getNumExtrasObjects()
+    {
+    return mNumExtras;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static int getNumTutorialObjects()
     {
-    return mNumTutorials;
+    return mNumExtras;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public static InputStream getTutorialStream(int tutorialOrdinal, Activity act)
+  public static InputStream getExtrasStream(int extrasOrdinal, Activity act)
     {
-    int objectOrdinal = getObjectOrdinal(tutorialOrdinal);
+    int objectOrdinal = getObjectOrdinal(extrasOrdinal);
     RubikObject object= getObject(objectOrdinal);
 
     if( object!=null )
       {
-      int jsonID = object.getTutorialJsonID();
-      return ObjectJson.getTutorialStream(jsonID,act);
+      int jsonID = object.getExtrasJsonID();
+      return ObjectJson.getExtrasStream(jsonID,act);
       }
 
     return null;
@@ -267,23 +274,47 @@ public class RubikObjectList
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public static int getObjectOrdinal(int tutorialOrdinal)
+  public static int getObjectOrdinal(int extrasOrdinal)
     {
-    for(int i=tutorialOrdinal; i<mNumObjects; i++)
+    for(int i=extrasOrdinal; i<mNumObjects; i++)
       {
       RubikObject object = getObject(i);
-      int tutOrd = object!=null ? object.getTutorialOrdinal() : -1;
-      if( tutOrd==tutorialOrdinal ) return i;
+      int extOrd = object!=null ? object.getExtrasOrdinal() : -1;
+      if( extOrd==extrasOrdinal ) return i;
       }
 
     return -1;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getExtrasOrdinal(int objectOrdinal)
+    {
+    RubikObject object = getObject(objectOrdinal);
+    return object!=null ? object.getExtrasOrdinal() : -1;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static int getTutorialOrdinal(int objectOrdinal)
     {
     RubikObject object = getObject(objectOrdinal);
-    return object!=null ? object.getTutorialOrdinal() : -1;
+    return object!=null ? object.getExtrasOrdinal() : -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getLocalObjectMinor(int objectOrdinal)
+    {
+    RubikObject object = getObject(objectOrdinal);
+    return object!=null ? object.getObjectMinor() : -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getLocalExtrasMinor(int objectOrdinal)
+    {
+    RubikObject object = getObject(objectOrdinal);
+    return object!=null ? object.getExtrasMinor() : -1;
     }
 }
