Project

General

Profile

« Previous | Next » 

Revision f0a462dc

Added by Leszek Koltunski 24 days ago

Add version to the solves JSON and make sure that if the files is somehow corrupt, it simply gets deleted.

View differences:

src/main/java/org/distorted/helpers/RememberedSolves.java
1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2025 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is proprietary software licensed under an EULA which you should have received      //
7
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
8
///////////////////////////////////////////////////////////////////////////////////////////////////
9

  
10
package org.distorted.helpers;
11

  
12
import android.app.Activity;
13

  
14
import org.distorted.library.type.Static4D;
15
import org.distorted.main.MainObjectPopup;
16
import org.distorted.objectlib.main.TwistyObject;
17
import org.json.JSONArray;
18
import org.json.JSONException;
19
import org.json.JSONObject;
20

  
21
import java.io.BufferedReader;
22
import java.io.File;
23
import java.io.FileOutputStream;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.InputStreamReader;
27
import java.nio.charset.StandardCharsets;
28
import java.util.Locale;
29

  
30
///////////////////////////////////////////////////////////////////////////////////////////////////
31

  
32
public class RememberedSolves
33
{
34
  private static final int FILE_VERSION = 1;
35
  private static final int MAXSOLVES = 4;
36
  private static RememberedSolves mThis;
37

  
38
///////////////////////////////////////////////////////////////////////////////////////////////////
39

  
40
  private RememberedSolves()
41
    {
42

  
43
    }
44

  
45
///////////////////////////////////////////////////////////////////////////////////////////////////
46

  
47
  private String produceFilename(String objname)
48
    {
49
    return objname.toLowerCase(Locale.ENGLISH)+"_solves.json";
50
    }
51

  
52
///////////////////////////////////////////////////////////////////////////////////////////////////
53

  
54
  private String readContents(InputStream stream) throws IOException
55
    {
56
    BufferedReader br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
57
    StringBuilder contents = new StringBuilder();
58
    String tmp;
59

  
60
    while( (tmp = br.readLine()) != null) contents.append(tmp);
61
    br.close();
62
    stream.close();
63

  
64
    return contents.toString();
65
    }
66

  
67
///////////////////////////////////////////////////////////////////////////////////////////////////
68

  
69
  private JSONObject createSolveData(int elapsed, Static4D rot, TwistyObject object) throws JSONException
70
    {
71
    JSONObject data = new JSONObject();
72
    data.put("time",System.currentTimeMillis());
73
    data.put("elapsed", elapsed);
74
    data.put("rot0", rot.get0() );
75
    data.put("rot1", rot.get1() );
76
    data.put("rot2", rot.get2() );
77
    data.put("rot3", rot.get3() );
78
    JSONObject state = object.generateObjectState();
79
    data.put("objState", state);
80

  
81
    return data;
82
    }
83

  
84
///////////////////////////////////////////////////////////////////////////////////////////////////
85

  
86
  private String deleteInfo(InputStream stream, int level, long time)
87
    {
88
    try
89
      {
90
      String contents = readContents(stream);
91
      JSONObject file = new JSONObject(contents);
92
      JSONArray levels = file.getJSONArray("levels");
93
      JSONArray lvl = levels.getJSONArray(level);
94
      int numSolves = lvl.length();
95

  
96
      for(int s=0; s<numSolves; s++)
97
        {
98
        JSONObject obj = lvl.getJSONObject(s);
99
        long tm = obj.getLong("time");
100
        if( tm==time ) { lvl.remove(s); break; }
101
        }
102

  
103
      return file.toString();
104
      }
105
    catch(IOException iex)    { android.util.Log.e("D", "addInfo: failed to read file: "+iex.getMessage() ); }
106
    catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
107

  
108
    return null;
109
    }
110

  
111
///////////////////////////////////////////////////////////////////////////////////////////////////
112

  
113
  private String addInfo(InputStream stream, int level, int elapsed, Static4D rot, TwistyObject object)
114
    {
115
    if( stream!=null )
116
      {
117
      try
118
        {
119
        String contents = readContents(stream);
120
        JSONObject file = new JSONObject(contents);
121
        JSONArray levels = file.getJSONArray("levels");
122
        JSONArray lvl = levels.getJSONArray(level);
123
        JSONObject data = createSolveData(elapsed,rot,object);
124
        if( lvl.length()>=MAXSOLVES || level==0) lvl.remove(0);
125
        lvl.put(data);
126
        return file.toString();
127
        }
128
      catch(IOException iex)    { android.util.Log.e("D", "addInfo: failed to read file: "+iex.getMessage() ); }
129
      catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
130
      }
131
    else
132
      {
133
      JSONObject file = new JSONObject();
134
      JSONArray levels = new JSONArray();
135

  
136
      try
137
        {
138
        for(int l=0; l<MainObjectPopup.LEVELS_SHOWN+1; l++)
139
          {
140
          JSONArray save = new JSONArray();
141

  
142
          if( l==level )
143
            {
144
            JSONObject data = createSolveData(elapsed,rot,object);
145
            save.put(data);
146
            }
147
          levels.put(save);
148
          }
149

  
150
        file.put("version",FILE_VERSION);
151
        file.put("levels",levels);
152
        }
153
      catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
154

  
155
      return file.toString();
156
      }
157

  
158
    return null;
159
    }
160

  
161
///////////////////////////////////////////////////////////////////////////////////////////////////
162
// PUBLIC API
163
///////////////////////////////////////////////////////////////////////////////////////////////////
164

  
165
  public static RememberedSolves getInstance()
166
    {
167
    if( mThis==null ) mThis = new RememberedSolves();
168
    return mThis;
169
    }
170

  
171
///////////////////////////////////////////////////////////////////////////////////////////////////
172

  
173
  public long getTime(JSONObject object) throws JSONException
174
    {
175
    return object.getLong("time");
176
    }
177

  
178
///////////////////////////////////////////////////////////////////////////////////////////////////
179

  
180
  public int getElapsed(JSONObject object) throws JSONException
181
    {
182
    return object.getInt("elapsed");
183
    }
184

  
185
///////////////////////////////////////////////////////////////////////////////////////////////////
186

  
187
  public Static4D getRotQuat(JSONObject object) throws JSONException
188
    {
189
    float r0 = (float)object.getDouble("rot0");
190
    float r1 = (float)object.getDouble("rot1");
191
    float r2 = (float)object.getDouble("rot2");
192
    float r3 = (float)object.getDouble("rot3");
193

  
194
    return new Static4D(r0,r1,r2,r3);
195
    }
196

  
197
///////////////////////////////////////////////////////////////////////////////////////////////////
198

  
199
  public JSONObject getObjectState(JSONObject object) throws JSONException
200
    {
201
    return object.getJSONObject("objState");
202
    }
203

  
204
///////////////////////////////////////////////////////////////////////////////////////////////////
205

  
206
  public JSONArray getLevelsArray(Activity act, String objname)
207
    {
208
    String filename  = produceFilename(objname);
209
    RubikFiles files = RubikFiles.getInstance();
210
    InputStream file = files.openFile(act,filename);
211

  
212
    if( file==null ) return null;
213

  
214
    try
215
      {
216
      String contents = readContents(file);
217
      JSONObject f = new JSONObject(contents);
218
      return f.getJSONArray("levels");
219
      }
220
    catch(IOException iex)
221
      {
222
      android.util.Log.e("D", "readFile: failed to read file: "+iex.getMessage() );
223
      files.deleteFile(act,filename);
224
      }
225
    catch(JSONException jex)
226
      {
227
      android.util.Log.e("D", "readFile: failed to parse file: "+jex.getMessage());
228
      files.deleteFile(act,filename);
229
      }
230

  
231
    return null;
232
    }
233

  
234
///////////////////////////////////////////////////////////////////////////////////////////////////
235

  
236
  public void deleteFile(Activity act, String objname)
237
    {
238
    String filename  = produceFilename(objname);
239
    RubikFiles files = RubikFiles.getInstance();
240
    files.deleteFile(act,filename);
241
    }
242

  
243
///////////////////////////////////////////////////////////////////////////////////////////////////
244

  
245
  public void deleteSolve(Activity act, String objname, int level, long time)
246
    {
247
    String filename  = produceFilename(objname);
248
    RubikFiles files = RubikFiles.getInstance();
249
    InputStream input= files.openFile(act,filename);
250

  
251
    if( input!=null )
252
      {
253
      String contents= deleteInfo(input,level,time);
254

  
255
      if( contents!=null )
256
        {
257
        File file = new File(act.getFilesDir(), filename);
258

  
259
        try( FileOutputStream fos = new FileOutputStream(file) )
260
          {
261
          fos.write(contents.getBytes(StandardCharsets.UTF_8));
262
          }
263
        catch(IOException ex)
264
          {
265
          android.util.Log.e("D", "deleteSolve: failed to save file "+filename+" : "+ex.getMessage());
266
          }
267
        }
268
      }
269
    }
270

  
271
///////////////////////////////////////////////////////////////////////////////////////////////////
272

  
273
  public void rememberSolve(Activity act, String objname, int level, int elapsed, Static4D rot, TwistyObject object)
274
    {
275
    String filename  = produceFilename(objname);
276
    RubikFiles files = RubikFiles.getInstance();
277
    InputStream input= files.openFile(act,filename);
278
    String contents  = addInfo(input,level,elapsed,rot,object);
279

  
280
    if( contents!=null )
281
      {
282
      File file = new File(act.getFilesDir(), filename);
283

  
284
      try( FileOutputStream fos = new FileOutputStream(file) )
285
        {
286
        fos.write(contents.getBytes(StandardCharsets.UTF_8));
287
        }
288
      catch(IOException ex)
289
        {
290
        android.util.Log.e("D", "rememberSolve: failed to save file "+filename+" : "+ex.getMessage());
291
        }
292
      }
293
    }
294
}
src/main/java/org/distorted/helpers/RubikRememberedSolves.java
1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2025 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is proprietary software licensed under an EULA which you should have received      //
7
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
8
///////////////////////////////////////////////////////////////////////////////////////////////////
9

  
10
package org.distorted.helpers;
11

  
12
import android.app.Activity;
13

  
14
import org.distorted.library.type.Static4D;
15
import org.distorted.main.MainObjectPopup;
16
import org.distorted.objectlib.main.TwistyObject;
17
import org.json.JSONArray;
18
import org.json.JSONException;
19
import org.json.JSONObject;
20

  
21
import java.io.BufferedReader;
22
import java.io.File;
23
import java.io.FileOutputStream;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.InputStreamReader;
27
import java.nio.charset.StandardCharsets;
28
import java.util.Locale;
29

  
30
///////////////////////////////////////////////////////////////////////////////////////////////////
31

  
32
public class RubikRememberedSolves
33
{
34
  private static final int MAXSOLVES = 4;
35
  private static RubikRememberedSolves mThis;
36

  
37
///////////////////////////////////////////////////////////////////////////////////////////////////
38

  
39
  private RubikRememberedSolves()
40
    {
41

  
42
    }
43

  
44
///////////////////////////////////////////////////////////////////////////////////////////////////
45

  
46
  private String produceFilename(String objname)
47
    {
48
    return objname.toLowerCase(Locale.ENGLISH)+"_solves.json";
49
    }
50

  
51
///////////////////////////////////////////////////////////////////////////////////////////////////
52

  
53
  private String readContents(InputStream stream) throws IOException
54
    {
55
    BufferedReader br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
56
    StringBuilder contents = new StringBuilder();
57
    String tmp;
58

  
59
    while( (tmp = br.readLine()) != null) contents.append(tmp);
60
    br.close();
61
    stream.close();
62

  
63
    return contents.toString();
64
    }
65

  
66
///////////////////////////////////////////////////////////////////////////////////////////////////
67

  
68
  private JSONObject createData(int elapsed, Static4D rot, TwistyObject object) throws JSONException
69
    {
70
    JSONObject data = new JSONObject();
71
    data.put("time",System.currentTimeMillis());
72
    data.put("elapsed", elapsed);
73
    data.put("rot0", rot.get0() );
74
    data.put("rot1", rot.get1() );
75
    data.put("rot2", rot.get2() );
76
    data.put("rot3", rot.get3() );
77
    JSONObject state = object.generateObjectState();
78
    data.put("objState", state);
79

  
80
    return data;
81
    }
82

  
83
///////////////////////////////////////////////////////////////////////////////////////////////////
84

  
85
  private String deleteInfo(InputStream stream, int level, long time)
86
    {
87
    try
88
      {
89
      String contents = readContents(stream);
90
      JSONArray levels = new JSONArray(contents);
91
      JSONArray lvl = levels.getJSONArray(level);
92
      int numSolves = lvl.length();
93

  
94
      for(int s=0; s<numSolves; s++)
95
        {
96
        JSONObject obj = lvl.getJSONObject(s);
97
        long tm = obj.getLong("time");
98
        if( tm==time ) { lvl.remove(s); break; }
99
        }
100

  
101
      return levels.toString();
102
      }
103
    catch(IOException iex)    { android.util.Log.e("D", "addInfo: failed to read file: "+iex.getMessage() ); }
104
    catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
105

  
106
    return null;
107
    }
108

  
109
///////////////////////////////////////////////////////////////////////////////////////////////////
110

  
111
  private String addInfo(InputStream stream, int level, int elapsed, Static4D rot, TwistyObject object)
112
    {
113
    if( stream!=null )
114
      {
115
      try
116
        {
117
        String contents = readContents(stream);
118
        JSONArray levels = new JSONArray(contents);
119
        JSONArray lvl = levels.getJSONArray(level);
120
        JSONObject data = createData(elapsed,rot,object);
121
        if( lvl.length()>=MAXSOLVES || level==0) lvl.remove(0);
122
        lvl.put(data);
123
        return levels.toString();
124
        }
125
      catch(IOException iex)    { android.util.Log.e("D", "addInfo: failed to read file: "+iex.getMessage() ); }
126
      catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
127
      }
128
    else
129
      {
130
      JSONArray levels = new JSONArray();
131

  
132
      try
133
        {
134
        for(int l=0; l< MainObjectPopup.LEVELS_SHOWN+1; l++)
135
          {
136
          JSONArray save = new JSONArray();
137

  
138
          if( l==level )
139
            {
140
            JSONObject data = createData(elapsed,rot,object);
141
            save.put(data);
142
            }
143
          levels.put(save);
144
          }
145
        }
146
      catch(JSONException jex)  { android.util.Log.e("D", "addInfo: failed to parse file: "+jex.getMessage()); }
147

  
148
      return levels.toString();
149
      }
150

  
151
    return null;
152
    }
153

  
154
///////////////////////////////////////////////////////////////////////////////////////////////////
155
// PUBLIC API
156
///////////////////////////////////////////////////////////////////////////////////////////////////
157

  
158
  public static RubikRememberedSolves getInstance()
159
    {
160
    if( mThis==null ) mThis = new RubikRememberedSolves();
161
    return mThis;
162
    }
163

  
164
///////////////////////////////////////////////////////////////////////////////////////////////////
165

  
166
  public long getTime(JSONObject object) throws JSONException
167
    {
168
    return object.getLong("time");
169
    }
170

  
171
///////////////////////////////////////////////////////////////////////////////////////////////////
172

  
173
  public int getElapsed(JSONObject object) throws JSONException
174
    {
175
    return object.getInt("elapsed");
176
    }
177

  
178
///////////////////////////////////////////////////////////////////////////////////////////////////
179

  
180
  public Static4D getRotQuat(JSONObject object) throws JSONException
181
    {
182
    float r0 = (float)object.getDouble("rot0");
183
    float r1 = (float)object.getDouble("rot1");
184
    float r2 = (float)object.getDouble("rot2");
185
    float r3 = (float)object.getDouble("rot3");
186

  
187
    return new Static4D(r0,r1,r2,r3);
188
    }
189

  
190
///////////////////////////////////////////////////////////////////////////////////////////////////
191

  
192
  public JSONObject getObjectState(JSONObject object) throws JSONException
193
    {
194
    return object.getJSONObject("objState");
195
    }
196

  
197
///////////////////////////////////////////////////////////////////////////////////////////////////
198

  
199
  public JSONArray readFile(Activity act, String objname)
200
    {
201
    RubikFiles files = RubikFiles.getInstance();
202
    InputStream file = files.openFile(act,produceFilename(objname));
203

  
204
    if( file==null ) return null;
205

  
206
    try
207
      {
208
      String contents = readContents(file);
209
      return new JSONArray(contents);
210
      }
211
    catch(IOException iex)    { android.util.Log.e("D", "readFile: failed to read file: "+iex.getMessage() ); }
212
    catch(JSONException jex)  { android.util.Log.e("D", "readFile: failed to parse file: "+jex.getMessage()); }
213

  
214
    return null;
215
    }
216

  
217
///////////////////////////////////////////////////////////////////////////////////////////////////
218

  
219
  public void deleteFile(Activity act, String objname)
220
    {
221
    String filename  = produceFilename(objname);
222
    RubikFiles files = RubikFiles.getInstance();
223
    files.deleteFile(act,filename);
224
    }
225

  
226
///////////////////////////////////////////////////////////////////////////////////////////////////
227

  
228
  public void deleteSolve(Activity act, String objname, int level, long time)
229
    {
230
    String filename  = produceFilename(objname);
231
    RubikFiles files = RubikFiles.getInstance();
232
    InputStream input= files.openFile(act,filename);
233

  
234
    if( input!=null )
235
      {
236
      String contents= deleteInfo(input,level,time);
237
      File file      = new File(act.getFilesDir(), filename);
238

  
239
      try( FileOutputStream fos = new FileOutputStream(file) )
240
        {
241
        fos.write(contents.getBytes(StandardCharsets.UTF_8));
242
        }
243
      catch(IOException ex)
244
        {
245
        android.util.Log.e("D", "deleteSolve: failed to save file "+filename+" : "+ex.getMessage());
246
        }
247
      }
248
    else
249
      {
250
      android.util.Log.e("D", "deleteSolve: error: file "+filename+" not found");
251
      }
252
    }
253

  
254
///////////////////////////////////////////////////////////////////////////////////////////////////
255

  
256
  public void rememberSolve(Activity act, String objname, int level, int elapsed, Static4D rot, TwistyObject object)
257
    {
258
    String filename  = produceFilename(objname);
259
    RubikFiles files = RubikFiles.getInstance();
260
    InputStream input= files.openFile(act,filename);
261
    String contents  = addInfo(input,level,elapsed,rot,object);
262
    File file        = new File(act.getFilesDir(), filename);
263

  
264
    try( FileOutputStream fos = new FileOutputStream(file) )
265
      {
266
      fos.write(contents.getBytes(StandardCharsets.UTF_8));
267
      }
268
    catch(IOException ex)
269
      {
270
      android.util.Log.e("D", "rememberSolve: failed to save file "+filename+" : "+ex.getMessage());
271
      }
272
    }
273
}
src/main/java/org/distorted/main/MainObjectPopup.java
26 26

  
27 27
import androidx.annotation.NonNull;
28 28

  
29
import org.distorted.helpers.RubikRememberedSolves;
29
import org.distorted.helpers.RememberedSolves;
30 30
import org.distorted.helpers.RubikScores;
31 31
import org.distorted.library.type.Static4D;
32 32
import org.distorted.objects.RubikObject;
......
82 82
      {
83 83
      public void run()
84 84
        {
85
        RubikRememberedSolves solves = RubikRememberedSolves.getInstance();
86
        mRememberedSolves = solves.readFile(act,objname);
85
        RememberedSolves solves = RememberedSolves.getInstance();
86
        mRememberedSolves = solves.getLevelsArray(act,objname);
87 87
        }
88 88
      };
89 89

  
......
296 296
            {
297 297
            if( numSolves>0 )
298 298
              {
299
              RubikRememberedSolves solves = RubikRememberedSolves.getInstance();
299
              RememberedSolves solves = RememberedSolves.getInstance();
300 300

  
301 301
              try
302 302
                {
src/main/java/org/distorted/main/MainSolvesPopup.java
21 21
import android.widget.PopupWindow;
22 22
import android.widget.TextView;
23 23

  
24
import org.distorted.helpers.RubikRememberedSolves;
24
import org.distorted.helpers.RememberedSolves;
25 25
import org.distorted.library.type.Static4D;
26 26
import org.distorted.objects.RubikObject;
27 27
import org.json.JSONArray;
......
80 80
    LinearLayout.LayoutParams pT = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, textH );
81 81

  
82 82
    int numSolves = array.length();
83
    RubikRememberedSolves solves = RubikRememberedSolves.getInstance();
83
    RememberedSolves solves = RememberedSolves.getInstance();
84 84

  
85 85
    try
86 86
      {
......
154 154
          {
155 155
          mLayout.removeView(view);
156 156
          int level = mLevel==LEVELS_SHOWN ? mLevel : mLevel+1;
157
          RubikRememberedSolves solves = RubikRememberedSolves.getInstance();
157
          RememberedSolves solves = RememberedSolves.getInstance();
158 158
          solves.deleteSolve(act, mObject.getLowerName(), level, time);
159 159
          }
160 160
        });
src/main/java/org/distorted/play/PlayActivity.java
27 27

  
28 28
import org.distorted.dialogs.DialogScores;
29 29
import org.distorted.helpers.BaseActivity;
30
import org.distorted.helpers.RubikRememberedSolves;
30
import org.distorted.helpers.RememberedSolves;
31 31
import org.distorted.library.main.DistortedLibrary;
32 32
import org.distorted.library.type.Static4D;
33 33
import org.distorted.objectlib.main.InitAssets;
......
369 369
        {
370 370
        public void run()
371 371
          {
372
          RubikRememberedSolves solves = RubikRememberedSolves.getInstance();
372
          RememberedSolves solves = RememberedSolves.getInstance();
373 373
          solves.rememberSolve(act,name,level,time,rotQuat,object);
374 374
          }
375 375
        };

Also available in: Unified diff