Added persistence of ComponentPresets in the ORK file. Bumped ORK file version number to 1.5 when saving designs containing ComponentPresets. Added a digest string to ComponentPreset which is used during reading and writing ORK files to verify the correct ComponentPreset is used.
This commit is contained in:
		
							parent
							
								
									68eaf21ef0
								
							
						
					
					
						commit
						edcd0008b1
					
				| @ -38,3 +38,5 @@ The following file format versions exist: | ||||
|       deploymentvelocity attributes to <flightdata> element.  The motor | ||||
|       digesting algorithm was changed.  Adds <separationevent> and | ||||
|       <separationdelay> elements to stage components (except sustainer). | ||||
| 
 | ||||
| 1.5:  Introduced with OpenRocket 12.xx.  Added ComponentPresets. | ||||
| @ -26,4 +26,6 @@ public interface ComponentPresetDao { | ||||
| 
 | ||||
| 	public void setFavorite( ComponentPreset preset, boolean favorite ); | ||||
| 	 | ||||
| 	public List<ComponentPreset> find( String manufacturer, String partNo ); | ||||
| 	 | ||||
| } | ||||
| @ -129,6 +129,17 @@ public class ComponentPresetDatabase extends Database<ComponentPreset> implement | ||||
| 		return result; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public List<ComponentPreset> find(String manufacturer, String partNo) { | ||||
| 		List<ComponentPreset> presets = new ArrayList<ComponentPreset>(); | ||||
| 		for( ComponentPreset preset : list ) { | ||||
| 			if ( preset.getManufacturer().getSimpleName().equals(manufacturer) && preset.getPartNo().equals(partNo) ) { | ||||
| 				presets.add(preset); | ||||
| 			} | ||||
| 		} | ||||
| 		return presets; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void setFavorite( ComponentPreset preset, boolean favorite ) { | ||||
| 		preset.setFavorite(favorite); | ||||
|  | ||||
| @ -184,6 +184,9 @@ public class OpenRocketSaver extends RocketSaver { | ||||
| 	 */ | ||||
| 	private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) { | ||||
| 		/* | ||||
| 		 * File version 1.5 is requires for: | ||||
| 		 *  - saving designs using ComponentPrests | ||||
| 		 *   | ||||
| 		 * File version 1.4 is required for: | ||||
| 		 *  - saving simulation data | ||||
| 		 *  - saving motor data | ||||
| @ -195,6 +198,22 @@ public class OpenRocketSaver extends RocketSaver { | ||||
| 		 * Otherwise use version 1.0. | ||||
| 		 */ | ||||
| 		 | ||||
| 		// Search the rocket for any ComponentPrests | ||||
| 		{ | ||||
| 			Rocket r = document.getRocket(); | ||||
| 			Iterator<RocketComponent> componentIterator = r.iterator(); | ||||
| 			boolean usesComponentPreset = false; | ||||
| 			while ( !usesComponentPreset && componentIterator.hasNext() ) { | ||||
| 				RocketComponent c = componentIterator.next(); | ||||
| 				if ( c.getPresetComponent() != null ) { | ||||
| 					usesComponentPreset = true; | ||||
| 				} | ||||
| 			} | ||||
| 			if ( usesComponentPreset ) { | ||||
| 				return FILE_VERSION_DIVISOR + 5; | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		// Check if design has simulations defined (version 1.4) | ||||
| 		if (document.getSimulationCount() > 0) { | ||||
| 			return FILE_VERSION_DIVISOR + 4; | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -9,6 +9,7 @@ import net.sf.openrocket.file.RocketSaver; | ||||
| import net.sf.openrocket.material.Material; | ||||
| import net.sf.openrocket.motor.Motor; | ||||
| import net.sf.openrocket.motor.ThrustCurveMotor; | ||||
| import net.sf.openrocket.preset.ComponentPreset; | ||||
| import net.sf.openrocket.rocketcomponent.ComponentAssembly; | ||||
| import net.sf.openrocket.rocketcomponent.MotorMount; | ||||
| import net.sf.openrocket.rocketcomponent.Rocket; | ||||
| @ -19,15 +20,22 @@ import net.sf.openrocket.util.LineStyle; | ||||
| 
 | ||||
| 
 | ||||
| public class RocketComponentSaver { | ||||
| 	 | ||||
| 
 | ||||
| 	protected RocketComponentSaver() { | ||||
| 		// Prevent instantiation from outside the package | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) { | ||||
| 		elements.add("<name>" + RocketSaver.escapeXML(c.getName()) + "</name>"); | ||||
| 		 | ||||
| 		 | ||||
| 
 | ||||
| 		ComponentPreset preset = c.getPresetComponent(); | ||||
| 		if ( preset != null ) { | ||||
| 			elements.add("<preset type=\"" + preset.getType() + | ||||
| 					"\" manufacturer=\"" + preset.getManufacturer().getSimpleName() + | ||||
| 					"\" partno=\"" + preset.getPartNo() + "\" digest=\"" + preset.getDigest() +"\"/>"); | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		// Save color and line style if significant | ||||
| 		if (!(c instanceof Rocket || c instanceof ComponentAssembly)) { | ||||
| 			Color color = c.getColor(); | ||||
| @ -35,23 +43,23 @@ public class RocketComponentSaver { | ||||
| 				elements.add("<color red=\"" + color.getRed() + "\" green=\"" + color.getGreen() | ||||
| 						+ "\" blue=\"" + color.getBlue() + "\"/>"); | ||||
| 			} | ||||
| 			 | ||||
| 
 | ||||
| 			LineStyle style = c.getLineStyle(); | ||||
| 			if (style != null) { | ||||
| 				// Type names currently equivalent to the enum names except for case. | ||||
| 				elements.add("<linestyle>" + style.name().toLowerCase(Locale.ENGLISH) + "</linestyle>"); | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 
 | ||||
| 
 | ||||
| 		// Save position unless "AFTER" | ||||
| 		if (c.getRelativePosition() != RocketComponent.Position.AFTER) { | ||||
| 			// The type names are currently equivalent to the enum names except for case. | ||||
| 			String type = c.getRelativePosition().name().toLowerCase(Locale.ENGLISH); | ||||
| 			elements.add("<position type=\"" + type + "\">" + c.getPositionValue() + "</position>"); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 
 | ||||
| 
 | ||||
| 		// Overrides | ||||
| 		boolean overridden = false; | ||||
| 		if (c.isMassOverridden()) { | ||||
| @ -66,26 +74,26 @@ public class RocketComponentSaver { | ||||
| 			elements.add("<overridesubcomponents>" + c.getOverrideSubcomponents() | ||||
| 					+ "</overridesubcomponents>"); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 
 | ||||
| 
 | ||||
| 		// Comment | ||||
| 		if (c.getComment().length() > 0) { | ||||
| 			elements.add("<comment>" + RocketSaver.escapeXML(c.getComment()) + "</comment>"); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	 | ||||
| 	 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	protected final String materialParam(Material mat) { | ||||
| 		return materialParam("material", mat); | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 
 | ||||
| 
 | ||||
| 	protected final String materialParam(String tag, Material mat) { | ||||
| 		String str = "<" + tag; | ||||
| 		 | ||||
| 
 | ||||
| 		switch (mat.getType()) { | ||||
| 		case LINE: | ||||
| 			str += " type=\"line\""; | ||||
| @ -99,27 +107,27 @@ public class RocketComponentSaver { | ||||
| 		default: | ||||
| 			throw new BugException("Unknown material type: " + mat.getType()); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		return str + " density=\"" + mat.getDensity() + "\">" + RocketSaver.escapeXML(mat.getName()) + "</" + tag + ">"; | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 
 | ||||
| 
 | ||||
| 	protected final List<String> motorMountParams(MotorMount mount) { | ||||
| 		if (!mount.isMotorMount()) | ||||
| 			return Collections.emptyList(); | ||||
| 		 | ||||
| 
 | ||||
| 		String[] motorConfigIDs = ((RocketComponent) mount).getRocket().getMotorConfigurationIDs(); | ||||
| 		List<String> elements = new ArrayList<String>(); | ||||
| 		 | ||||
| 
 | ||||
| 		elements.add("<motormount>"); | ||||
| 		 | ||||
| 
 | ||||
| 		for (String id : motorConfigIDs) { | ||||
| 			Motor motor = mount.getMotor(id); | ||||
| 			 | ||||
| 
 | ||||
| 			// Nothing is stored if no motor loaded | ||||
| 			if (motor == null) | ||||
| 				continue; | ||||
| 			 | ||||
| 
 | ||||
| 			elements.add("  <motor configid=\"" + id + "\">"); | ||||
| 			if (motor.getMotorType() != Motor.Type.UNKNOWN) { | ||||
| 				elements.add("    <type>" + motor.getMotorType().name().toLowerCase(Locale.ENGLISH) + "</type>"); | ||||
| @ -133,27 +141,27 @@ public class RocketComponentSaver { | ||||
| 			elements.add("    <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>"); | ||||
| 			elements.add("    <diameter>" + motor.getDiameter() + "</diameter>"); | ||||
| 			elements.add("    <length>" + motor.getLength() + "</length>"); | ||||
| 			 | ||||
| 
 | ||||
| 			// Motor delay | ||||
| 			if (mount.getMotorDelay(id) == Motor.PLUGGED) { | ||||
| 				elements.add("    <delay>none</delay>"); | ||||
| 			} else { | ||||
| 				elements.add("    <delay>" + mount.getMotorDelay(id) + "</delay>"); | ||||
| 			} | ||||
| 			 | ||||
| 
 | ||||
| 			elements.add("  </motor>"); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		elements.add("  <ignitionevent>" | ||||
| 				+ mount.getIgnitionEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "") | ||||
| 				+ "</ignitionevent>"); | ||||
| 		 | ||||
| 
 | ||||
| 		elements.add("  <ignitiondelay>" + mount.getIgnitionDelay() + "</ignitiondelay>"); | ||||
| 		elements.add("  <overhang>" + mount.getMotorOverhang() + "</overhang>"); | ||||
| 		 | ||||
| 
 | ||||
| 		elements.add("</motormount>"); | ||||
| 		 | ||||
| 
 | ||||
| 		return elements; | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,13 @@ | ||||
| package net.sf.openrocket.preset; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.DataOutputStream; | ||||
| import java.security.MessageDigest; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import net.sf.openrocket.material.Material; | ||||
| @ -9,6 +16,7 @@ import net.sf.openrocket.rocketcomponent.BodyTube; | ||||
| import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; | ||||
| import net.sf.openrocket.unit.UnitGroup; | ||||
| import net.sf.openrocket.util.BugException; | ||||
| import net.sf.openrocket.util.TextUtil; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
| @ -21,37 +29,38 @@ import net.sf.openrocket.util.BugException; | ||||
|  */ | ||||
| // FIXME - Implement clone. | ||||
| public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 	 | ||||
| 
 | ||||
| 	private final TypedPropertyMap properties = new TypedPropertyMap(); | ||||
| 	 | ||||
| 
 | ||||
| 	private boolean favorite = false; | ||||
| 	 | ||||
| 	private String digest = ""; | ||||
| 
 | ||||
| 	public enum Type { | ||||
| 		BODY_TUBE, | ||||
| 		NOSE_CONE; | ||||
| 
 | ||||
| 		Type[] compatibleTypes; | ||||
| 		 | ||||
| 
 | ||||
| 		Type () { | ||||
| 			compatibleTypes = new Type[1]; | ||||
| 			compatibleTypes[0] = this; | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		Type( Type ... t ) { | ||||
| 			 | ||||
| 
 | ||||
| 			compatibleTypes = new Type[t.length+1]; | ||||
| 			compatibleTypes[0] = this; | ||||
| 			for( int i=0; i<t.length; i++ ) { | ||||
| 				compatibleTypes[i+1] = t[i]; | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		public Type[] getCompatibleTypes() { | ||||
| 			return compatibleTypes; | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public final static TypedKey<Manufacturer> MANUFACTURER = new TypedKey<Manufacturer>("Manufacturer", Manufacturer.class); | ||||
| 	public final static TypedKey<String> PARTNO = new TypedKey<String>("PartNo",String.class); | ||||
| 	public final static TypedKey<Type> TYPE = new TypedKey<Type>("Type",Type.class); | ||||
| @ -63,7 +72,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 	public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class, UnitGroup.UNITS_LENGTH); | ||||
| 	public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class); | ||||
| 	public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class, UnitGroup.UNITS_MASS); | ||||
| 	 | ||||
| 
 | ||||
| 	public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>(); | ||||
| 	static { | ||||
| 		keyMap.put(MANUFACTURER.getName(), MANUFACTURER); | ||||
| @ -78,15 +87,15 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 		keyMap.put(FILLED.getName(), FILLED); | ||||
| 		keyMap.put(MASS.getName(), MASS); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException { | ||||
| 		 | ||||
| 
 | ||||
| 		ComponentPreset preset = new ComponentPreset(); | ||||
| 		// First do validation. | ||||
| 		if ( !props.containsKey(TYPE)) { | ||||
| 			throw new InvalidComponentPresetException("No Type specified " + props.toString() ); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		if (!props.containsKey(MANUFACTURER)) { | ||||
| 			throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() ); | ||||
| 		} | ||||
| @ -96,25 +105,25 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 		} | ||||
| 
 | ||||
| 		preset.properties.putAll(props); | ||||
| 		 | ||||
| 
 | ||||
| 		// Should check for various bits of each of the types. | ||||
| 		Type t = props.get(TYPE); | ||||
| 		switch ( t ) { | ||||
| 		case BODY_TUBE: { | ||||
| 			 | ||||
| 
 | ||||
| 			if ( !props.containsKey(LENGTH) ) { | ||||
| 				throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString()); | ||||
| 			} | ||||
| 			 | ||||
| 
 | ||||
| 			BodyTube bt = new BodyTube(); | ||||
| 			 | ||||
| 
 | ||||
| 			bt.setLength(props.get(LENGTH)); | ||||
| 			 | ||||
| 
 | ||||
| 			// Need to verify contains 2 of OD, thickness, ID.  Compute the third. | ||||
| 			boolean hasOd = props.containsKey(OUTER_DIAMETER); | ||||
| 			boolean hasId = props.containsKey(INNER_DIAMETER); | ||||
| 			boolean hasThickness = props.containsKey(THICKNESS); | ||||
| 			 | ||||
| 
 | ||||
| 			if ( hasOd ) { | ||||
| 				double outerRadius = props.get(OUTER_DIAMETER)/2.0; | ||||
| 				double thickness = 0; | ||||
| @ -140,7 +149,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 			preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0); | ||||
| 			preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0); | ||||
| 			preset.properties.put(THICKNESS, bt.getThickness()); | ||||
| 			 | ||||
| 
 | ||||
| 			// Need to translate Mass to Density. | ||||
| 			if ( props.containsKey(MASS) ) { | ||||
| 				String materialName = "TubeCustom"; | ||||
| @ -150,35 +159,57 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 				Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false); | ||||
| 				preset.properties.put(MATERIAL, m); | ||||
| 			} | ||||
| 			 | ||||
| 
 | ||||
| 			break; | ||||
| 		} | ||||
| 		case NOSE_CONE: { | ||||
| 			break; | ||||
| 		} | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		preset.computeDigest(); | ||||
| 
 | ||||
| 		return preset; | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	// Private constructor to encourage use of factory. | ||||
| 	private ComponentPreset() { | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Convenience method to retrieve the Type of this ComponentPreset. | ||||
| 	 *  | ||||
| 	 * @return | ||||
| 	 */ | ||||
| 	public Type getType() { | ||||
| 		return properties.get(TYPE); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Convenience method to retrieve the Manufacturer of this ComponentPreset. | ||||
| 	 * @return | ||||
| 	 */ | ||||
| 	public Manufacturer getManufacturer() { | ||||
| 		return properties.get(MANUFACTURER); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Convenience method to retrieve the PartNo of this ComponentPreset. | ||||
| 	 * @return | ||||
| 	 */ | ||||
| 	public String getPartNo() { | ||||
| 		return properties.get(PARTNO); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public String getDigest() { | ||||
| 		return digest; | ||||
| 	} | ||||
| 
 | ||||
| 	public boolean has(Object key) { | ||||
| 		return properties.containsKey(key); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public <T> T get(TypedKey<T> key) { | ||||
| 		T value = properties.get(key); | ||||
| 		if (value == null) { | ||||
| @ -186,7 +217,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 		} | ||||
| 		return (T) value; | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public boolean isFavorite() { | ||||
| 		return favorite; | ||||
| 	} | ||||
| @ -200,7 +231,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 		int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName()); | ||||
| 		if ( manuCompare != 0 ) | ||||
| 			return manuCompare; | ||||
| 		 | ||||
| 
 | ||||
| 		int partNoCompare = this.getPartNo().compareTo(p2.getPartNo()); | ||||
| 		return partNoCompare; | ||||
| 	} | ||||
| @ -209,9 +240,63 @@ public class ComponentPreset implements Comparable<ComponentPreset> { | ||||
| 	public String toString() { | ||||
| 		return get(MANUFACTURER).toString() + " " + get(PARTNO); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	public String preferenceKey() { | ||||
| 		return get(MANUFACTURER).toString() + "|" + get(PARTNO); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	private void computeDigest() { | ||||
| 
 | ||||
| 		try { | ||||
| 			ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
| 			DataOutputStream os = new DataOutputStream(bos); | ||||
| 
 | ||||
| 			List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet()); | ||||
| 
 | ||||
| 			Collections.sort(keys, new Comparator<TypedKey<?>>() { | ||||
| 				@Override | ||||
| 				public int compare( TypedKey<?> a, TypedKey<?> b ) { | ||||
| 					return a.getName().compareTo(b.getName()); | ||||
| 				} | ||||
| 			}); | ||||
| 
 | ||||
| 			for ( TypedKey<?> key : keys  ) { | ||||
| 
 | ||||
| 				Object value = properties.get(key); | ||||
| 
 | ||||
| 				os.writeBytes(key.getName()); | ||||
| 
 | ||||
| 				if ( key.getType() == Double.class ) { | ||||
| 					Double d = (Double) value; | ||||
| 					os.writeDouble(d); | ||||
| 				} else if (key.getType() == String.class ) { | ||||
| 					String s = (String) value; | ||||
| 					os.writeBytes(s); | ||||
| 				} else if (key.getType() == Manufacturer.class ) { | ||||
| 					String s = ((Manufacturer)value).getSimpleName(); | ||||
| 					os.writeBytes(s); | ||||
| 				} else if ( key.getType() == Finish.class ) { | ||||
| 					String s = ((Finish)value).name(); | ||||
| 					os.writeBytes(s); | ||||
| 				} else if ( key.getType() == Type.class ) { | ||||
| 					String s = ((Type)value).name(); | ||||
| 					os.writeBytes(s); | ||||
| 				} else if ( key.getType() == Boolean.class ) { | ||||
| 					Boolean b = (Boolean) value; | ||||
| 					os.writeBoolean(b); | ||||
| 				} else if ( key.getType() == Material.class ) { | ||||
| 					double d = ((Material)value).getDensity(); | ||||
| 					os.writeDouble(d); | ||||
| 				} | ||||
| 
 | ||||
| 			} | ||||
| 
 | ||||
| 			MessageDigest md5 = MessageDigest.getInstance("MD5"); | ||||
| 			digest = TextUtil.hexString(md5.digest( bos.toByteArray() )); | ||||
| 		} | ||||
| 		catch ( Exception e ) { | ||||
| 			throw new BugException(e); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user