Project

General

Profile

« Previous | Next » 

Revision f0a462dc

Added by Leszek Koltunski about 16 hours ago

  • ID f0a462dc08308e17770d468f47cb8f92b1b6320e
  • Parent 1a7269af

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